diff --git a/angular.json b/angular.json index 5952480a9ced5a16dc48a17e46fb1669d4e89426..c46936b10f55fdfb9c9f7319765c57bef8d4eb3d 100644 --- a/angular.json +++ b/angular.json @@ -30,7 +30,10 @@ ], "styles": [ "./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", - "src/styles.css" + "./node_modules/font-awesome/css/font-awesome.css", + "./node_modules/bootstrap/dist/css/bootstrap.min.css", + "./node_modules/animate.css/animate.min.css", + "src/styles.scss" ], "scripts": [] }, diff --git a/package.json b/package.json index c9142eda9628c3fc9cf5db44b62bc836760bdad5..7305e6006f4c94a3087b04167971f9ca74ccc9b0 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,11 @@ "@fortawesome/free-solid-svg-icons": "^5.12.1", "@tweenjs/tween.js": "~18.5.0", "ali-oss": "^6.5.1", + "animate.css": "3.7.0", + "bootstrap": "4.1.1", "compressing": "^1.5.0", + "font-awesome": "4.7.0", + "lodash": "4.17.10", "ng-zorro-antd": "^8.5.2", "rxjs": "~6.5.4", "spark-md5": "^3.0.0", diff --git a/src/app/form/form.component.css b/src/app/form/form.component.css deleted file mode 100644 index 41f7757290161973406aeec2177e0feef3ffcdaa..0000000000000000000000000000000000000000 --- a/src/app/form/form.component.css +++ /dev/null @@ -1,8 +0,0 @@ -@import '../style/common_mixin.css'; - - -.model-content { - width: 100%; - height: 100%; - -} diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html index a51aa6ba14036cd5e1b9a4544b55a7bf0b75c1c8..a17f2640cd1b43996951340ddf0386d2ecaa1159 100644 --- a/src/app/form/form.component.html +++ b/src/app/form/form.component.html @@ -1,25 +1,91 @@ - -<div class="model-content"> - - - <div style="position: absolute; left: 200px; top: 100px; width: 800px;"> - - <input type="text" nz-input [(ngModel)]="item.text" (blur)="save()"> - +<nz-form-item> + <nz-form-label [nzSpan]="4">Vocabulary</nz-form-label> + <nz-form-control [nzSpan]="8"> <app-upload-image-with-preview - [picUrl]="item.pic_url" - (imageUploaded)="onImageUploadSuccess($event, 'pic_url')" + [picUrl]="item.contentObj.pic_id" + (imageUploaded)="onHandleQuestionImage($event)" ></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> - - -</div> - \ No newline at end of file + <input type="text" nz-input placeholder="Please input title" + [(ngModel)]="item.contentObj.title" (blur)="updateQuestionTitle()"> + </nz-form-control> + <app-audio-recorder [audioUrl]="item.contentObj.question_audio_id" + (audioUploaded)="onHandleQuestionOptionItemAudio($event)"> + </app-audio-recorder> +</nz-form-item> +<nz-form-item> + <nz-form-label [nzSpan]="4">Option</nz-form-label> + <nz-form-control [nzSpan]="20"> + <button + nz-button + nzType="default" + (click)="addNewOptionItem()" + [disabled]="options.length >= 4" + > + <i class="anticon anticon-plus-circle-o"></i> + Add + </button> + <div class="opt-container"> + <div + class="opt-wrap" + *ngFor="let item of options; let i = index" + > + <div class="tool-bar"> + <label nz-checkbox [ngModel]="item.right" + (ngModelChange)="setRightAnswer(i)" class="right-answer"> + Right + </label> + <div class="button-group"> + <button + nz-button + nzType="primary" + nzSize="small" + nzShape="circle" + style="margin-right: 1rem;" + (click)="onHandleSwitchContent(item)" + > + <i class="anticon anticon-retweet"></i> + </button> + <nz-dropdown [nzTrigger]="'click'"> + <button + nz-button + nzType="primary" + nzSize="small" + nzShape="circle" + nz-dropdown + > + <i class="anticon anticon-setting"></i> + </button> + <ul nz-menu> + <li nz-menu-item (click)="onMovePage(i, -1)" [class.disabled]="i < 1"> + <i class="anticon anticon-arrow-up"></i> + Move Up + </li> + <li nz-menu-item (click)="onMovePage(i, 1)" [class.disabled]="i + 1 >= options.length"> + <i class="anticon anticon-arrow-down"></i> + Move Down + </li> + <li nz-menu-item (click)="onDeleteCoursewareItem(i)"> + <i class="anticon anticon-delete"></i> + Delete + </li> + </ul> + </nz-dropdown> + </div> + </div> + <div class="content-editor"> + <input *ngIf="item.type === OPTION_TYPE.TEXT" type="text" nz-input [(ngModel)]="item.text" + placeholder="Please input title" (blur)="updateOptionItemText(i)"> + <app-upload-image-with-preview + *ngIf="item.type === OPTION_TYPE.PIC" + [picUrl]="item.pic_id" + (imageUploaded)="onHandleOptionItemImage($event, i)" + ></app-upload-image-with-preview> + <app-audio-recorder *ngIf="item.type === OPTION_TYPE.AUDIO" + [audioUrl]="item.audio_id" + (audioUploaded)="onHandleOptionItemAudio($event, i)"> + </app-audio-recorder> + </div> + </div> + </div> + </nz-form-control> +</nz-form-item> diff --git a/src/app/form/form.component.scss b/src/app/form/form.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..ba5a8d5ae5fc59cb2f2ff8c28d79b435f4924509 --- /dev/null +++ b/src/app/form/form.component.scss @@ -0,0 +1,28 @@ +.opt-container { + width: 100%; + display: flex; + flex-wrap: wrap; +.opt-wrap { + margin: 0 1rem 1rem 0; + display: flex; + flex-direction: column; + height: 14rem; + width: 20rem; + border: 2px dashed #ddd; + border-radius: 0.5rem; + padding: 0.8rem; +.tool-bar { + display: flex; + justify-content: space-between; + align-items: center; +} +.content-editor { + height: 100%; +} +} +} + +.disabled { + pointer-events: none; + opacity: 0.6; +} diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts index 8bf084e921171b4cf11f2779fd6dfc49daa67fdb..c9de59da7415d7fcc2d57de94d2a69bae9efa283 100644 --- a/src/app/form/form.component.ts +++ b/src/app/form/form.component.ts @@ -1,36 +1,53 @@ import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core'; - +import * as _ from 'lodash'; +import { NzModalService } from 'ng-zorro-antd'; @Component({ selector: 'app-form', templateUrl: './form.component.html', - styleUrls: ['./form.component.css'] + styleUrls: ['./form.component.scss'] }) export class FormComponent implements OnInit, OnChanges, OnDestroy { // å‚¨å˜æ•°æ®ç”¨ - saveKey = "test_0011"; + saveKey = 'ww_question2'; // 储å˜å¯¹è±¡ item; + originContent; + options = []; + OPTION_TYPE = {TEXT: 1, PIC: 2, AUDIO: 3}; - constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) { + constructor(private appRef: ApplicationRef, + private modalService: NzModalService, + private changeDetectorRef: ChangeDetectorRef) { } ngOnInit() { - this.item = {}; + this.item = { + contentObj: {} + }; // 获å–å˜å‚¨çš„æ•°æ® - (<any> window).courseware.getData((data) => { + (window as any).courseware.getData((data) => { if (data) { this.item = data; + this.originContent = {...this.item.contentObj}; + this.options = _.get(this.item.contentObj, 'options', []); + // Ken 2019-04-14 17:41 把showPic, showText的逻辑æ¢ä¸ºtype + _.forEach(this.options, o => { + if (_.isUndefined(o.type)) { + o.type = o.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT; + } + }); } + this.init(); this.changeDetectorRef.markForCheck(); this.changeDetectorRef.detectChanges(); @@ -54,25 +71,18 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { } - /** - * 储å˜å›¾ç‰‡æ•°æ® - * @param e - */ - onImageUploadSuccess(e, key) { - - this.item[key] = e.url; - this.save(); - } + // onImageUploadSuccess(e, key) { + // + // this.item[key] = e.url; + // this.save(); + // } - /** - * 储å˜éŸ³é¢‘æ•°æ® - * @param e - */ - onAudioUploadSuccess(e, key) { - this.item[key] = e.url; - this.save(); - } + // onAudioUploadSuccess(e, key) { + // + // this.item[key] = e.url; + // this.save(); + // } @@ -80,7 +90,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { * å‚¨å˜æ•°æ® */ save() { - (<any> window).courseware.setData(this.item, null, this.saveKey); + (window as any).courseware.setData(this.item, null, this.saveKey); this.refresh(); } @@ -93,5 +103,90 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { }, 1); } + + onHandleQuestionImage(e) { + this.item.contentObj.pic_id = e.url; + this.save(); + } + + updateQuestionTitle() { + this.save(); + } + + onHandleOptionItemImage(e, index) { + this.options[index].pic_id = e.url; + this.item.contentObj.options = this.options; + this.save(); + } + + onHandleOptionItemAudio(e, index) { + this.options[index].audio_id = e.url; + this.item.contentObj.options = this.options; + this.save(); + } + onHandleQuestionOptionItemAudio(e) { + console.log(e); + this.item.contentObj.question_audio_id = e.url; + this.save(); + } + updateOptionItemText(index) { + this.item.contentObj.options = this.options; + this.save(); + } + + setRightAnswer(index) { + this.options[index].right = !this.options[index].right; + this.item.contentObj.options = this.options; + this.save(); + } + + addNewOptionItem() { + const content = { + text: '', + pic_id: null, + right: false, + type: this.OPTION_TYPE.TEXT, + }; + this.options.push(content); + } + + onHandleSwitchContent(item) { + switch (item.type) { + case this.OPTION_TYPE.TEXT: + item.type = this.OPTION_TYPE.PIC; + break; + case this.OPTION_TYPE.PIC: + item.type = this.OPTION_TYPE.AUDIO; + break; + case this.OPTION_TYPE.AUDIO: + item.type = this.OPTION_TYPE.TEXT; + break; + } + this.item.contentObj.options = this.options; + this.save(); + } + onMovePage(index, direction) { + const temp = this.options[index + direction]; + this.options[index + direction] = this.options[index]; + this.options[index] = temp; + this.options = [...this.options]; + this.item.contentObj.options = this.options; + this.save(); + } + + onDeleteCoursewareItem(index) { + this.modalService.confirm({ + nzTitle: 'Are you sure delete this?', + nzOkText: 'Yes', + nzOkType: 'danger', + nzCancelText: 'No', + nzOnOk: () => { + this.options.splice(index, 1); + this.item.contentObj.options = this.options; + this.save(); + }, + }); + } + } diff --git a/src/app/play/play.component.css b/src/app/play/play.component.css deleted file mode 100644 index bbda49566c0a6aa2c621e2f863caf1849e492572..0000000000000000000000000000000000000000 --- a/src/app/play/play.component.css +++ /dev/null @@ -1,19 +0,0 @@ -.game-container { - width: 100%; - height: 100%; - background: #ffffff; - background-size: cover; -} - -#canvas { - -} - - - -@font-face -{ - font-family: 'BRLNSDB'; - src: url("../../assets/font/BRLNSDB.TTF") ; -} - diff --git a/src/app/play/play.component.html b/src/app/play/play.component.html index 960fb495a359c3c7803c4370d8c7d8457517d70e..cd2d681da77331d651e2efa5794c7911175ce45f 100644 --- a/src/app/play/play.component.html +++ b/src/app/play/play.component.html @@ -1,3 +1,36 @@ -<div class="game-container" #wrap> - <canvas id="canvas" #canvas></canvas> +<div class="question-player" style="position: relative"> + <div class="top-part"> + <img + *ngIf="data.contentObj.pic_id" + [src]="data.contentObj.pic_id " + alt="cover" + (dragstart)="false;$event.preventDefault();" + (click)="onHandleZoomIn($event)" + [@zoom]="zoomIn ? 'in' : 'none'" + /> + <div class="title">{{data.contentObj.title}}</div> + </div> + <div class="opt-container"> + <div + class="opt-wrap" + *ngFor="let item of options; let i = index" + [@tip-border]="item.state || 'none'" + (click)="handleAnimation(item)" + > + <span class="opt-circle" [@tip-circle]="item.state || 'none'">{{transCharCode(i)}}</span> + <div class="text" *ngIf="item.type === OPTION_TYPE.TEXT">{{item.text}}</div> + <div class="audio" style="text-align: center" *ngIf="item.type === OPTION_TYPE.AUDIO" (click)="playAudio(item, $event)"> + <i class="anticon anticon-sound"></i> + </div> + <img *ngIf="item.type === OPTION_TYPE.PIC" [src]="item.pic_id" alt="" + (dragstart)="false;$event.preventDefault();"/> + </div> + </div> + <div (click)="playQuestionAudio($event)" + style="width: 100px;height: 100px;position: absolute;bottom: 50px;right: 80px;"> + <img style="max-width: 100%;max-height: 100%;" *ngIf="!playerState.getValue().isPlaying" src="/assets/question/play.png" alt="" (dragstart)="false;$event.preventDefault();"> + <img style="max-width: 100%;max-height: 100%;" *ngIf="playerState.getValue().isPlaying" src="/assets/question/pause.png" alt="" (dragstart)="false;$event.preventDefault();"> + </div> + <!--<img src="/assets/question/left.png" alt="ufo" class="left-ufo" (dragstart)="false;$event.preventDefault();">--> + <!--<img src="/assets/question/right.png" alt="ufo" class="right-ufo" (dragstart)="false;$event.preventDefault();">--> </div> diff --git a/src/app/play/play.component.scss b/src/app/play/play.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..00b6669c9cba487364bb7eaa820ec1b8156ef370 --- /dev/null +++ b/src/app/play/play.component.scss @@ -0,0 +1,103 @@ +.question-player { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + width: 100%; + background-image: url("../../assets/common_bg.jpg"); + background-size: cover; + background-repeat: no-repeat; + + .top-part { + height: 35%; + min-width: 60%; + max-width: 90%; + display: flex; + justify-content: center; + align-items: center; + + img { + //width: 40%; + height: 60%; + margin-right: 1.5rem; + object-fit: contain; + } + .title { + font-size: 2.5rem; + color: #ffb00f; + background-color: white; + padding: 0.5rem 1rem; + border-radius: 1rem; + } + } + .opt-container { + height: 55%; + width: 80%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + + .opt-wrap { + width: 30%; + height: 45%; + position: relative; + background-color: #ffffff; + margin: 1rem; + border-radius: 0.8rem; + display: flex; + justify-content: center; + align-items: center; + border: 0.3rem solid #ffffff; + + .opt-circle { + width: 2.5rem; + height: 2.5rem; + background-color: #ffffff; + position: absolute; + border-radius: 50%; + left: -1rem; + top: -1rem; + text-align: center; + line-height: 2.5rem; + font-size: 24px; + font-weight: 500; + } + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + + .text { + font-size: 28px; + font-weight: 500; + } + + .audio { + color: white; + background-color: #00BCD4; + border-radius: 5px; + font-size: 5vh; + font-weight: 500; + width: 13vh; + cursor: pointer; + } + } + } + .left-ufo { + width: 10%; + object-fit: contain; + position: absolute; + left: 15vh; + bottom: 20vh; + } + .right-ufo { + width: 10%; + object-fit: contain; + position: absolute; + right: 15vh; + bottom: 20vh; + } +} diff --git a/src/app/play/play.component.ts b/src/app/play/play.component.ts index f4b8a863d45aa9f51852483802422d4dfacce939..b6ed09ef238fbe89692c0904b8dc808396e25e27 100644 --- a/src/app/play/play.component.ts +++ b/src/app/play/play.component.ts @@ -1,5 +1,5 @@ import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core'; - +import * as _ from 'lodash'; import { Label, MySprite, tweenChange, @@ -11,6 +11,8 @@ import {Subject} from 'rxjs'; import {debounceTime} from 'rxjs/operators'; import TWEEN from '@tweenjs/tween.js'; +import {BehaviorSubject} from 'rxjs'; +import { animate, keyframes, state, style, transition, trigger } from '@angular/animations'; @@ -18,662 +20,187 @@ import TWEEN from '@tweenjs/tween.js'; @Component({ selector: 'app-play', templateUrl: './play.component.html', - styleUrls: ['./play.component.css'] + styleUrls: ['./play.component.scss'], + animations: [ + trigger('tip-border', [ + state('right', style({border: '0.3rem solid #52E55D', 'background-color': '#52E55D', transform: 'scale(1)'})), + state('none', style({transform: 'scale(1)'})), + state('wrong', style({border: '0.3rem solid #ff3333', 'background-color': '#ff3333', transform: 'scale(1)'})), + transition('none => wrong', animate('.2s ease-in', keyframes([ + style({transform: 'scale(1)'}), + style({transform: 'scale(0.6)'}), + style({transform: 'scale(1)'}), + ]))), // ç¬¬ä¸€ä¸ªå‚æ•°:动画时间, ç¬¬äºŒä¸ªå‚æ•°:动画延迟时间 + transition('none => right', animate('.2s ease-in', keyframes([ + style({transform: 'scale(1)'}), + style({transform: 'scale(0.6)'}), + style({transform: 'scale(1)'}), + ]))), + transition('wrong => none', animate('.2s ease-out')) + ]), + trigger('tip-circle', [ + state('right', style({'background-color': '#52E55D'})), + state('none', style({})), + state('wrong', style({'background-color': '#ff3333'})), + transition('none => wrong', animate('.2s ease-in')), // ç¬¬ä¸€ä¸ªå‚æ•°:动画时间, ç¬¬äºŒä¸ªå‚æ•°:动画延迟时间 + transition('none => right', animate('.2s ease-in')), + transition('wrong => none', animate('.2s ease-out')) + ]), + trigger('zoom', [ + state('in', style({ + position: 'absolute', 'z-index': '1', top: '0', left: '0', right: '0', bottom: '0', margin: 'auto', + height: '60%', 'object-fit': 'contain', 'max-width': '80%' + })), + state('none', style({})), + transition('none => in', animate('.3s', keyframes([ + style({ + height: '10%', position: 'absolute', 'z-index': '1', top: '0', left: '0', + right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%', + opacity: '0' + }), + style({ + height: '20%', position: 'absolute', 'z-index': '1', top: '0', left: '0', + right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%', + opacity: '0.2' + }), + style({ + height: '30%', position: 'absolute', 'z-index': '1', top: '0', left: '0', + right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%', + opacity: '0.4' + }), + style({ + height: '40%', position: 'absolute', 'z-index': '1', top: '0', left: '0', + right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%', + opacity: '0.6' + }), + style({ + height: '50%', position: 'absolute', 'z-index': '1', top: '0', left: '0', + right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%', + opacity: '0.8' + }), + ]))), // ç¬¬ä¸€ä¸ªå‚æ•°:动画时间, ç¬¬äºŒä¸ªå‚æ•°:动画延迟时间 + transition('in => none', animate('.5s ease-out')) + ]) + ] }) export class PlayComponent implements OnInit, OnDestroy { - - @ViewChild('canvas', {static: true }) canvas: ElementRef; - @ViewChild('wrap', {static: true }) wrap: ElementRef; + saveKey = 'ww_question2'; + questionAudio = new Audio(); + playerState = new BehaviorSubject<any>({ isPlaying: false }); + options; + zoomIn; + rightAudio = new Audio(); + wrongAudio = new Audio(); + OPTION_TYPE = {TEXT: 1, PIC: 2, AUDIO: 3}; + optionAudio = new Audio(); // æ•°æ® data; + constructor() { + this.rightAudio.src = '/assets/right.mp3'; + this.rightAudio.load(); + this.wrongAudio.src = '/assets/wrong.mp3'; + this.wrongAudio.load(); + this.questionAudio.addEventListener('play', () => { + this.playerState.next({ isPlaying: true }); + }); - ctx; - - canvasWidth = 1280; // canvas实际宽度 - canvasHeight = 720; // canvas实际高度 - - canvasBaseW = 1280; // canvas 资æºé¢„设宽度 - canvasBaseH = 720; // canvas 资æºé¢„设高度 - - mx; // 点击xåæ ‡ - my; // 点击yåæ ‡ - - - // èµ„æº - rawImages = new Map(res); - rawAudios = new Map(resAudio); - - images = new Map(); - - 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(); + this.questionAudio.addEventListener('pause', () => { + this.playerState.next({ isPlaying: false }); + }); } - ngOnInit() { - this.data = {}; + ngOnInit() { + this.data = { + contentObj: {} + }; + this.zoomIn = false; + this.options = _.get(this.data.contentObj, 'options', []).map(item => { + item.state = ''; + // Ken 2019-04-15 22:38 把showPic, showText的逻辑æ¢ä¸ºtype + if (_.isUndefined(item.type)) { + item.type = item.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT; + } + return item; + }); // èŽ·å–æ•°æ® - const getData = (<any> window).courseware.getData; + const getData = (window as any).courseware.getData; getData((data) => { - if (data && typeof data == 'object') { + if (data && typeof data === 'object') { + console.log(data); this.data = data; + this.zoomIn = false; + this.options = _.get(this.data.contentObj, 'options', []).map(item => { + item.state = ''; + // Ken 2019-04-15 22:38 把showPic, showText的逻辑æ¢ä¸ºtype + if (_.isUndefined(item.type)) { + item.type = item.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT; + } + return item; + }); + } + const question_audio_id = _.get(this.data.contentObj, 'question_audio_id', null); + // console.log('question_audio_id', question_audio_id); + this.questionAudio.src = question_audio_id; + this.questionAudio.load(); + if (window['air']) { + window['air'].hideAirClassLoading(this.saveKey, this.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.update(); - }); + this.rightAudio.pause(); + this.wrongAudio.pause(); + this.optionAudio.pause(); + this.rightAudio = null; + this.wrongAudio = null; + this.optionAudio = null; } - - 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(); - } - } - - updateArr(arr) { - if (!arr) { - return; - } - for (let i = 0; i < arr.length; i++) { - arr[i].update(this); - } - } - - - - - - - - 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(); + transCharCode(index) { + return String.fromCharCode(65 + index); } - - playAudio(key, now = false, callback = null) { - - const audio = this.audioObj[key]; - if (audio) { - if (now) { - audio.pause(); - audio.currentTime = 0; - } - - if (callback) { - audio.onended = () => { - callback(); - }; - } - audio.play(); + handleAnimation(item) { + if (item.right) { + item.state = 'right'; + this.rightAudio.play(); + } else { + item.state = 'wrong'; + this.wrongAudio.play(); + setTimeout(() => { + item.state = ''; + }, 1500); } } - - - 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); - }); - return Promise.all(pr); - } - - preload(url) { - return new Promise((resolve, reject) => { - const img = new Image(); - // img.crossOrigin = "anonymous"; - img.onload = () => resolve(img); - img.onerror = reject; - img.src = url; - }); + onHandleZoomIn(e) { + e.stopPropagation(); + this.zoomIn = !this.zoomIn; } - preloadAudio(url) { - return new Promise((resolve, reject) => { - const audio = new Audio(); - audio.oncanplay = (a) => { - resolve(); - }; - audio.onerror = () => { - reject(); - }; - audio.src = url; - audio.load(); - }); - } - - - renderAfterResize() { - this.canvasWidth = this.wrap.nativeElement.clientWidth; - this.canvasHeight = this.wrap.nativeElement.clientHeight; - this.init(); - } - - - - - - checkClickTarget(target) { - - const rect = target.getBoundingBox(); - - if (this.checkPointInRect(this.mx, this.my, rect)) { - return true; - } - return false; + playAudio(option, e) { + e.stopPropagation(); + this.optionAudio.src = option.audio_id; + // this.optionAudio.load(); + this.optionAudio.play(); } - - 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; + currentQuestionStatus() { + const sts = this.playerState.asObservable(); + console.log(this.playerState.getValue().isPlaying) } - - checkPointInRect(x, y, rect) { - if (x >= rect.x && x <= rect.x + rect.width) { - if (y >= rect.y && y <= rect.y + rect.height) { - return true; - } + playQuestionAudio(e) { + e.stopPropagation(); + if (this.questionAudio.paused) { + this.questionAudio.play(); + } else { + this.questionAudio.pause(); } - 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; - } - - 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; - }); - } - - nextPage() { - - if (this.curPic == this.pic2) { - return; - } - - 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; - }); - } - - 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) { - - } - - 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); - - - } - - - } diff --git a/src/app/style/mixin.scss b/src/app/style/mixin.scss new file mode 100644 index 0000000000000000000000000000000000000000..dd32d36e1154249b2b4c2ec319ef10923ad39134 --- /dev/null +++ b/src/app/style/mixin.scss @@ -0,0 +1,18 @@ +@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; +} diff --git a/src/assets/common_bg.jpg b/src/assets/common_bg.jpg new file mode 100755 index 0000000000000000000000000000000000000000..e1a3aac22a633ca64eb9bc038d0be799e5fda585 Binary files /dev/null and b/src/assets/common_bg.jpg differ diff --git a/src/assets/question/bg.png b/src/assets/question/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..017779dcf41100c9bf31b1866b8c7d64d0452c63 Binary files /dev/null and b/src/assets/question/bg.png differ diff --git a/src/assets/question/left.png b/src/assets/question/left.png new file mode 100644 index 0000000000000000000000000000000000000000..d3d4d417b89705e6484e93d9894f3ab22e2a11d0 Binary files /dev/null and b/src/assets/question/left.png differ diff --git a/src/assets/question/pause.png b/src/assets/question/pause.png new file mode 100755 index 0000000000000000000000000000000000000000..671bc71ba557eca518ece6e9ae8b3e338b15cd1f Binary files /dev/null and b/src/assets/question/pause.png differ diff --git a/src/assets/question/play.png b/src/assets/question/play.png new file mode 100755 index 0000000000000000000000000000000000000000..d9f76d79145bd783077d340cd8ea42cd617bf8ee Binary files /dev/null and b/src/assets/question/play.png differ diff --git a/src/assets/question/right.png b/src/assets/question/right.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf47c0827e540e014f07dc411ad9974dea43103 Binary files /dev/null and b/src/assets/question/right.png differ diff --git a/src/assets/right.mp3 b/src/assets/right.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c02694bcae3bd70abeca0d77230e47c33a829434 Binary files /dev/null and b/src/assets/right.mp3 differ diff --git a/src/assets/wrong.mp3 b/src/assets/wrong.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6c4b324803190f0a4c9ca28742a34c55962d74f0 Binary files /dev/null and b/src/assets/wrong.mp3 differ diff --git a/src/styles.css b/src/styles.css deleted file mode 100644 index 90d4ee0072ce3fc41812f8af910219f9eea3c3de..0000000000000000000000000000000000000000 --- a/src/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/src/styles.scss b/src/styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..c1f593fa6def1161f9667969f2f00faeec36a95c --- /dev/null +++ b/src/styles.scss @@ -0,0 +1,51 @@ +/* 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; +}