Commit 050d85cb authored by liujiaxin's avatar liujiaxin

add form

parent cefb9793
......@@ -16,15 +16,15 @@ const compressing = require("compressing");
//Windows系统上'Windows_NT'
let sysType = os.type();
Date.prototype.Format = function (fmt) {
Date.prototype.Format = function(fmt) {
var o = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S": this.getMilliseconds()
"M+" : this.getMonth() + 1,
"d+" : this.getDate(),
"h+" : this.getHours(),
"m+" : this.getMinutes(),
"s+" : this.getSeconds(),
"q+" : Math.floor((this.getMonth() + 3) / 3),
"S" : this.getMilliseconds()
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
......@@ -34,24 +34,24 @@ Date.prototype.Format = function (fmt) {
return fmt;
}
function clean(zipPath) {
if (fs.existsSync(zipPath)) {
function clean(zipPath){
if(fs.existsSync(zipPath)){
fs.unlinkSync(zipPath);
}
}
const runSpawn = async function () {
const runSpawn = async function (){
await new Promise(function (resolve, reject) {
await new Promise(function(resolve,reject){
let pkg = require("../package.json");
let ls;
if (sysType === "Windows_NT") {
if(sysType==="Windows_NT"){
//ng build --prod --build--optimizer --base-href /ng-one/
ls = spawn("cmd.exe", ['/c', 'ng', 'build', '--prod', '--build--optimizer', '--base-href', '/template-base-href/']);
} else {
ls = spawn("ng", ['build', '--prod', '--build--optimizer', '--base-href', '/template-base-href/']);
ls = spawn("cmd.exe", ['/c', 'ng', 'build', '--prod', '--build--optimizer', '--base-href', '/template-base-href/'] );
}else{
ls = spawn("ng", ['build', '--prod', '--build--optimizer', '--base-href', '/template-base-href/'] );
}
ls.stdout.on('data', (data) => {
......@@ -66,23 +66,22 @@ const runSpawn = async function () {
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
//要压缩的目录
let zippath = path.resolve(__dirname, "../dist", pkg.name);
let zippath = path.resolve(__dirname,"../dist", pkg.name);
//压缩包的存放目录
let date = new Date();
// let zipname = pkg.name+"_"+date.Format("yyyyMMdd hh-mm-ss");
let zipname = 'form';
let zipdir = path.resolve(__dirname, "../publish/" + zipname + ".zip");
let zipname = pkg.name+"_"+date.Format("yyyyMMdd hh-mm-ss");
let zipdir = path.resolve(__dirname,"../publish/"+zipname+".zip");
clean(zipdir); //删除原有的包
const tarStream = new compressing.zip.Stream();
fs.readdir(zippath, function (err, files) {
if (err) {
fs.readdir(zippath,function(err,files){
if(err){
console.log("======文件打开异常======");
console.log(err);
reject();
}
for (let i = 0; i < files.length; i++) {
tarStream.addEntry(zippath + "/" + files[i]);
for(let i=0;i<files.length;i++){
tarStream.addEntry(zippath+"/"+files[i]);
}
let writeStream = fs.createWriteStream(zipdir);
tarStream.pipe(writeStream);
......@@ -104,7 +103,7 @@ const runSpawn = async function () {
// }
// projects = process.argv[2];
let exec = async function () {
let exec = async function(){
//压缩模板
await runSpawn();
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -24,6 +24,7 @@ import {AudioRecorderComponent} from './common/audio-recorder/audio-recorder.com
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from '@fortawesome/free-regular-svg-icons';
import { LrcEditorComponent } from './common/lrc-editor/lrc-editor.component';
registerLocaleData(zh);
......@@ -41,7 +42,8 @@ registerLocaleData(zh);
UploadVideoComponent,
CustomHotZoneComponent,
PlayerContentWrapperComponent
PlayerContentWrapperComponent,
LrcEditorComponent,
],
imports: [
......
......@@ -329,6 +329,7 @@ export class MySprite extends Sprite {
this.scaleX = this.scaleY = value;
}
getBoundingBox() {
const getParentData = (item) => {
......@@ -1956,6 +1957,8 @@ export class HotZoneItem extends MySprite {
audio_url;
pic_url;
text;
gIdx;
private _itemType;
private shapeRect: ShapeRect;
......@@ -2208,6 +2211,8 @@ export class EditorItem extends MySprite {
arrow: MySprite;
label: Label;
text;
arrowTop;
arrowRight;
showLabel(text = null) {
......@@ -2246,6 +2251,13 @@ export class EditorItem extends MySprite {
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();
......@@ -2262,16 +2274,25 @@ export class EditorItem extends MySprite {
this.hideLabel();
}
refreshLabelScale() {}
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() {
......@@ -2325,784 +2346,3 @@ export class EditorItem extends MySprite {
}
//
//
// 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;
//
// }
......@@ -32,15 +32,61 @@
<nz-divider style="margin-top: 10px;"></nz-divider>
<div style="margin-top: -20px; margin-bottom: 5px; width: 100%;">
<div *ngIf="customTypeGroupArr">
<nz-radio-group [ngModel]="it.gIdx" (ngModelChange)="customRadioChange($event, it)" style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap;">
<div *ngFor="let group of customTypeGroupArr; let gIdx = index" style="display: flex; ">
<label nz-radio nzValue="{{gIdx}}">{{group.name}}</label>
</div>
</nz-radio-group>
</div>
<div *ngIf="!customTypeGroupArr">
<nz-radio-group [ngModel]="it.itemType" (ngModelChange)="radioChange($event, it)" style="display: flex; align-items: center; justify-content: center">
<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>
<div *ngIf="customTypeGroupArr && customTypeGroupArr[it.gIdx]">
<div *ngIf="customTypeGroupArr[it.gIdx].pic">
<app-upload-image-with-preview
[picUrl]="it?.pic_url"
(imageUploaded)="onItemImgUploadSuccess($event, it)">
</app-upload-image-with-preview>
</div>
<div *ngIf="customTypeGroupArr[it.gIdx].text" style="margin-top: 5px">
<input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
</div>
<div *ngIf="customTypeGroupArr[it.gIdx].anima" align="center" style="margin-top: 5px">
<button nz-button (click)="setAnimaBtnClick(i)" >
<i nz-icon nzType="tool" nzTheme="outline"></i>
配置龙骨动画
</button>
</div>
<div *ngIf="customTypeGroupArr[it.gIdx].audio" style="display: flex;align-items: center; margin-top: 5px">
<app-audio-recorder
style="margin: auto"
[audioUrl]="it.audio_url"
(audioUploaded)="onItemAudioUploadSuccess($event, it)"
></app-audio-recorder>
</div>
</div>
<div *ngIf="!customTypeGroupArr">
<div *ngIf="it.itemType == 'pic'">
<app-upload-image-with-preview
[picUrl]="it?.pic_url"
......@@ -52,13 +98,26 @@
<input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
</div>
<div style="width: 100%; margin-top: 5px;">
<div *ngIf="isHasAudio"
style="width: 100%; margin-top: 5px; display: flex; align-items: center; justify-items: center;">
<app-audio-recorder
style="margin: auto"
[audioUrl]="it.audio_url"
(audioUploaded)="onItemAudioUploadSuccess($event, it)"
></app-audio-recorder>
</div>
<div *ngIf="it.itemType == 'rect' && isHasAnima" align="center" style="margin-bottom: 5px">
<button nz-button (click)="setAnimaBtnClick(i)" >
<i nz-icon nzType="tool" nzTheme="outline"></i>
配置龙骨动画
</button>
</div>
</div>
</div>
</div>
......@@ -83,12 +142,29 @@
<div class="save-box">
<button class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
<button style="margin-left: 200px" class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
(click)="saveClick()" >
<i nz-icon nzType="save"></i>
Save
</button>
<div style="height: 40px; display: flex; justify-items: center;" >
<label style="margin-left: 40px" nz-checkbox [(ngModel)]="bgItem.isShowDebugLine">显示辅助框</label>
<button style="margin-left: 20px; margin-top: -5px" nz-button (click)="copyHotZoneData()"> 复制基础数据 </button>
<div style="margin-left: 10px; margin-top: -5px" >
<span>粘贴数据: </span>
<input style="width: 100px;" type="text" nz-input [(ngModel)]="pasteData" >
<button style="margin-left: 5px;" nz-button [disabled]="pasteData==''" nzType="primary" (click)="importData()">导入</button>
</div>
<!--<button style="margin-left: 20px; margin-top: -5px" nz-button (click)="pasteHotZoneData()" > 粘贴数据 </button>-->
</div>
</div>
......@@ -98,3 +174,50 @@
<label style="opacity: 0; position: absolute; top: 0px; font-family: 'BRLNSR_1'">1</label>
<!--龙骨面板-->
<nz-modal [(nzVisible)]="animaPanelVisible" nzTitle="配置资源文件" (nzOnCancel)="animaPanelCancel()" (nzOnOk)="animaPanelOk()" nzOkText="保存">
<div class="anima-upload-btn">
<span style="margin-right: 10px">上传 ske_json 文件: </span>
<nz-upload [nzShowUploadList]="false"
nzAccept="application/json"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="skeJsonHandleChange($event)">
<button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
</nz-upload>
<i *ngIf="isSkeJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
<span *ngIf="skeJsonData['name']" style="margin-left: 10px"><u> {{skeJsonData['name']}} </u></span>
</div>
<div class="anima-upload-btn">
<span style="margin-right: 10px">上传 tex_json 文件: </span>
<nz-upload [nzShowUploadList]="false"
nzAccept="application/json"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="texJsonHandleChange($event)">
<button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
</nz-upload>
<i *ngIf="isTexJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
<span *ngIf="texJsonData['name']" style="margin-left: 10px"><u> {{texJsonData['name']}} </u></span>
</div>
<div class="anima-upload-btn">
<span style="margin-right: 10px">上传 tex_png 文件: </span>
<nz-upload [nzShowUploadList]="false"
nzAccept = "image/*"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="texPngHandleChange($event)">
<button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
</nz-upload>
<i *ngIf="isTexPngLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
<span *ngIf="texPngData['name']" style="margin-left: 10px"><u> {{texPngData['name']}} </u></span>
</div>
</nz-modal>
......@@ -81,19 +81,21 @@
}
}
h5 {
margin-top: 1rem;
.anima-upload-btn {
padding: 10px;
}
@font-face
{
font-family: 'BRLNSR_1';
src: url("/assets/font/BRLNSR_1.TTF") ;
h5 {
margin-top: 1rem;
}
//
//@font-face
//{
// font-family: 'BRLNSR_1';
// src: url("/assets/font/BRLNSR_1.TTF") ;
//}
......@@ -107,104 +109,3 @@ h5 {
//@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;
//}
......@@ -16,6 +16,7 @@ import {EditorItem, HotZoneImg, HotZoneItem, HotZoneLabel, Label, MySprite, remo
import TWEEN from '@tweenjs/tween.js';
import {getMinScale} from "../../play/Unit";
import {tar} from "compressing";
import {NzMessageService} from "ng-zorro-antd";
@Component({
......@@ -25,18 +26,14 @@ import {tar} from "compressing";
})
export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
@ViewChild('canvas', {static: true}) canvas: ElementRef;
@ViewChild('wrap', {static: true}) wrap: ElementRef;
@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()
......@@ -44,17 +41,28 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
@Input()
isHasText = true;
@Input()
hotZoneFontObj = {
size: 50,
name: 'BRLNSR_1',
color: '#8f3758'
}
isHasAudio = true;
@Input()
isHasAnima = false;
@Input()
customTypeGroupArr; // [{name:string, rect:boolean, pic:boolean, text:boolean, audio:boolean, anima:boolean}, ...]
@Input()
hotZoneFontObj;
// hotZoneFontObj = {
// size: 50,
// name: 'BRLNSR_1',
// color: '#8f3758'
// }
@Input()
defaultItemType = 'text';
@Input()
hotZoneImgSize = 190;
@Output()
save = new EventEmitter();
saveDisabled = true;
canvasWidth = 1280;
......@@ -86,8 +94,31 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
changeSizeFlag = false;
changeTopSizeFlag = false;
changeRightSizeFlag = false;
animaPanelVisible = false;
constructor() {
uploadUrl;
uploadData;
skeJsonData = {};
texJsonData = {};
texPngData = {};
animaName = '';
curDragonBoneIndex;
pasteData = '';
isSkeJsonLoading = false;
isTexJsonLoading = false;
isTexPngLoading = false;
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;
};
}
_bgItem = null;
......@@ -110,6 +141,8 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
ngOnInit() {
this.initListener();
// this.init();
......@@ -150,8 +183,12 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
this.renderArr.push(this.bg);
}
if (!this.bgItem.url) {
this.bgItem.url = 'http://teach.cdn.ireadabc.com/8ebb1858564340ea0936b83e3280ad7d.jpg';
}
const bg = this.bg;
if (this.bgItem.url) {
// if (this.bgItem.url) {
bg.load(this.bgItem.url).then(() => {
const rate1 = this.canvasWidth / bg.width;
......@@ -168,7 +205,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
callBack();
}
});
}
// }
}
......@@ -248,8 +285,10 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
item.x = this.canvasWidth / 2;
item.y = this.canvasHeight / 2;
item.itemType = this.defaultItemType;
item.itemType = this.getDefaultItemType();
item.gIdx = '0';
item['id'] = new Date().getTime().toString();
if (saveData) {
const saveRect = saveData.rect;
......@@ -257,31 +296,40 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
item.scaleY = saveRect.height / item.height;
item.x = saveRect.x + saveRect.width / 2;
item.y = saveRect.y + saveRect.height / 2;
item.gIdx = saveData.gIdx;
item['id'] = saveData.id;
item['skeJsonData'] = saveData.skeJsonData;
item['texJsonData'] = saveData.texJsonData;
item['texPngData'] = saveData.texPngData;
}
item.showLineDash();
const pic = new HotZoneImg(this.ctx);
// const pic = new HotZoneImg(this.ctx);
const pic = new EditorItem(this.ctx);
pic.visible = false;
item['pic'] = pic;
if (saveData && saveData.pic_url) {
this.loadHotZonePic(pic, saveData.pic_url);
this.loadHotZonePic(pic, saveData.pic_url, saveData.imgScale);
}
pic.x = item.x;
pic.y = item.y;
this.renderArr.push(pic);
const textLabel = new HotZoneLabel(this.ctx);
if (this.hotZoneFontObj) {
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) {
if (saveData && saveData.text && this.hotZoneFontObj) {
textLabel.text = saveData.text;
textLabel.refreshSize();
}
......@@ -292,6 +340,22 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
return item;
}
getDefaultItemType() {
if (this.customTypeGroupArr) {
const group = this.customTypeGroupArr[0];
if (group.rect) {
return 'rect';
}
if (group.pic) {
return 'pic';
}
if (group.text) {
return 'text';
}
} else {
return this.defaultItemType;
}
}
getPicItem(img, saveData = null) {
......@@ -327,9 +391,11 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
item.y = saveRect.y + saveRect.height / 2;
} else {
item.showLineDash();
// item.showLineDash();
}
item.showLineDash();
console.log('aaa');
});
return item;
}
......@@ -356,6 +422,48 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
// console.log(' in radioChange e: ', e);
}
customRadioChange(e, item) {
console.log('in customRadioChange', e);
item.gIdx = e;
this.refreshCustomItem(item);
// const curGroup = this.customTypeGroupArr[e];
}
refreshCustomItem(item) {
this.hideHotZoneItem(item);
const group = this.customTypeGroupArr[item.gIdx];
if (!group) {
return;
}
if (group.text) {
item.textLabel.visible = true;
item.itemType = 'text';
}
if (group.rect) {
item.visible = true;
item.itemType = 'rect';
}
if (group.pic && !group.anima) {
item.pic.visible = true;
item.itemType = 'pic';
}
}
hideHotZoneItem(item) {
item.visible = false;
item.pic.visible = false;
item.textLabel.visible = false;
}
refreshItem(item) {
switch (item.itemType) {
case 'rect':
......@@ -375,6 +483,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
init() {
console.log('init');
this.initData();
this.initCtx();
......@@ -387,13 +496,6 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
} else {
this.refreshBackground(() => {
// if (!this.imgItemArr) {
// this.imgItemArr = [];
// } else {
// this.initImgArr();
// }
// console.log('aaaaa');
if (!this.hotZoneItemArr) {
this.hotZoneItemArr = [];
......@@ -427,6 +529,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
this.hotZoneArr = [];
const arr = this.hotZoneItemArr.concat();
console.log('this.hotZoneItemArr: ', this.hotZoneItemArr);
for (let i = 0; i < arr.length; i++) {
const data = JSON.parse(JSON.stringify(arr[i]));
......@@ -449,7 +552,13 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
item.pic_url = data.pic_url;
item.text = data.text;
item.itemType = data.itemType;
if (this.customTypeGroupArr) {
this.refreshCustomItem(item);
} else {
this.refreshItem(item);
}
console.log('item: ', item);
this.hotZoneArr.push(item);
......@@ -463,48 +572,6 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
}
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;
......@@ -582,7 +649,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
this.oldPos = {x: this.mx, y: this.my};
this.saveDisabled = true;
// this.saveDisabled = false;
}
......@@ -852,9 +919,21 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
saveClick() {
const sendData = this.getSendData();
this.save.emit(sendData);
}
getSendData() {
const bgItem = this.bgItem;
if (this.bg) {
bgItem['rect'] = this.bg.getBoundingBox();
const rect = this.bg.getBoundingBox();
bgItem['rect'] = rect;
rect.x = Math.round(rect.x * 100) / 100;
rect.y = Math.round(rect.y * 100) / 100;
rect.width = Math.round(rect.width * 100) / 100;
rect.height = Math.round(rect.height * 100) / 100;
} else {
bgItem['rect'] = {
x: 0,
......@@ -870,19 +949,34 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
for (let i = 0; i < hotZoneArr.length; i++) {
const hotZoneItem = {
id: hotZoneArr[i].id,
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
mapScale: this.mapScale,
skeJsonData: hotZoneArr[i].skeJsonData,
texJsonData: hotZoneArr[i].texJsonData,
texPngData: hotZoneArr[i].texPngData,
gIdx: hotZoneArr[i].gIdx
};
if (this.hotZoneFontObj) {
hotZoneItem['fontSize'] = this.hotZoneFontObj.size;
hotZoneItem['fontName'] = this.hotZoneFontObj.name;
hotZoneItem['ontColor'] = this.hotZoneFontObj.color;
}
if (hotZoneArr[i].itemType == 'pic') {
hotZoneItem['rect'] = hotZoneArr[i].pic.getBoundingBox();
} else {
hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox();
}
// 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;
......@@ -894,9 +988,102 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
console.log('hotZoneItemArr: ', hotZoneItemArr);
this.save.emit({bgItem, hotZoneItemArr});
return {bgItem, hotZoneItemArr};
}
saveText(item) {
item.textLabel.text = item.text;
}
setAnimaBtnClick(index) {
this.curDragonBoneIndex = index;
const {skeJsonData, texJsonData, texPngData} = this.hotZoneArr[index];
this.skeJsonData = skeJsonData || {};
this.texJsonData = texJsonData || {};
this.texPngData = texPngData || {};
this.animaPanelVisible = true;
}
animaPanelCancel() {
this.animaPanelVisible = false;
}
animaPanelOk() {
this.setItemDragonBoneData(this.hotZoneArr[this.curDragonBoneIndex]);
console.log('this.hotZoneArr: ', this.hotZoneArr);
this.animaPanelVisible = false;
}
setItemDragonBoneData(item) {
item['skeJsonData'] = this.skeJsonData;
item['texJsonData'] = this.texJsonData;
item['texPngData'] = this.texPngData;
}
skeJsonHandleChange(e) {
console.log('e: ', e);
switch (e.type) {
case 'start':
this.isSkeJsonLoading = true;
break;
case 'success':
this.skeJsonData['url'] = e.file.response.url;
this.skeJsonData['name'] = e.file.name;
this.nzMessageService.success('上传成功');
this.isSkeJsonLoading = false;
break;
case 'progress':
break;
}
}
texJsonHandleChange(e) {
console.log('e: ', e);
switch (e.type) {
case 'start':
this.isTexJsonLoading = true;
break;
case 'success':
this.texJsonData['url'] = e.file.response.url;
this.texJsonData['name'] = e.file.name;
this.nzMessageService.success('上传成功');
this.isTexJsonLoading = false;
break;
case 'progress':
break;
}
}
texPngHandleChange(e) {
console.log('e: ', e);
switch (e.type) {
case 'start':
this.isTexPngLoading = true;
break;
case 'success':
this.texPngData['url'] = e.file.response.url;
this.texPngData['name'] = e.file.name;
this.nzMessageService.success('上传成功');
this.isTexPngLoading = false;
break;
case 'progress':
break;
}
}
private updatePos() {
this.hotZoneArr.forEach((item) => {
let x, y;
......@@ -962,6 +1149,17 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
private clickedHotZonePic(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);
}
this.curItem = item;
}
}
......@@ -972,15 +1170,98 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
}
}
saveText(item) {
item.textLabel.text = item.text;
}
private loadHotZonePic(pic: HotZoneImg, url) {
private loadHotZonePic(pic: HotZoneImg, url, scale = null) {
const baseLen = this.hotZoneImgSize * this.mapScale;
pic.load(url).then(() => {
const s = getMinScale(pic, baseLen);
pic.setScaleXY(s);
if (!scale) {
scale = getMinScale(pic, baseLen);
}
pic.setScaleXY(scale);
});
}
copyHotZoneData() {
const {bgItem, hotZoneItemArr} = this.getSendData();
// const hotZoneItemArrNew = [];
// const tmpArr = JSON.parse(JSON.stringify(hotZoneItemArr));
// tmpArr.forEach((item) => {
// if (item.gIdx === '0') {
// hotZoneItemArr.push(item);
// }
// })
const jsonData = {
bgItem,
hotZoneItemArr,
isHasRect: this.isHasRect,
isHasPic: this.isHasPic,
isHasText: this.isHasText,
isHasAudio: this.isHasAudio,
isHasAnima: this.isHasAnima,
hotZoneFontObj: this.hotZoneFontObj,
defaultItemType: this.defaultItemType,
hotZoneImgSize: this.hotZoneImgSize,
};
const oInput = document.createElement('input');
oInput.value = JSON.stringify(jsonData);
document.body.appendChild(oInput);
oInput.select(); // 选择对象
document.execCommand("Copy"); // 执行浏览器复制命令
document.body.removeChild(oInput);
this.nzMessageService.success('复制成功');
// alert('复制成功');
}
importData() {
if (!this.pasteData) {
return;
}
try {
const data = JSON.parse(this.pasteData);
console.log('data:', data);
const {
bgItem,
hotZoneItemArr,
isHasRect,
isHasPic,
isHasText,
isHasAudio,
isHasAnima,
hotZoneFontObj,
defaultItemType,
hotZoneImgSize,
} = data;
this.hotZoneItemArr = hotZoneItemArr;
this.isHasRect = isHasRect;
this.isHasPic = isHasPic;
this.isHasText = isHasText;
this.isHasAudio = isHasAudio;
this.isHasAnima = isHasAnima;
this.hotZoneFontObj = hotZoneFontObj;
this.defaultItemType = defaultItemType;
this.hotZoneImgSize = hotZoneImgSize;
this.bgItem = bgItem;
this.pasteData = '';
} catch (e) {
console.log('err: ', e);
this.nzMessageService.error('导入失败');
}
}
}
import { EventEmitter } from '@angular/core';
export class AudioDelegate {
audioObj = new Audio();
public audioPlayTimestamp = new EventEmitter();
public audioDataLoaded = new EventEmitter();
public audioPlayBarPosition = new EventEmitter();
public audioPlayEnd = new EventEmitter();
syncAudioCurrentTimeId: number = null;
private arrayBuffer = null;
formatter = new Intl.NumberFormat('en', {
minimumIntegerDigits: 2,
minimumFractionDigits: 3,
maximumFractionDigits: 3,
useGrouping: false,
});
constructor() {
this.audioObj.onloadeddata = this.onAudioDataLoaded.bind(this);
this.audioObj.onplay = this.onAudioPlay.bind(this);
this.audioObj.onpause = this.onAudioPause.bind(this);
this.audioObj.ontimeupdate = this.onAudioTimeUpdate.bind(this);
this.audioObj.onratechange = this.onAudioRateChange.bind(this);
this.audioObj.onended = this.onAudioEnded.bind(this);
this.audioObj.onerror = () => {
};
}
set playbackRate(val) {
this.audioObj.playbackRate = val;
}
set src(val) {
this.audioObj.src = val;
}
get src() {
return this.audioObj.src;
}
convertTagToTime(tag) {
tag = tag.replace('[', '');
tag = tag.replace(']', '');
const parts = tag.split(':');
let h = 0;
let m = 0;
let s = 0;
if (parts.length === 3) {
h = parseInt(parts[0], 10);
m = parseInt(parts[1], 10);
s = parseInt(parts[2], 10);
} else if (parts.length === 2) {
m = parseInt(parts[0], 10);
s = parseFloat(parts[1] );
}
return h * 60 * 60 + m * 60 + s;
}
convertTimeToTag(time, withBracket = true): string {
if (time === undefined) {
return '';
}
const hh = Math.floor(time / 60 / 60)
.toString()
.padStart(2, '0');
const mm = Math.floor(time / 60)
.toString()
.padStart(2, '0');
const ss = this.formatter.format(time % 60);
return withBracket ? `[${hh}:${mm}:${ss}]` : `${hh}:${mm}:${ss}`;
}
setSource(ab) {
this.arrayBuffer = ab;
const blob = new Blob([ab], { type: 'audio/wav' });
this.audioObj.src = URL.createObjectURL(blob);
}
getDataBuffer() {
return this.arrayBuffer;
}
getBufferClip(start, end) {
return this.arrayBuffer.slice(start * this.arrayBuffer.length, end * this.arrayBuffer.length);
}
load() {
this.audioObj.load();
}
syncAudioCurrentTime() {
this.audioPlayTimestamp.emit({
timeFormat: this.convertTimeToTag(this.audioObj.currentTime, false),
time: this.audioObj.currentTime
});
this.syncAudioCurrentTimeId = requestAnimationFrame(() => {
this.syncAudioCurrentTime();
});
}
onAudioDataLoaded(evt) {
console.log('onAudioDataLoaded', evt);
this.audioDataLoaded.emit(this.arrayBuffer);
}
onAudioPlay() {
this.syncAudioCurrentTimeId = requestAnimationFrame(() => {
this.syncAudioCurrentTime();
});
console.log('onAudioPlay');
}
onAudioPause() {
console.log('onAudioPause');
cancelAnimationFrame(this.syncAudioCurrentTimeId);
}
onAudioEnded() {
console.log('onAudioEnded');
this.audioPlayEnd.emit()
cancelAnimationFrame(this.syncAudioCurrentTimeId);
}
onAudioTimeUpdate() {
// console.log('onAudioTimeUpdate', this.convertTimeToTag(this.audioObj.currentTime));
// this.audioPlayTimestamp.emit(this.convertTimeToTag(this.audioObj.currentTime));
}
onAudioRateChange() {
console.log('onAudioRateChange');
}
get isPlaying() {
return !!(this.audioObj.currentTime > 0
&& !this.audioObj.paused
&& !this.audioObj.ended
&& this.audioObj.readyState > 2);
}
currentTimeFormatted(time?) {
let t = this.audioObj.currentTime;
if (typeof time !== 'undefined') {
t = time;
}
return this.convertTimeToTag(t);
}
get currentTime() {
return this.audioObj.currentTime;
}
set currentTime(val) {
this.audioPlayBarPosition.emit({
time: val
});
this.audioObj.currentTime = val;
}
get duration() {
return this.audioObj.duration;
}
get durationFormatted() {
return this.convertTimeToTag(this.audioObj.duration, false);
}
get currentSrc() {
return this.audioObj.currentSrc;
}
pause() {
this.audioObj.pause();
}
async play() {
return this.audioObj.play();
}
}
import { EventEmitter } from '@angular/core';
export class DragElement {
onMove = new EventEmitter();
canMove = false;
dragEl = null;
relX = 0;
private readonly bindMove: any;
private readonly maxWidth: any;
private readonly bindDown: any;
private readonly bindUp: any;
constructor(el, maxWidth) {
this.dragEl = el;
this.maxWidth = maxWidth;
this.bindMove = this.move.bind(this);
this.bindDown = this.down.bind(this);
this.bindUp = this.up.bind(this);
this.dragEl.addEventListener('mousedown', this.bindDown, false);
document.addEventListener('mouseup', this.bindUp, false);
}
dispose() {
this.dragEl.removeEventListener('mousedown', this.bindDown, false);
}
down(e) {
document.addEventListener('mousemove', this.bindMove, false);
// relX = e.pageX - this.timeLine.offsetWidth || 0;
// const left = parseInt(el.offsetWidth|| 0)
const matrix = new DOMMatrix(this.dragEl.style.transform);
this.relX = e.pageX - matrix.m41 || 0;
this.canMove = true;
}
up(e) {
this.canMove = false;
document.removeEventListener('mousemove', this.bindMove, false);
}
move(e) {
if (!this.canMove) {
return;
}
const matrix = new DOMMatrix(this.dragEl.style.transform);
const w = matrix.m41;
if (w > this.maxWidth) {
this.dragEl.style.transform = `translateX(${this.maxWidth}px)`;
return;
}
if (w < 0 ) {
this.dragEl.style.transform = `translateX(0px)`;
return;
}
// this.dragEl.style.transform = `translateX(${(e.pageX - this.relX)}px)`;
this.onMove.emit({
position: e.pageX - this.relX,
});
}
}
<div class="cmp-lrc-editor" >
<div id="step2" class="step" >
<nz-spin [nzSpinning]="isLoadingAudioBuffer" style="width: 100%;height: 100%;">
<div class="content">
<div class="center" >
<div style="display: flex; line-height: 36px;">
<span style="margin-right: 20px">{{currentAudioTime}}/{{currentAudioDuration}}</span>
&nbsp;
<app-audio-recorder [audioUrl]="LRCData.audio_url"
(audioUploaded)="onAudioUploaded($event)"></app-audio-recorder>
&nbsp;
<button nz-button nzSize="default"
nzType="primary"
nz-tooltip nzTooltipTitle="上剪头播放暂停,下箭头打点,左右剪头微调"
(click)="togglePlayAudio($event)">
<ng-container *ngIf="isPlaying"><i nz-icon nzType="pause" nzTheme="outline"></i>暂停(上箭头)</ng-container>
<ng-container *ngIf="!isPlaying"><i nz-icon nzType="caret-right" nzTheme="outline"></i>播放(上箭头)</ng-container>
</button>
&nbsp;
<button nz-button nzSize="default"
nzType="primary"
nz-tooltip nzTooltipTitle="上剪头播放暂停,下箭头打点,左右剪头微调"
id="enterbtn"
(click)="setTimestampPoint()">打点(下箭头)</button>
&nbsp;
<button nz-button nzSize="default"
nzType="danger"
(click)="saveUserData()"><i nz-icon nzType="cloud-upload" nzTheme="outline"></i>保存</button>
</div>
<div style="display: flex; line-height: 36px;">
<span>播放速度:</span>&nbsp;
<span style="width: 150px;">
<nz-slider [(ngModel)]="playbackRate"
(ngModelChange)="changePlaybackRate($event)"
[nzMax]="2" [nzMin]="0.25" [nzStep]="0.25"></nz-slider>
</span>
<label style="margin-right: 20px;margin-left: 20px">文字大小: <nz-input-number [(ngModel)]="LRCData.fontSize" [nzMin]="1" [nzMax]="100" [nzStep]="1"></nz-input-number></label>
<label style="">行高: <nz-input-number [(ngModel)]="LRCData.lineHeight" [nzMin]="1" [nzMax]="100" [nzStep]="1"></nz-input-number></label>
<input type="file" onclick="this.value=null;" accept=".lrc" style="display: none" #uploadBtn>
<button [disabled]="!LRCData.audio_url" nz-button nzType="link" (click)="uploadLRC()">上传LRC文件</button>
<nz-select [(ngModel)]="lrcFileEncoding" (ngModelChange)="changeLrcFileEncoding($event)">
<nz-option nzValue="UTF-8" nzLabel="UTF-8"></nz-option>
<nz-option nzValue="GB18030" nzLabel="GB18030"></nz-option>
</nz-select>
</div>
<!-- <span>{{currentAudioTime}}/{{currentAudioDuration}}</span>-->
<!-- <nz-radio-group (ngModelChange)="changeMode($event)" [(ngModel)]="LRCData.mode" class="mode">-->
<!-- <label nz-radio [nzValue]="MODE.TEXT">文本模式</label>-->
<!-- <label nz-radio [nzValue]="MODE.IMAGE">图片模式</label>-->
<!-- </nz-radio-group>-->
<!-- <span style="width: 150px;">-->
<!-- <nz-slider [(ngModel)]="playbackRate"-->
<!-- (ngModelChange)="changePlaybackRate($event)"-->
<!-- [nzMax]="2" [nzMin]="0.25" [nzStep]="0.25"></nz-slider>-->
<!-- </span>-->
</div>
<div class="timestamp-container">
<ng-template #insertLineContentTemplate>
<div>
<p>Content</p>
<p>Content</p>
</div>
</ng-template>
<div class="timestamp-line"
(click)="selectTimePoint(i)"
[ngClass]="{selected: selectHighlightTimePointIndex === i}"
*ngFor="let item of timePointData; let i = index">
<div class="time-tag" [ngClass]="{warn: item.warn}">{{item.timeFormatted}}</div>
<div class="add-line" style="margin-right: 4px;">
<button nz-tooltip nzTooltipTitle="向后插入一行" nz-button nzType="danger" nzSize="small" nzShape="circle"
(click)="insertTimePoint(i)">
<i nz-icon nzType="plus" nzTheme="outline"></i>
</button>
</div>
<div class="time-content">
<!-- <app-upload-image-with-preview-->
<!-- [picUrl]="item.data"-->
<!-- (imageUploaded)="onImageUploadSuccess($event)"-->
<!-- ></app-upload-image-with-preview>-->
<input nz-input [(ngModel)]="item.data" />
</div>
<div class="line-break">
<label nz-checkbox nz-tooltip nzTitle="添加换行" [(ngModel)]="item.newLine"></label>
</div>
<div class="time-del">
<button nz-button nzType="primary" nzSize="small" nzShape="circle" (click)="removeTimePoint(i)">
<i nz-icon nzType="delete" nzTheme="outline"></i>
</button>
</div>
</div>
</div>
</div>
</nz-spin>
</div>
<div class="wave-player" [ngStyle]="{visibility: LRCData.audio_url ? 'visible' : 'hidden'}" >
<canvas #waveEl autofocus></canvas>
<div class="time-line" #timeLineEl>
<div class="ctrl-bar">
</div>
<div class="play-bar"></div>
</div>
<div class="point-line">
<div *ngFor="let item of timePointData; let i = index"
[ngStyle]="{transform: item.position, zIndex: selectHighlightTimePointIndex === i ? 1 : 0}">
<div class="arrow-up"
nzTrigger="click"
nzTitle="⇽⇾左右方向键可以微调该时间点"
nzPlacement="bottom"
nz-tooltip
[ngClass]="{selected: selectHighlightTimePointIndex === i}">
<div class="ctrl-bar" (click)="selectTimePoint(i)"></div>
</div>
</div>
</div>
<!-- [nzDisabled]="isScaleTimeLine"-->
<nz-slider nzRange style="flex: 1" [nzTipFormatter]="formatter" [nzStep]="0.01" [nzMax]="timeRangeObj.max" [nzMin]="timeRangeObj.min"
(nzOnAfterChange)="timeRangeAfterChange($event)"
[(ngModel)]="timeRangeSelector"></nz-slider>
<div style="width: 100%;
position: relative;
height: 30px;
display: flex;
flex-direction: row-reverse;">
<!-- *ngIf="!isScaleTimeLine"-->
<!-- *ngIf="isScaleTimeLine"-->
<button [disabled]="!isScaleTimeLine" style="margin-right: 8px;" nz-button nzSize="small"
(click)="restoreTimeLine()"
nzType="primary">
返回</button>
<button style="margin-right: 8px;" nz-button nzSize="small"
(click)="scaleTimeLine()"
nzType="primary">
缩放时间轴</button>
</div>
</div>
</div>
@import '../../style/common_mixin.css';
:host ::ng-deep .cmp-lrc-editor .ant-spin-container {
width: 100%;
height: 100%;
}
.cmp-lrc-editor{
width: 100%;
.step{
width: 100%;
height: 500px;
position: relative;
.content{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
}
.flex1{
flex: 1;
}
.wave-player{
display: inline-block;
position: relative;
width: 100%;
canvas{
width: 100%;
height: 100px;
}
}
.time-line{
height: 100%;
position: absolute;
border: 0;
top: 0;
opacity: 0.5;
width: 0px;
z-index: 1;
}
.line-break{
margin: 0 4px 0 8px;
}
.time-tag.warn{
background: firebrick;
}
.time-tag:after {
content: "\27A4";
}
.timestamp-container{
width: 100%;
flex: 1;
overflow: auto;
}
.timestamp-line.selected{
background: green;
}
.timestamp-line{
display: flex;
margin: 2px 0;
height: 36px;
line-height: 36px;
.time-tag{
flex: 0;
margin-right: 4px;
}
.time-content{
flex: 1;
}
.time-del{
margin-left: 4px;
flex: 0;
}
}
.ctrl-bar{
height: 100%;
width: 0px;
transform: translateX(50%);
position: absolute;
cursor: ew-resize;
user-select: none;
padding: 0 2px;
box-shadow: 0.5px 0 0 blue;
}
.point-line{
position: relative;
height: 5px;
.arrow-up.selected{
border-bottom-color: #faad14;
}
.arrow-up {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
position: absolute;
border-bottom: 5px solid black;
//transform: translateX(-100%);
.ctrl-bar{
bottom: 0;
transform: translateX(-100%);
box-shadow: 0.5px 0 0 blue;
height: 100px;
position: absolute;
}
}
}
}
import {
AfterViewInit,
Component,
ElementRef, EventEmitter,
Input,
Output,
OnChanges,
OnDestroy,
OnInit,
ViewChild
} from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AudioDelegate } from './audio-delegate';
import { fromEvent, Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import { DragElement } from './drag-element';
import { NzMessageService } from 'ng-zorro-antd';
// interface Blob {
// arrayBuffer: () => Promise<ArrayBuffer>;
// }
const MODE = {
TEXT: 1,
IMAGE: 2
};
function arrayBufferToString( buffer, encoding ) {
return new Promise((resolve, reject) => {
const blob = new Blob([buffer], { type: 'text/plain' });
const reader = new FileReader();
reader.onload = (evt) => {
resolve(evt.target.result);
};
reader.readAsText(blob, encoding);
});
}
@Component({
selector: 'app-lrc-editor',
templateUrl: './lrc-editor.component.html',
styleUrls: ['./lrc-editor.component.scss']
})
export class LrcEditorComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@ViewChild('waveEl', {static: true }) canvasElRef: ElementRef;
@ViewChild('timeLineEl', {static: true }) timeLineEl: ElementRef;
@ViewChild('uploadBtn', {static: true }) uploadBtn: ElementRef;
lrcFileEncoding = 'GB18030';
@Output()
editFinished = new EventEmitter();
// MODE = MODE;
@Input()
LRCData: any = {
audio_url: null,
fontSize: 24,
lineHeight: 32,
// mode: MODE.TEXT,
lyrics: []
};
timeRangeModeConfObj = {};
timeRangeObj = {
min: 0,
max: 100
};
isScaleTimeLine = false;
timeRangeSelector = [0, 100];
currentAudioTime = '00:00:00.000';
currentAudioDuration = '00:00:00.000';
selectHighlightTimePointIndex = -1;
currentStep = 0;
// item = {
// audio_url: null,
// mode: MODE.TEXT,
// lyrics: []
// };
audioObj = new AudioDelegate();
isLoadingAudioBuffer = false;
private waveWidth: number;
private waveHeight: number;
private ctx: CanvasRenderingContext2D;
private adContext = new AudioContext();
private timeLineTimer: number;
private timeLine: any;
timePointData = [];
httpErrorTryCount = 0;
httpErrorTryMax = 3;
playbackRate = 1;
// private audioBuffer: AudioBuffer;
private audioArrayBuffer: ArrayBuffer;
timePointDataFull: any;
private currentTimeRangDuration: number;
private timeRangeEditLines: number[];
private audioPlayBarPositionSub: Subscription;
private audioDataLoadedSub: Subscription;
private audioPlayTimestampSub: Subscription;
private audioPlayEndSub: Subscription;
private dragMoveBarSub: Subscription;
private arrowKeyUpSub: Subscription;
private loadAudioSub: Subscription;
constructor(private client: HttpClient, private nzMessageService: NzMessageService) {
}
ngOnInit() {
window['lrc'] = this;
// console.log('this.LRCData 1', this.LRCData);
if (!this.LRCData) {
this.LRCData = {};
}
// console.log('this.LRCData 1', this.LRCData);
this.LRCData = Object.assign( {
audio_url: null,
fontSize: 24,
lineHeight: 32,
// mode: MODE.TEXT,
lyrics: []
}, this.LRCData);
// window.player = this;
if (this.audioPlayBarPositionSub) {
this.audioPlayBarPositionSub.unsubscribe();
}
this.audioPlayBarPositionSub = this.audioObj.audioPlayBarPosition.subscribe((evt) => {
this.calcPlayBarPositionByTime(evt.time);
});
if (this.audioDataLoadedSub) {
this.audioDataLoadedSub.unsubscribe();
}
this.audioDataLoadedSub = this.audioObj.audioDataLoaded.subscribe((ab) => {
console.log('this.audioObj.audioDataLoaded');
this.timeRangeObj.max = this.audioObj.duration;
// this.timeRangeSelector[1] = this.audioObj.duration;
this.timeRangeSelector = [0, this.audioObj.duration];
this.currentAudioDuration = this.audioObj.durationFormatted;
if (this.LRCData && this.LRCData.lyrics) {
this.timePointData = [];
this.LRCData.lyrics.forEach(timeline => {
this.setTimestampPoint(timeline);
});
}
this.drawArrayBuffer(ab);
});
if (this.audioPlayTimestampSub) {
this.audioPlayTimestampSub.unsubscribe();
}
this.audioPlayTimestampSub = this.audioObj.audioPlayTimestamp.subscribe((evt) => {
this.currentAudioTime = evt.timeFormat;
let time = evt.time;
// if (this.isScaleTimeLine) {
// if ( evt.time >= this.timeRangeSelector[0]) {
// time = evt.time - this.timeRangeSelector[0];
// }
// if (evt.time >= this.timeRangeSelector[1]) {
// this.audioObj.pause();
// this.audioObj.currentTime = evt.time; // - 5 * 60 / 1000;
// this.calcPlayBarPositionByTime(this.timeRangeSelector[0]);
// }
// }
this.calcPlayBarPositionByTime(time);
});
if (this.audioPlayEndSub) {
this.audioPlayEndSub.unsubscribe();
}
this.audioPlayEndSub = this.audioObj.audioPlayEnd.subscribe(() => {
this.calcPlayBarPositionByTime(0);
this.selectHighlightTimePointIndex = -1;
});
this.uploadBtn.nativeElement.addEventListener('change', () => {
const lrcFile = this.uploadBtn.nativeElement.files[0];
if (!lrcFile) {
this.nzMessageService.error('请正确选择文件');
return;
}
lrcFile.arrayBuffer().then(ab => {
arrayBufferToString(ab, this.lrcFileEncoding).then((s: string) => {
this.timePointData.length = 0;
s.split('[').forEach(l => {
const p = l.indexOf(']');
const t = l.substr(0, p);
const data = l.substr(p + 1).trim();
if (!isNaN(Number(l[0]))) {
const time = this.audioObj.convertTagToTime(t);
this.timePointData.push({
time,
timeFormatted: t,
data,
position: `translateX(${ this.getXPositionByTime(time)}px)`,
});
}
console.log( t, data);
});
});
});
});
if (this.LRCData.audio_url) {
this.goNextStep2();
}
}
drawArrayBuffer(ab) {
this.adContext.close();
this.adContext = new AudioContext();
this.adContext.decodeAudioData(ab).then(adb => {
this.draw(adb);
this.isLoadingAudioBuffer = false;
(document.activeElement as HTMLButtonElement).blur();
this.canvasElRef.nativeElement.focus();
});
}
ngOnChanges(value) {
if (value.LRCData && !value.LRCData.firstChange) {
// console.log(1111111111)
this.ngOnInit();
// this.onAudioUploaded({url: this.LRCData.audio_url});
}
// console.log('ngOnChanges', JSON.stringify(value)); // this.ngOnInit();
}
ngOnDestroy(): void {
this.audioPlayBarPositionSub.unsubscribe();
this. audioDataLoadedSub.unsubscribe();
this. audioPlayTimestampSub.unsubscribe();
this. audioPlayEndSub.unsubscribe();
this. dragMoveBarSub.unsubscribe();
this. arrowKeyUpSub.unsubscribe();
this. loadAudioSub.unsubscribe();
}
ngAfterViewInit() {
this.timeLine = this.timeLineEl.nativeElement;
// this.timeLineBar = this.timeLineBarEl.nativeElement;
this.waveWidth = this.canvasElRef.nativeElement.offsetWidth;
this.waveHeight = this.canvasElRef.nativeElement.offsetHeight;
this.ctx = this.canvasElRef.nativeElement.getContext('2d');
const dpr = window.devicePixelRatio || 1;
this.canvasElRef.nativeElement.width = this.waveWidth * dpr;
this.canvasElRef.nativeElement.height = this.waveHeight * dpr;
this.ctx.scale(dpr, dpr);
const cb = (e) => {
if (e.code === 'ArrowUp'
|| e.code === 'ArrowDown'
|| e.code === 'ArrowLeft'
|| e.code === 'ArrowRight') {
e.preventDefault();
e.stopPropagation();
// set time point
}
};
document.removeEventListener('keydown', cb );
document.addEventListener('keydown', cb );
if (this.arrowKeyUpSub) {
this.arrowKeyUpSub.unsubscribe();
}
this.arrowKeyUpSub = fromEvent<KeyboardEvent>(document, 'keyup')
.pipe(throttleTime(100) )
.subscribe((e) => {
e.preventDefault();
e.stopPropagation();
if (e.code === 'ArrowUp') {
this.togglePlayAudio();
this.canvasElRef.nativeElement.focus();
}
if (e.code === 'ArrowDown') {
this.setTimestampPoint();
this.canvasElRef.nativeElement.focus();
// set time point
}
if (e.code === 'ArrowLeft') {
if (this.selectHighlightTimePointIndex >= 0) {
this.updateTimePointData(this.selectHighlightTimePointIndex, -0.05);
} else {
this.audioObj.currentTime -= 1;
}
this.canvasElRef.nativeElement.focus();
}
if (e.code === 'ArrowRight') {
if (this.selectHighlightTimePointIndex >= 0) {
this.updateTimePointData(this.selectHighlightTimePointIndex, 0.05);
} else {
this.audioObj.currentTime += 1;
}
this.canvasElRef.nativeElement.focus();
}
});
this.ctx.fillStyle = '#0F0';
if (this.dragMoveBarSub) {
this.dragMoveBarSub.unsubscribe();
}
this.dragMoveBarSub = new DragElement(this.timeLine, this.waveWidth).onMove.subscribe(evt => {
this.selectHighlightTimePointIndex = -1;
const percent = evt.position / this.waveWidth;
let baseDur = this.audioObj.duration;
let startPoint = 0;
let currentTime = percent * this.audioObj.duration;
if (this.isScaleTimeLine) {
baseDur = this.currentTimeRangDuration;
startPoint = this.timeRangeObj.min;
currentTime = this.timeRangeObj.min + percent * baseDur;
}
this.audioObj.currentTime = currentTime;
console.log('dd', baseDur, this.audioObj.currentTime );
this.currentAudioTime = this.audioObj.currentTimeFormatted();
this.calcPlayBarPositionByTime(currentTime);
});
}
get isPlaying() {
return this.audioObj.isPlaying;
}
updateTimePointData(index, time) {
const p = this.timePointData[index];
if (p) {
const newTime = p.time + time;
let posTime = newTime;
if (this.isScaleTimeLine) {
posTime = posTime - this.timeRangeObj.min;
}
this.audioObj.currentTime = newTime;
p.time = newTime;
p.position = this.calcPlayBarPositionByTime(posTime, true);
p.timeFormatted = this.audioObj.currentTimeFormatted(newTime);
if (p.time > this.audioObj.duration) {
p.warn = true;
}
}
}
getXPositionByTime(time) {
let base = this.audioObj.duration;
if (this.currentTimeRangDuration) {
time = time - this.timeRangeObj.min;
if (time < 0) {
time = 0;
}
base = this.currentTimeRangDuration;
}
return ((time / base) * this.waveWidth) ;
}
calcPlayBarPositionByTime(time, returnVal = false) {
const x = this.getXPositionByTime(time)
console.log(x);
const pos = `translateX(${x}px)`;
if (returnVal) {
return pos;
}
this.timeLine.style.transform = pos;
}
togglePlayAudio(evt?) {
if (evt) {
evt.target.blur();
}
console.log('togglePlayAudio', evt);
if (this.isPlaying) {
this.audioObj.pause();
clearInterval(this.timeLineTimer);
return;
}
if (this.isScaleTimeLine
&& (this.audioObj.currentTime < this.timeRangeSelector[0]
|| this.audioObj.currentTime >= this.timeRangeSelector[1] )) {
this.audioObj.currentTime = this.timeRangeSelector[0];
}
if (this.selectHighlightTimePointIndex >= 0) {
const p = this.timePointData[this.selectHighlightTimePointIndex];
this.audioObj.currentTime = p.time;
}
this.audioObj.play().then(() => {
// this.timeLineTimer = window.setInterval(() => {
// if (canSetPoint) {
// console.log(audio.currentTime,audioImages[audio.currentTime] )
// audioImages.push([audio.currentTime, pointAudio]);
// canSetPoint = false;
// }
// this.timeLine.style.width = `${Math.ceil((this.audioObj.currentTime / this.audioObj.duration) * 100) }%`;
// }, 32);
});
}
setTimestampPoint(timelineData = null, select = false) {
if (!this.audioObj.currentSrc) {
return;
}
// console.log('setTimestampPoint');
let pointTime = this.audioObj.currentTime;
let data = '';
let newLine = false;
if (timelineData) {
pointTime = timelineData.time;
data = timelineData.data;
newLine = timelineData.newLine;
}
let warn = false;
if (pointTime > this.audioObj.duration || pointTime < 0) {
warn = true;
}
const tmpAllTime = this.timePointData.map(d => {
return {...d};
});
tmpAllTime.forEach(p => {
p.timeFormatted = this.audioObj.convertTimeToTag(p.time, true);
const delta = Math.abs(p.time - pointTime ) * 1000;
if (delta < 150) {
p.warn = true;
}
});
// if (this.isScaleTimeLine) {
// pointTime = pointTime - this.timeRangeObj.min;
// }
tmpAllTime.push({
time: pointTime, // this.audioObj.currentTime,
timeFormatted: this.audioObj.currentTimeFormatted(pointTime),
data,
position: `translateX(${ this.getXPositionByTime(pointTime)}px)`,
warn,
newLine
});
tmpAllTime.sort((a, b) => {
return a.time - b.time;
});
this.timePointData.length = 0;
tmpAllTime.forEach(item => {
this.timePointData.push(item);
});
if (select) {
this.selectHighlightTimePointIndex = this.timePointData.length - 1;
}
}
insertTimePoint(index) {
const st = this.timePointData[index].time;
let et = st + 5;
const next = this.timePointData[index + 1];
if (next) {
et = next.time;
}
const time = st + (et - st) / 2;
// if (this.isScaleTimeLine) {
// time = time + this.timeRangeObj.min;
// }
this.audioObj.currentTime = time;
this.setTimestampPoint({
time,
data: ''
});
// this.timePointData.splice(index + 1, 0, {
// time,
// timeFormatted: this.audioObj.currentTimeFormatted(time),
// data: '',
// position: `translateX(${ this.getXPositionByTime(time)}px)`,
// } );
this.selectHighlightTimePointIndex = index + 1;
}
removeTimePoint(index) {
this.timePointData.splice(index, 1);
this.selectHighlightTimePointIndex = -1;
}
selectTimePoint(index) {
this.selectHighlightTimePointIndex = index;
this.audioObj.currentTime = this.timePointData[index].time;
}
onAudioUploaded(evt) {
console.log(evt);
this.LRCData.audio_url = evt.url;
this.LRCData.lyrics.length = 0;
this.timePointData.length = 0;
this.goNextStep2();
}
loadingAudioBuffer() {
this.isLoadingAudioBuffer = true;
if (this.loadAudioSub) {
this.loadAudioSub.unsubscribe();
}
this.loadAudioSub = this.client.get(this.LRCData.audio_url, {responseType: 'arraybuffer'})
.subscribe( (ab) => {
// const blob = new Blob([ab], { type: 'audio/wav' });
// this.audioObj.src = URL.createObjectURL(blob);
this.audioArrayBuffer = ab.slice(0);
this.audioObj.setSource(ab);
this.audioObj.load();
}, ( error ) => {
console.log(error);
this.isLoadingAudioBuffer = false;
if (this.httpErrorTryCount >= this.httpErrorTryMax) {
this.nzMessageService.error('请检查网络,刷新重试');
return;
}
this.httpErrorTryCount += 1;
this.goNextStep2();
});
}
goNextStep2() {
this.currentStep = 1;
this.loadingAudioBuffer();
}
filterAudioSamples( adb: AudioBuffer, samples) {
const data = [];
const length = adb.length;
const blockSize = Math.floor(length / samples);
const rawData = adb.getChannelData(0);
for (let i = 0; i < samples; i++) {
const blockStart = i * blockSize;
let sum = 0;
for (let j = 0; j < blockSize; j++) {
sum = sum + Math.abs(rawData[blockStart + j]);
}
data.push(sum / blockSize);
}
return data;
}
normalizeData(data) {
const base = Math.pow(Math.max(...data), -1);
return data.map(n => n * base);
}
draw(adb) {
this.ctx.clearRect(0, 0 , this.waveWidth, this.waveHeight);
const waveData = this.filterAudioSamples(adb, this.waveWidth);
const normalizeDataForAllAudioBuffer = this.normalizeData(waveData);
const width = this.waveWidth / normalizeDataForAllAudioBuffer.length;
normalizeDataForAllAudioBuffer.forEach((d, i) => {
const x = width * i;
const height = d * this.waveHeight / 2;
this.ctx.fillRect(x, this.waveHeight / 2 - height, 1, height * 2);
});
}
saveUserData() {
const result = [];
this.timePointData.forEach(p => {
const t = {...p};
delete t.position;
delete t.warn;
delete t.timeFormatted;
t.data = t.data;
result.push(t);
});
this.LRCData.lyrics = result;
this.editFinished.emit(this.LRCData);
// console.log(this.timePointData);
}
changePlaybackRate(val) {
this.audioObj.playbackRate = val;
}
onImageUploadSuccess(evt) {
}
uploadLRC() {
this.uploadBtn.nativeElement.click();
}
changeLrcFileEncoding(enc) {
this.lrcFileEncoding = enc;
}
formatter = (value: number): string => {
return this.audioObj.convertTimeToTag(value, false);
}
timeRangeAfterChange(value) {
this.timeRangeSelector = value;
}
scaleTimeLine() {
this.audioObj.pause();
this.selectHighlightTimePointIndex = -1;
this.isScaleTimeLine = true;
if (!this.timePointDataFull) {
this.timePointDataFull = this.timePointData.map(line => {
return {...line};
});
}
// this.timeRangeModeConfObj.startTime = this.timeRangeSelector[0];
// this.timeRangeModeConfObj.endTime = this.timeRangeSelector[0];
const start = this.timeRangeSelector[0];
const end = this.timeRangeSelector[1];
const sp = start / this.audioObj.duration;
const ep = end / this.audioObj.duration;
const sl = sp * this.audioArrayBuffer.byteLength;
const el = ep * this.audioArrayBuffer.byteLength;
this.currentTimeRangDuration = end - start;
this.timeRangeObj.max = end;
this.timeRangeObj.min = start;
this.calcPlayBarPositionByTime(start);
// this.timeRangeSelector[0] = start;
// this.timeRangeSelector[1] = end;
this.timeRangeSelector = [start, end];
this.timeRangeEditLines = [];
this.timePointData = this.timePointData.filter( (line, idx) => {
if (line.time > start && line.time < end) {
this.timeRangeEditLines.push(idx);
return true;
}
return false;
}).map( line => {
line.position = this.calcPlayBarPositionByTime( line.time , true);
return line;
});
const ab = this.audioArrayBuffer.slice(sl, el);
// const ab = this.audioObj.getBufferClip(s, e);
this.drawArrayBuffer(ab);
}
restoreTimeLine() {
this.audioObj.pause();
this.timeRangeObj.max = this.audioObj.duration;
this.timeRangeObj.min = 0;
this.timeRangeSelector = [0, this.audioObj.duration];
// this.timeRangeSelector[1] = this.audioObj.duration;
this.selectHighlightTimePointIndex = -1;
this.isScaleTimeLine = false;
this.currentTimeRangDuration = this.audioObj.duration;
if (this.timeRangeEditLines.length) {
const head = this.timePointDataFull.slice(0, Math.min(...this.timeRangeEditLines) );
const tail = this.timePointDataFull.slice( Math.max(...this.timeRangeEditLines) + 1 );
// this.timePointData = [...head, ...this.timePointData, ...tail];
this.timePointData = [...head, ...this.timePointData, ...tail].map(line => {
return {...line};
}).sort((a, b) => {
return a.time - b.time;
});
} else {
this.timePointData = [...this.timePointDataFull, ...this.timePointData];
}
this.timePointData = this.timePointData.map(line => {
line.position = this.calcPlayBarPositionByTime(line.time, true);
return {...line};
});
this.timePointDataFull = null;
const ab = this.audioArrayBuffer.slice(0);
// const ab = this.audioObj.getBufferClip(s, e);
this.drawArrayBuffer(ab);
}
}
......@@ -6,30 +6,3 @@
height: 100%;
}
.radioPaire {
float: left;
margin: 3px;
border-style: dashed;
border-color: #000;
border-width: 1px;
}
.border {
border-radius: 20px;
border-style: dashed;
padding: 20px;
margin: 20px;
/*width: 500px; */
/*//border-radius: 20px;*/
/*//border-width: 2px;*/
/*//border-color: #000000;*/
}
.border-lite {
border: 2px dashed #ddd;
border-radius: 0.5rem;
padding: 10px;
margin: 10px;
}
<div class="model-content">
<nz-divider nzText="配置歌词"></nz-divider>
<app-lrc-editor [LRCData]="item.lrcData" (editFinished)="getLRC($event)">
</app-lrc-editor>
<nz-divider nzText="配置伴奏音频"></nz-divider>
<div style="padding: 0 20px;">
<app-audio-recorder
style="margin-top: 5px"
[audioUrl]="item.accompany_audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'accompany_audio_url', item)"
></app-audio-recorder>
</div>
<nz-divider nzText="显示配置"></nz-divider>
<div style="padding: 10px;">
<div *ngIf="item.lrcData" style="padding: 20px">
<app-custom-hot-zone
[bgItem]="bgItem"
[hotZoneItemArr]="hotZoneItemArr"
[customTypeGroupArr]="customTypeGroupArr"
(save)="saveHotZone($event)"
>
<div style="width: 300px;" align='center'>
<span>图1: </span>
<app-upload-image-with-preview
[picUrl]="item.pic_url"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')">
</app-upload-image-with-preview>
</app-custom-hot-zone>
<div style="margin-top: 30px;">
<div style="display: flex; flex-wrap: wrap">
<div *ngFor="let it of picArr; let i = index" style="padding: 10px">
<div style="width: 230px; height: 240px; border: 1px solid #ccc; border-radius: 10px; padding: 5px; display: flex; align-items: center; flex-direction: column">
<div style="width: 230px; height: 40px; position: absolute;" align="right">
<button style="margin-top: 5px; margin-right: 5px" nz-button nzSize="small" nzType="danger" (click)="deletePic(i)">
<i nz-icon nzType="close" nzTheme="outline"></i>
</button>
</div>
<div style="width: 300px; margin-top: 5px;" align='center'>
<span>图2: </span>
<h3 align="center" style="margin-top: 5px">item-{{i + 1}}</h3>
<div style="width: 90%">
<app-upload-image-with-preview
[picUrl]="item.pic_url_2"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url_2')">
</app-upload-image-with-preview>
[picUrl]="it.pic_url"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url', it)"
></app-upload-image-with-preview>
</div>
<div style="width: 300px; margin-top: 15px;">
<span>文本: </span>
<input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
</div>
<input type="text" nz-input [(ngModel)]="it.text" (blur)="save()" style="width: 80%;margin-top: 5px">
<div style="margin-top: 5px">
<span>音频: </span>
<app-audio-recorder
[audioUrl]="item.audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
style="margin-top: 5px"
[audioUrl]="it.audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url', it)"
></app-audio-recorder>
</div>
</div>
<div style="padding: 10px">
<button style="width: 230px; height: 240px; padding: 5px;" nz-button nzType="dashed" (click)="addPic()">
<i nz-icon nzType="plus-circle" nzTheme="outline"></i>添加底部item
</button>
</div>
</div>
</div>
</div>
<!--<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')"-->
<!--&gt;</app-upload-image-with-preview>-->
<!--<app-audio-recorder-->
<!--[audioUrl]="item.audio_url"-->
<!--(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"-->
<!--&gt;</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>
import { Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef } from '@angular/core';
import { JsonPipe } from '@angular/common';
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import {NzMessageService} from "ng-zorro-antd";
@Component({
......@@ -10,38 +11,58 @@ import { JsonPipe } from '@angular/common';
export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用
saveKey = "test_001";
saveKey = 'cocos_karaoke';
// 储存对象
item;
constructor(private appRef: ApplicationRef, private changeDetectorRef: ChangeDetectorRef) {
bgItem = {};
hotZoneItemArr = [];
animaArr = [];
picArr;
customTypeGroupArr = [
{
name: '基础资源',
rect: true,
anima: true,
audio: true,
pic: true,
text: true,
},
{
name: '图片',
pic: true
},
{
name: '歌词区域',
rect: true,
},
{
name: '播放按钮',
rect: true,
},
{
name: '模式切换按钮',
rect: true,
}
]
createShell() {
this.item.wordList.push({
word: '',
audio: '',
backWord: '',
backWordAudio: '',
});
this.save();
}
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef, private message: NzMessageService) {
removeShell(idx) {
this.item.wordList.splice(idx, 1);
this.save();
}
ngOnInit() {
this.item = {};
// 获取存储的数据
(<any>window).courseware.getData((data) => {
(<any> window).courseware.getData((data) => {
// console.log(1, data);
if (data) {
this.item = data;
// console.log(2, data);
}
this.init();
......@@ -50,16 +71,26 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.refresh();
}, this.saveKey);
}
ngOnChanges() {
}
ngOnDestroy() {
}
init() {
this.bgItem = this.item.bgItem || {};
this.hotZoneItemArr = this.item.hotZoneItemArr || [];
this.animaArr = this.item.animaArr || [];
this.picArr = this.item.picArr || [];
this.refreshAnimaArr();
}
......@@ -67,9 +98,9 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存图片数据
* @param e
*/
onImageUploadSuccess(e, key) {
onImageUploadSuccess(e, key, it) {
this.item[key] = e.url;
it[key] = e.url;
this.save();
}
......@@ -77,29 +108,83 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存音频数据
* @param e
*/
onAudioUploadSuccess(e, key) {
this.item[key] = e.url;
onAudioUploadSuccess(e, key, it) {
it[key] = e.url;
this.save();
}
onWordAudioUploadSuccess(e, idx) {
this.item.wordList[idx].audio = e.url;
saveHotZone(e) {
console.log('e: ', e);
const {bgItem, hotZoneItemArr} = e;
this.bgItem = bgItem;
this.hotZoneItemArr = hotZoneItemArr;
this.item.bgItem = bgItem;
this.item.hotZoneItemArr = hotZoneItemArr;
this.refreshAnimaArr();
this.save();
this.message.create(
'success',
'保存成功'
);
}
onBackWordAudioUploadSuccess(e, idx) {
this.item.wordList[idx].backWordAudio = e.url;
refreshAnimaArr() {
const tmpArr = [];
this.hotZoneItemArr.forEach((item) => {
let data = this.animaArr.find((anima) => {
return anima.id === item.id;
});
if (!data) {
data = this.createAnimaData(item);
}
tmpArr.push(data);
});
this.animaArr = tmpArr;
}
createAnimaData(hotZoneItem) {
const animaData = {};
animaData['id'] = hotZoneItem.id;
return animaData;
}
addPic() {
this.picArr.push({
pic_url: '',
audio_url: '',
text: ''
});
this.item.picArr = this.picArr;
this.save();
}
deletePic(i) {
this.picArr.splice(i, 1);
this.item.picArr = this.picArr;
this.save();
}
/**
* 储存数据
*/
save() {
(<any>window).courseware.setData(this.item, null, this.saveKey);
console.log('this.item: ', this.item);
console.log('this.item json: ', JSON.stringify(this.item) );
(<any> window).courseware.setData(this.item, null, this.saveKey);
this.refresh();
console.log('this.item = ' + JSON.stringify(this.item));
}
/**
......@@ -110,5 +195,10 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.appRef.tick();
}, 1);
}
getLRC(evt) {
this.item.lrcData = evt;
this.save();
}
}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
This source diff could not be displayed because it is too large. You can view the blob instead.
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