Commit af06ba7b authored by starry_sky's avatar starry_sky

master

parent 3695f7ce
...@@ -4,21 +4,10 @@ angularjs技术框架下的H5互动模板框架脚手架,基于NG-ZORRO实现 ...@@ -4,21 +4,10 @@ angularjs技术框架下的H5互动模板框架脚手架,基于NG-ZORRO实现
# 使用简介 # 使用简介
## 前期准备
* git下载 https://git-scm.com/downloads
* nodejs下载 https://nodejs.org/zh-cn/download/
* 谷歌浏览器下载 https://www.google.cn/chrome/
都下载最新版就行,然后默认安装就可以
## 生成项目 ## 生成项目
* 登录绘玩云课件开发平台:http://staging-teach.ireadabc.com/ * 登录绘玩云课件开发平台:http://staging-teach.ireadabc.com/
* 点击“登录账号,查看我的课件” * 点击“模板管理” 菜单,然后点击“新建模板”, 填写必要的信息,在“技术选型”一项上选择“Angular”
* 输入测试的用户名/密码:developers/12345678
* 在右上角“个人中心”的下拉菜单里,点击“我的模板” 菜单,然后点击“新建模板”, 填写必要的信息,在“技术选型”一项上选择“Angular”
* 点击“确定”后,列表页就会出现一个新生成的模板项目 * 点击“确定”后,列表页就会出现一个新生成的模板项目
* 在项目的卡片下找到“开发”按钮,则会弹出相对应的git地址 * 在项目的卡片下找到“开发”按钮,则会弹出相对应的git地址
...@@ -28,8 +17,7 @@ angularjs技术框架下的H5互动模板框架脚手架,基于NG-ZORRO实现 ...@@ -28,8 +17,7 @@ angularjs技术框架下的H5互动模板框架脚手架,基于NG-ZORRO实现
// xxx 是上面项目对应的Git地址 // xxx 是上面项目对应的Git地址
git clone xxx git clone xxx
cd 项目名称/ cd 项目名称/
npm install -g yarn npm install
yarn install
npm start npm start
//启动成功后打开浏览器,输入:http://localhost:4200 则可以看到这个项目的初始化效果了 //启动成功后打开浏览器,输入:http://localhost:4200 则可以看到这个项目的初始化效果了
...@@ -164,152 +152,6 @@ npm start ...@@ -164,152 +152,6 @@ npm start
}); });
``` ```
### 互动课件
通过H5的交互方式,我们可以很轻松的制作在线的互动课件,使老师端与学生端实现联动,使在线课堂更具有交互性。
* onEvent 定义事件:用于自定义同步事件,配合多端互动使用,与sendEvent方法配合使用,用于监听多端发送的同步事件
参数
> evtName : 自定义事件的名称
> callback : 回调函数,回调函数里会得到传过来的数据
使用例子:
```
const cw = (<any> window).courseware;
//订阅一个事件
cw.onEvent('testEvent', (data,next) => {
console.log(data);
//处理事件的同步逻辑
//在逻辑处理完之后一定要执行next方法,用于解锁事件队列
next();
});
```
* sendEvent 发送事件:用于自定义同步事件,配合多端互动使用,与onEvent方法配合使用,用于对端发送同步事件
参数
> evtName : 自定义事件的名称
> data : 传递的参数
使用例子:
```
const cw = (<any> window).courseware;
//发送事件
//这样,所有端(教师、学生) 都会触发 testEvent 方法
cw.sendEvent('testEvent', 'Hello world');
```
* storeAspect 存储切面数据: 课件的切面状态数据;互动课件需要每一个开发者设计切面状态,用于学生进入已经进行的课件,恢复课件同步。例如老师讲图片轮播到第四页,这个时候学生进入教师,要恢复到第四页,才能跟老师同步上课。
参数
> data : 保存的数据,一个JSON对象
使用例子:
```
const cw = (<any> window).courseware;
//存储切面数据,用于学生进入教室,同步课堂信息等
cw.storeAspect({page: 5});
```
* getAspect 获取切面数据: 课件的切面状态数据;互动课件需要每一个开发者设计切面状态,用于学生进入已经进行的课件,恢复课件同步。例如老师讲图片轮播到第四页,这个时候学生进入教师,要恢复到第四页,才能跟老师同步上课。
参数
> callback : 获取切面数据的回调函数
使用例子:
```
const cw = (<any> window).courseware;
//存储切面数据,用于学生进入教室,同步课堂信息等
cw.getAspect(function(aspect){
//恢复切面状态的逻辑
});
```
* 补充项
1) 有时候我们在实现模板效果的时候,需要一些基本的信息,比如教室信息、当前用户信息等等,例如,老师会显示某个按钮,而学生不显示,我们可以用如下方式获取教室信息;后续大部分静态信息都会完善到这个对象里
```
//获取教室信息,值得注意的是获取这个信息一定要再 getData方法之后,或者确保页面完成之后
getData((data, aspect) => {
let airClassInfo = window["air"].airClassInfo;
console.log(airClassInfo);
});
```
2) 互动课件的 getData 方法的回调函数里多返回了一个切面参数,类似如下:
```
const getData = (<any> window).courseware.getData;
getData((data, aspect) => {
//data 就是云端获取的数据,也就是setData方法存到云端的那个obj
//aspect 切面参数,用于恢复页面的状态
//模板开发者需要根据数据来渲染或控制展示页面的呈现,达到模板的互动目的
});
```
3) 一些必要的内置事件,通过监听这些事件,我们可以实现一些相关的功能
* userchange 事件,用来监听教室内部的人员变动情况,例如某人进入,某人退出都会触发此事件
示例:
```
const cw = (<any> window).courseware;
//订阅一个事件
cw.onEvent('userchange', (data,next) => {
//data的结构如下
/*{
* id: '变动用户的ID',
* connected: true/false, true: 连接的,false:d断开的
* status: 'connect/reconnect', connect:新建连接,reconnect:重新连接;这个字段在断开连接的事件里是缺失的
* all_user: [] 这个是现有的用户列表
*}
*/
console.log(data);
//处理事件的同步逻辑
//在逻辑处理完之后一定要执行next方法,用于解锁事件队列
next();
});
```
4) 测试互动课件
我们在本地开发中,无法模拟线上老师和学生互动的交互场景,所以提供了一套开发者测试的账号,如下:
测试地址:http://staging-ac.ireadabc.com/
老师用户名/密码:devtea / 1
学生用户名/密码: 学生1(13877777711 / 1) 学生2(13877777712 / 1)
测试方法:
1、首先开发者发布完模板之后,在制作课件的菜单下用这个模板制作一个课件
2、在本地打开两个浏览器窗口 一个登陆老师用户,另一个登陆学生用户
3、老师用户进去之后,就会有很多课件,这个时候双击自己制作的课件,学生的窗口就会同步的打开课件了
4、测试自己的交互事件,看看老师端和学生端是否是自己想要的展示效果
### 补充方法 ### 补充方法
......
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"ng-template-generator": { "ng-one": {
"projectType": "application", "projectType": "application",
"schematics": {}, "schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"prefix": "app", "prefix": "app",
...@@ -13,26 +17,39 @@ ...@@ -13,26 +17,39 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"outputPath": "dist/ng-template-generator", "outputPath": "dist",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"aot": true, "aot": false,
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets", "src/assets",
{ "glob": "**/*", "input": "src/assets/libs/service-worker/", "output": "/" },
{ {
"glob": "**/*", "glob": "**/*",
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/" "output": "/assets/"
},
{
"glob": "**/*",
"input": "./dist/game/",
"output": "/assets/cocos/"
} }
], ],
"styles": [ "styles": [
"src/styles.scss",
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", "./node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
"src/styles.css" "./node_modules/font-awesome/css/font-awesome.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"./node_modules/animate.css/animate.min.css"
], ],
"scripts": [] "scripts": [
"src/assets/libs/audio-recorder/lame.min.js",
"src/assets/libs/audio-recorder/worker.js",
"src/assets/libs/audio-recorder/recorder.js"
]
}, },
"configurations": { "configurations": {
"production": { "production": {
...@@ -47,39 +64,28 @@ ...@@ -47,39 +64,28 @@
"sourceMap": false, "sourceMap": false,
"extractCss": true, "extractCss": true,
"namedChunks": false, "namedChunks": false,
"aot": true,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
} }
} }
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "ng-template-generator:build" "browserTarget": "ng-one:build"
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "ng-template-generator:build:production" "browserTarget": "ng-one:build:production"
} }
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "ng-template-generator:build" "browserTarget": "ng-one:build"
} }
}, },
"test": { "test": {
...@@ -94,8 +100,7 @@ ...@@ -94,8 +100,7 @@
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", "src/styles.scss"
"src/styles.css"
], ],
"scripts": [] "scripts": []
} }
...@@ -117,16 +122,15 @@ ...@@ -117,16 +122,15 @@
"builder": "@angular-devkit/build-angular:protractor", "builder": "@angular-devkit/build-angular:protractor",
"options": { "options": {
"protractorConfig": "e2e/protractor.conf.js", "protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ng-template-generator:serve" "devServerTarget": "ng-one:serve"
}, },
"configurations": { "configurations": {
"production": { "production": {
"devServerTarget": "ng-template-generator:serve:production" "devServerTarget": "ng-one:serve:production"
} }
} }
} }
} }
} }},
}, "defaultProject": "ng-one"
"defaultProject": "ng-template-generator" }
} \ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* 运行 npm run publish T_01,T_02,T_03,T_04 命令来分别打包 T_01,T_02,T_03,T_04 这四个模板,注意逗号要用英文的 * 运行 npm run publish T_01,T_02,T_03,T_04 命令来分别打包 T_01,T_02,T_03,T_04 这四个模板,注意逗号要用英文的
* 运行 npm run publish all 命令来打包所有模板 * 运行 npm run publish all 命令来打包所有模板
*/ */
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
...@@ -12,9 +12,9 @@ const os = require('os'); ...@@ -12,9 +12,9 @@ const os = require('os');
const compressing = require("compressing"); const compressing = require("compressing");
//Linux系统上'Linux' //Linux系统上'Linux'
//macOS 系统上'Darwin' //macOS 系统上'Darwin'
//Windows系统上'Windows_NT' //Windows系统上'Windows_NT'
let sysType = os.type(); let sysType = os.type();
Date.prototype.Format = function(fmt) { Date.prototype.Format = function(fmt) {
var o = { var o = {
...@@ -44,9 +44,9 @@ const runSpawn = async function (){ ...@@ -44,9 +44,9 @@ const runSpawn = async function (){
await new Promise(function(resolve,reject){ await new Promise(function(resolve,reject){
let pkg = require("../package.json"); let pkg = require("../package.json");
let ls; let ls;
if(sysType==="Windows_NT"){ if(sysType==="Windows_NT"){
//ng build --prod --build--optimizer --base-href /ng-one/ //ng build --prod --build--optimizer --base-href /ng-one/
ls = spawn("cmd.exe", ['/c', '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/'] );
...@@ -57,7 +57,7 @@ const runSpawn = async function (){ ...@@ -57,7 +57,7 @@ const runSpawn = async function (){
ls.stdout.on('data', (data) => { ls.stdout.on('data', (data) => {
console.log(` ${data}`); console.log(` ${data}`);
}); });
ls.stderr.on('data', (data) => { ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`); console.log(`stderr: ${data}`);
reject(); reject();
...@@ -66,13 +66,13 @@ const runSpawn = async function (){ ...@@ -66,13 +66,13 @@ const runSpawn = async function (){
ls.on('close', (code) => { ls.on('close', (code) => {
console.log(`child process exited with code ${code}`); console.log(`child process exited with code ${code}`);
//要压缩的目录 //要压缩的目录
let zippath = path.resolve(__dirname,"../dist", pkg.name); let zippath = path.resolve(__dirname,"../dist");
//压缩包的存放目录 //压缩包的存放目录
let date = new Date(); let date = new Date();
let zipname = pkg.name+"_"+date.Format("yyyyMMdd hh-mm-ss"); let zipname = pkg.name+"_"+date.Format("yyyyMMdd hh-mm-ss");
let zipdir = path.resolve(__dirname,"../publish/"+zipname+".zip"); let zipdir = path.resolve(__dirname,"../publish/"+zipname+".zip");
clean(zipdir); //删除原有的包 clean(zipdir); //删除原有的包
const tarStream = new compressing.zip.Stream(); const tarStream = new compressing.zip.Stream();
fs.readdir(zippath,function(err,files){ fs.readdir(zippath,function(err,files){
if(err){ if(err){
...@@ -80,20 +80,21 @@ const runSpawn = async function (){ ...@@ -80,20 +80,21 @@ const runSpawn = async function (){
console.log(err); console.log(err);
reject(); reject();
} }
console.log(files);
for(let i=0;i<files.length;i++){ for(let i=0;i<files.length;i++){
tarStream.addEntry(zippath+"/"+files[i]); tarStream.addEntry(zippath+"/"+files[i]);
} }
let writeStream = fs.createWriteStream(zipdir); let writeStream = fs.createWriteStream(zipdir);
tarStream.pipe(writeStream); tarStream.pipe(writeStream);
writeStream.on('close', () => { writeStream.on('close', () => {
console.log(`模板 ${pkg.name} 打包已完成!`); console.log(`模板 ${pkg.name} 打包已完成!`);
resolve(); resolve();
}) })
}); });
}); });
}); });
} }
// let projects = ""; // let projects = "";
...@@ -101,7 +102,7 @@ const runSpawn = async function (){ ...@@ -101,7 +102,7 @@ const runSpawn = async function (){
// console.log("缺少参数"); // console.log("缺少参数");
// return; // return;
// } // }
// projects = process.argv[2]; // projects = process.argv[2];
let exec = async function(){ let exec = async function(){
//压缩模板 //压缩模板
...@@ -110,5 +111,6 @@ let exec = async function(){ ...@@ -110,5 +111,6 @@ let exec = async function(){
exec(); exec();
\ No newline at end of file
This diff is collapsed.
...@@ -4,54 +4,73 @@ ...@@ -4,54 +4,73 @@
"scripts": { "scripts": {
"start": "ng serve", "start": "ng serve",
"build": "ng build --build--optimizer --aot --base-href /JGT/v3/", "build": "ng build --build--optimizer --aot --base-href /JGT/v3/",
"publish": "node ./bin/publish.js", "publish": "node ./bin/publish.js"
"ng": "ng",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~9.0.2", "@angular/animations": "^7.2.10",
"@angular/common": "~9.0.2", "@angular/cdk": "^7.2.2",
"@angular/compiler": "~9.0.2", "@angular/common": "^7.2.10",
"@angular/core": "~9.0.2", "@angular/compiler": "^7.2.10",
"@angular/forms": "~9.0.2", "@angular/core": "^7.2.10",
"@angular/platform-browser": "~9.0.2", "@angular/flex-layout": "^7.0.0-beta.24",
"@angular/platform-browser-dynamic": "~9.0.2", "@angular/forms": "^7.2.10",
"@angular/router": "~9.0.2", "@angular/http": "^7.2.10",
"@fortawesome/angular-fontawesome": "^0.6.0", "@angular/material": "^7.2.2",
"@fortawesome/fontawesome-svg-core": "^1.2.27", "@angular/platform-browser": "^7.2.10",
"@fortawesome/free-regular-svg-icons": "^5.12.1", "@angular/platform-browser-dynamic": "^7.2.10",
"@fortawesome/free-solid-svg-icons": "^5.12.1", "@angular/platform-server": "^7.2.10",
"@tweenjs/tween.js": "^18.5.0", "@angular/router": "^7.2.10",
"ali-oss": "^6.5.1", "@tweenjs/tween.js": "^17.3.0",
"compressing": "^1.5.0", "ali-oss": "^6.0.0",
"ng-zorro-antd": "^8.5.2", "angular-cropperjs": "^1.0.1",
"rxjs": "~6.5.4", "angular2-draggable": "^2.1.9",
"angular2-fontawesome": "^5.2.1",
"angularx-qrcode": "^1.5.3",
"animate.css": "^3.7.0",
"bootstrap": "^4.1.1",
"browser-image-compression": "^1.0.5",
"compressing": "^1.4.0",
"core-js": "^2.6.1",
"cropperjs": "1.4.1",
"css-element-queries": "^1.0.2",
"decimal.js": "^10.0.1",
"font-awesome": "^4.7.0",
"hammerjs": "^2.0.8",
"install": "^0.12.2",
"karma-cli": "^2.0.0",
"lodash": "^4.17.10",
"nedb": "^1.8.0",
"ng-lottie": "^0.3.1",
"ng-zorro-antd": "^7.2.0",
"node-sass": "^4.14.1",
"npm": "^6.5.0",
"rxjs": "^6.3.3",
"rxjs-compat": "^6.3.3",
"rxjs-tslint": "^0.1.6",
"spark-md5": "^3.0.0", "spark-md5": "^3.0.0",
"tslib": "^1.10.0", "webpack": "^4.28.2",
"zone.js": "~0.10.2" "zone.js": "^0.8.26"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.900.3", "@angular-devkit/build-angular": "^0.11.4",
"@angular/cli": "~9.0.3", "@angular/cli": "^7.2.10",
"@angular/compiler-cli": "~9.0.2", "@angular/compiler-cli": "^7.2.10",
"@angular/language-service": "~9.0.2", "@angular/language-service": "^7.2.10",
"@types/jasmine": "~3.5.0", "@types/jasmine": "^3.3.5",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1", "@types/node": "^10.12.18",
"codelyzer": "^5.1.2", "codelyzer": "^4.5.0",
"jasmine-core": "~3.5.0", "jasmine-core": "^3.3.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "^4.2.1",
"karma": "~4.3.0", "karma": "^3.1.4",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "~2.1.0", "karma-coverage-istanbul-reporter": "~2.0.0",
"karma-jasmine": "~2.0.1", "karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.2", "karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.3", "protractor": "^5.4.2",
"ts-node": "~8.3.0", "ts-node": "~5.0.1",
"tslint": "~5.18.0", "tslint": "^5.12.0",
"typescript": "~3.7.5" "typescript": "3.1.1"
} }
} }
import { ErrorHandler } from '@angular/core';
export class MyErrorHandler implements ErrorHandler {
handleError(error) {
console.log(error.stack);
(<any> window).courseware.sendErrorLog(error);
}
}
\ No newline at end of file
...@@ -5,12 +5,12 @@ import { Component , OnInit} from '@angular/core'; ...@@ -5,12 +5,12 @@ import { Component , OnInit} from '@angular/core';
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit{
type = 'play'; type = 'play';
constructor() { constructor() {
const tp = this.getQueryString('type'); let tp = this.getQueryString("type");
if (tp) { if (tp){
this.type = tp; this.type = tp;
} }
} }
......
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import {MyErrorHandler} from './MyError';
import { AppComponent } from './app.component'; import { BrowserModule } from '@angular/platform-browser';
import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd';
import {Angular2FontawesomeModule} from 'angular2-fontawesome/angular2-fontawesome';
import { AppComponent } from './app.component';
import { FormComponent } from './form/form.component';
import { PlayComponent } from "./play/play.component";
import { LessonTitleConfigComponent } from './common/lesson-title-config/lesson-title-config.component';
import { AudioRecorderComponent } from './common/audio-recorder/audio-recorder.component';
import { PlayerContentWrapperComponent } from './common/player-content-wrapper/player-content-wrapper.component';
/** 配置 angular i18n **/
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh'; import zh from '@angular/common/locales/zh';
import {FormComponent} from './form/form.component'; import {UploadImageWithPreviewComponent} from "./common/upload-image-with-preview/upload-image-with-preview.component";
import {PlayComponent} from './play/play.component'; import {BackgroundImagePipe} from "./pipes/background-image.pipe";
import {LessonTitleConfigComponent} from './common/lesson-title-config/lesson-title-config.component'; import {UploadVideoComponent} from "./common/upload-video/upload-video.component";
import {BackgroundImagePipe} from './pipes/background-image.pipe'; import {ResourcePipe} from "./pipes/resource.pipe";
import {UploadImageWithPreviewComponent} from './common/upload-image-with-preview/upload-image-with-preview.component'; import {TimePipe} from "./pipes/time.pipe";
import {PlayerContentWrapperComponent} from './common/player-content-wrapper/player-content-wrapper.component'; import {CustomHotZoneComponent} from "./common/custom-hot-zone/custom-hot-zone.component";
import {CustomHotZoneComponent} from './common/custom-hot-zone/custom-hot-zone.component';
import {UploadVideoComponent} from './common/upload-video/upload-video.component';
import {TimePipe} from './pipes/time.pipe';
import {ResourcePipe} from './pipes/resource.pipe';
import {AudioRecorderComponent} from './common/audio-recorder/audio-recorder.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from '@fortawesome/free-regular-svg-icons';
registerLocaleData(zh); registerLocaleData(zh);
@NgModule({ @NgModule({
...@@ -42,24 +42,20 @@ registerLocaleData(zh); ...@@ -42,24 +42,20 @@ registerLocaleData(zh);
CustomHotZoneComponent, CustomHotZoneComponent,
PlayerContentWrapperComponent PlayerContentWrapperComponent
], ],
imports: [ imports: [
BrowserModule,
NgZorroAntdModule,
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FontAwesomeModule BrowserModule,
Angular2FontawesomeModule,
NgZorroAntdModule,
], ],
providers: [ /** 配置 ng-zorro-antd 国际化(文案 及 日期) **/
{provide: ErrorHandler, useClass: MyErrorHandler}, providers : [
{ provide: NZ_I18N, useValue: zh_CN } { provide: NZ_I18N, useValue: zh_CN }
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { export class AppModule { }
constructor(library: FaIconLibrary) {
library.addIconPacks(fas, far);
}
}
<div class="d-flex"> <div class="d-flex">
<div class="p-btn-record d-flex"> <div class="p-btn-record d-flex">
<div class="btn-clear" style="cursor: pointer" (click)="onBtnClearAudio()" *ngIf="withRmBtn && (audioUrl || audioBlob)"> <div class="btn-clear" (click)="onBtnClearAudio()" *ngIf="withRmBtn && (audioUrl || audioBlob)">
<fa-icon icon="times"></fa-icon> <fa name="close"></fa>
</div> </div>
<div class="btn-record" *ngIf="type===Type.RECORD && !isUploading" <div class="btn-record" *ngIf="type===Type.RECORD && !isUploading"
[class.p-recording]="isRecording" [class.p-recording]="isRecording"
(click)="onBtnRecord()"> (click)="onBtnRecord()">
<fa-icon icon="microphone"></fa-icon> <fa name="microphone"></fa>
Record Audio Record Audio
</div> </div>
<nz-upload <nz-upload
[nzAccept] = "'.mp3'" [nzAccept] = "'.mp3'"
[nzShowUploadList]="false" [nzShowUploadList]="false"
[nzAction]="uploadUrl" [nzAction]="uploadUrl"
[nzData]="uploadData" [nzData]="uploadData"
(nzChange)="handleChange($event)"> (nzChange)="handleChange($event)">
<div class="btn-upload" [ngClass]="{'has-clear': withRmBtn && (audioUrl || audioBlob)}" *ngIf="type===Type.UPLOAD && !isUploading"> <div class="btn-upload" [ngClass]="{'has-clear': withRmBtn && (audioUrl || audioBlob)}" *ngIf="type===Type.UPLOAD && !isUploading">
<fa-icon icon="upload"></fa-icon> <fa name="upload"></fa>
Upload Audio Upload Audio
</div> </div>
</nz-upload> </nz-upload>
<div class="p-upload-progress-bg" *ngIf="isUploading"> <div class="p-upload-progress-bg" *ngIf="isUploading">
<div class="i-bg" [style.width]="progress+'%'"></div> <div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text"> <div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon> <fa name="cloud-upload"></fa>
Uploading... Uploading...
</div> </div>
</div> </div>
...@@ -35,14 +35,14 @@ ...@@ -35,14 +35,14 @@
<ng-template #truthyTemplate > <ng-template #truthyTemplate >
<div class="btn-delete" (click)="onBtnDeleteAudio()"> <div class="btn-delete" (click)="onBtnDeleteAudio()">
<fa-icon icon="close"></fa-icon> <fa name="close"></fa>
</div> </div>
</ng-template> </ng-template>
<ng-template #falsyTemplate> <ng-template #falsyTemplate>
<div class="btn-switch" (click)="onBtnSwitchType()"> <div class="btn-switch" (click)="onBtnSwitchType()">
<fa-icon icon="cog"></fa-icon> <fa name="cog"></fa>
</div> </div>
</ng-template> </ng-template>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
<nz-progress [nzPercent]="percent" [nzWidth]="30" [nzFormat]="progressText" <nz-progress [nzPercent]="percent" [nzWidth]="30" [nzFormat]="progressText"
nzType="circle"></nz-progress> nzType="circle"></nz-progress>
<div class="p-btn-play" [style.left]="isPlaying?'8px':''"> <div class="p-btn-play" [style.left]="isPlaying?'8px':''">
<fa-icon [icon]="playIcon"></fa-icon> <fa [name]="playIcon"></fa>
</div> </div>
</div> </div>
</div> </div>
.d-flex{
display: flex;
}
.p-btn-record { .p-btn-record {
font-size: 0.9rem; font-size: 0.9rem;
color: #555; color: #555;
......
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, NgZone, OnChanges} from '@angular/core'; import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, NgZone, OnChanges} from '@angular/core';
import {NzMessageService, NzNotificationService, UploadFile} from 'ng-zorro-antd'; import {NzMessageService, NzNotificationService, UploadFile} from 'ng-zorro-antd';
import {HttpClient, HttpEvent, HttpEventType, HttpRequest} from '@angular/common/http'; import {HttpClient, HttpEvent, HttpEventType, HttpRequest} from '@angular/common/http';
import {environment} from '../../../environments/environment'; import {environment} from '../../../environments/environment';
declare var Recorder; declare var Recorder;
...@@ -9,7 +9,7 @@ declare var Recorder; ...@@ -9,7 +9,7 @@ declare var Recorder;
selector: 'app-audio-recorder', selector: 'app-audio-recorder',
templateUrl: './audio-recorder.component.html', templateUrl: './audio-recorder.component.html',
styleUrls: ['./audio-recorder.component.scss'] styleUrls: ['./audio-recorder.component.scss']
}) })
export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
_audioUrl: string; _audioUrl: string;
audio = new Audio(); audio = new Audio();
...@@ -19,11 +19,10 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -19,11 +19,10 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
isUploading = false; isUploading = false;
type = Type.UPLOAD; // record | upload type = Type.UPLOAD; // record | upload
Type = Type; Type = Type;
@Input()
withRmBtn = false; withRmBtn = false;
uploadUrl; uploadUrl = (<any>window).courseware.uploadUrl();
uploadData; uploadData = (<any>window).courseware.uploadData();
@Input() @Input()
needRemove = false; needRemove = false;
...@@ -33,7 +32,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -33,7 +32,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
@Input() @Input()
set audioUrl(url) { set audioUrl(url) {
this._audioUrl = url; this._audioUrl = url
if (url) { if (url) {
this.audio.src = this._audioUrl; this.audio.src = this._audioUrl;
this.audio.load(); this.audio.load();
...@@ -53,15 +52,9 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -53,15 +52,9 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
recorder: any; recorder: any;
audioBlob: any; audioBlob: any;
constructor( private nzMessageService: NzMessageService ) {
constructor( private nzMessageService: NzMessageService ) {
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
} }
init() { init() {
...@@ -144,14 +137,13 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -144,14 +137,13 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
} }
// 开始录音 // 开始录音
onBtnRecord = () => { onBtnRecord = () => {
} }
// 切换模式 // 切换模式
onBtnSwitchType() { onBtnSwitchType() {
} }
onBtnClearAudio() { onBtnClearAudio() {
this.audioUrl = null;
this.audioRemoved.emit(); this.audioRemoved.emit();
} }
...@@ -160,7 +152,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -160,7 +152,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
this.audioRemoved.emit(); this.audioRemoved.emit();
} }
handleChange(info: { type: string, file: UploadFile, event: any }): void { handleChange(info: { type: string, file: UploadFile, event: any }): void {
switch (info.type) { switch (info.type) {
case 'start': case 'start':
this.isUploading = true; this.isUploading = true;
...@@ -193,19 +185,19 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy { ...@@ -193,19 +185,19 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
} }
return true; return true;
} }
beforeUpload = (file: File) => { beforeUpload = (file: File) => {
this.audioUrl = null; this.audioUrl = null;
if (!this.checkSelectFile(file)) { if (!this.checkSelectFile(file)) {
return false; return false;
} }
this.isUploading = true; this.isUploading = true;
this.progress = 0; this.progress = 0;
} }
uploadSuccess = (url) => { uploadSuccess = (url) => {
this.nzMessageService.info('Upload Success'); this.nzMessageService.info('Upload Success');
this.isUploading = false; this.isUploading = false;
this.audioUrl = url; this.audioUrl = url;
} }
uploadFailure = (err, file) => { uploadFailure = (err, file) => {
this.isUploading = false; this.isUploading = false;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<div class="bg-box"> <div class="bg-box">
<app-upload-image-with-preview <app-upload-image-with-preview
[picUrl]="bgItem?.url" [picUrl]="bgItem.url"
(imageUploaded)="onBackgroundUploadSuccess($event)"> (imageUploaded)="onBackgroundUploadSuccess($event)">
</app-upload-image-with-preview> </app-upload-image-with-preview>
</div> </div>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
<button class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round" <button class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
(click)="saveClick()"> (click)="saveClick()">
<i nz-icon nzType="save"></i> <i nz-icon type="save"></i>
Save Save
</button> </button>
......
...@@ -42,8 +42,8 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges { ...@@ -42,8 +42,8 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
@Output() @Output()
save = new EventEmitter(); save = new EventEmitter();
@ViewChild('canvas', {static: true }) canvas: ElementRef; @ViewChild('canvas') canvas: ElementRef;
@ViewChild('wrap', {static: true }) wrap: ElementRef; @ViewChild('wrap') wrap: ElementRef;
// @HostListener('window:resize', ['$event']) // @HostListener('window:resize', ['$event'])
canvasWidth = 1280; canvasWidth = 1280;
...@@ -613,9 +613,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges { ...@@ -613,9 +613,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
update() { update() {
if (!this.ctx) {
return;
}
this.animationId = window.requestAnimationFrame(this.update.bind(this)); this.animationId = window.requestAnimationFrame(this.update.bind(this));
......
<div class="title-config"> <div class="title-config">
<div class="title-wrap"> <div class="title-wrap">
<div class="row" style="margin: 5px;"> <div class="row" style="margin: 5px;">
<div class="p-content" style="width:100%"> <div class="p-content" style="width:100%">
<div class="p-tool-box d-flex" style="background: #fff;"> <div class="p-tool-box d-flex" style="background: #fff;">
<nz-select class="ml-1" style="width: 120px;" [(ngModel)]="__fontFamily" <nz-select class="ml-1" style="width: 120px;" [(ngModel)]="__fontFamily"
(ngModelChange)="onChangeFontFamily($event)" (ngModelChange)="onChangeFontFamily($event)"
nzPlaceHolder="Font Family" nzPlaceHolder="Font Family"
[nzDropdownMatchSelectWidth]="false"> [nzDropdownMatchSelectWidth]="false"
<nz-option [nzValue]="font" nzCustomContent [nzLabel]="font" *ngFor="let font of fontFamilyList"> >
<span [ngStyle]="{'font-family': font}" >{{font}}</span> <nz-option [nzValue]="font" [nzLabel]="font" *ngFor="let font of fontFamilyList"></nz-option>
</nz-option> </nz-select>
</nz-select>
<nz-select class="ml-1" style="width: 110px;" [(ngModel)]="__fontSize" <nz-select class="ml-1" style="width: 110px;" [(ngModel)]="__fontSize"
(ngModelChange)="onChangeFontSize()" (ngModelChange)="onChangeFontSize()"
nzPlaceHolder="Font Size"> nzPlaceHolder="Font Size">
<nz-option [nzValue]="i" [nzLabel]="'Size - ' + i" *ngFor="let i of fontSizeRange"></nz-option> <nz-option [nzValue]="i" [nzLabel]="'Size - ' + i" *ngFor="let i of fontSizeRange"></nz-option>
</nz-select> </nz-select>
<div class="p-divider"></div> <div class="p-divider"></div>
<div class="i-tool-font-btn d-flex mr-2"> <div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeBold()"> <div class="position-relative fa-icon" (click)="onChangeBold()">
<!-- <div class="fa fa-bold"></div>--> <div class="fa fa-bold"></div>
<fa-icon icon="bold"></fa-icon>
</div> </div>
</div> </div>
<div class="i-tool-font-btn d-flex mr-2"> <div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeItalic()"> <div class="position-relative fa-icon" (click)="onChangeItalic()">
<!-- <div class="fa fa-italic"></div>--> <div class="fa fa-italic"></div>
<fa-icon icon="italic"></fa-icon>
</div> </div>
</div> </div>
<div class="i-tool-font-btn d-flex mr-2"> <div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeUnderline()"> <div class="position-relative fa-icon" (click)="onChangeUnderline()">
<!-- <div class="fa fa-underline"></div>--> <div class="fa fa-underline"></div>
<fa-icon icon="underline"></fa-icon>
</div> </div>
</div> </div>
<div class="i-tool-font-btn d-flex"> <div class="i-tool-font-btn d-flex">
<div class="position-relative fa-icon" (click)="onChangeStrikethrough()"> <div class="position-relative fa-icon" (click)="onChangeStrikethrough()">
<!-- <div class="fa fa-strikethrough"></div>--> <div class="fa fa-strikethrough"></div>
<fa-icon icon="strikethrough"></fa-icon>
</div> </div>
</div> </div>
<div class="p-divider"></div> <div class="p-divider"></div>
<div class="i-tool-font-color d-flex"> <div class="i-tool-font-color d-flex">
<div class="position-relative i-left flex-fill" (click)="onChangeFontColor($event)"> <div class="position-relative i-left flex-fill" (click)="onChangeFontColor($event)">
<!-- <div class="fa fa-font"></div>--> <div class="fa fa-font"></div>
<fa-icon icon="palette"></fa-icon>
<div class="i-color" [style.background-color]="__fontColor"></div> <div class="i-color" [style.background-color]="__fontColor"></div>
</div> </div>
<div class="i-dropdown-menu" nzPlacement="bottom" <div class="i-dropdown-menu" nzPlacement="bottom"
nz-popover [(nzVisible)]="isShowFontColorPane" nzTrigger="click" nz-popover [(nzVisible)]="isShowFontColorPane" nzTrigger="click"
[nzContent]="colorPane"> [nzContent]="colorPane">
<i nz-icon type="down" theme="outline"></i> <i nz-icon type="down" theme="outline"></i>
</div> </div>
</div> </div>
<div class="p-divider"></div> <div class="p-divider"></div>
<div style="background: #fff;display: block;"> <div style="background: #fff;display: block;">
<div class="position-relative"> <div class="position-relative" (click)="onChangeStrikethrough()">
<app-audio-recorder [audioUrl]="titleObj && titleObj.audio_url" (audioUploaded)="titleAudioUploaded($event)"></app-audio-recorder> <app-audio-recorder [audioUrl]="titleObj && titleObj.audio_url" (audioUploaded)="titleAudioUploaded($event)"></app-audio-recorder>
</div> </div>
</div> </div>
</div> </div>
<div class="width-100 d-flex"> <div class="width-100 d-flex">
<iframe #titleEl id="titleContentEgret" frameborder="0" style="overflow: hidden;width: 100%; height:48px; margin: 0; padding: 0;"></iframe> <iframe #titleEl frameborder="0" style="overflow: hidden;width: 100%; height:48px; margin: 0; padding: 0;"></iframe>
</div> </div>
</div> </div>
...@@ -83,11 +76,37 @@ ...@@ -83,11 +76,37 @@
<ng-container *ngIf="withIcon">
<div class="row type-row">
课程类型:
<nz-radio-group [(ngModel)]="titleObj && titleObj.type" (ngModelChange)="typeChange($event)">
<label nz-radio nzValue="a">单数课</label>
<label nz-radio nzValue="b">双数课</label>
<label nz-radio nzValue="c">复习课</label>
</nz-radio-group>
</div>
</ng-container>
</div> </div>
<ng-container *ngIf="withIcon">
<div class="title-icons">
<div class="icons-list">
<nz-checkbox-wrapper style="width: 100%;clear:both" (nzOnChange)="iconsChanges($event)">
<div [class]="'icon-item icon-'+i" *ngFor="let i of groupIconsCount[titleObj.type];">
<div class="img-box">
<nz-badge class="icon-badge" [nzCount]="titleObj && titleObj.icons && titleObj.icons.indexOf(i) + 1">
<img [src]="'assets/title-icons/'+titleObj.type+'/icon-'+i+'.png'" alt="">
</nz-badge>
</div>
<label nz-checkbox [nzValue]="i" [ngModel]="titleObj && titleObj.icons && titleObj.icons.indexOf(i) > -1"></label>
</div>
</nz-checkbox-wrapper>
</div>
</div>
</ng-container>
</div> </div>
@import '../../style/common_mixin.css'; @import '../../style/common_mixin';
.title-config { .title-config {
.letter-wrap{ .letter-wrap{
...@@ -11,73 +11,39 @@ ...@@ -11,73 +11,39 @@
.type-row{ .type-row{
margin: 0;padding-top: 1rem; margin: 0;padding-top: 1rem;
} }
.icon-item{
margin-right: 16px;
float: left;
width: 45px;
height: 75px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.icon-badge{
position: absolute;
top: 0;
right: 0;
}
.img-box{
top: 0;
position: absolute;
width: 45px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
img{
max-width: 100%;
}
}
label{
position: absolute;
bottom: 0;
}
}
}
}
@font-face
{
font-family: 'BRLNSDB';
src: url("../../../assets/font/BRLNSDB.TTF") ;
}
@font-face
{
font-family: 'BRLNSB';
src: url("../../../assets/font/BRLNSB.TTF") ;
}
@font-face
{
font-family: 'BRLNSR';
src: url("../../../assets/font/BRLNSR.TTF") ;
}
@font-face
{
font-family: 'GOTHIC';
src: url("../../../assets/font/GOTHIC.TTF") ;
}
@font-face
{
font-family: 'GOTHICB';
src: url("../../../assets/font/GOTHICB.TTF") ;
}
@font-face
{
font-family: 'GOTHICBI';
src: url("../../../assets/font/GOTHICBI.TTF") ;
}
@font-face
{
font-family: 'GOTHICI';
src: url("../../../assets/font/GOTHICI.TTF") ;
}
@font-face
{
font-family: 'MMTextBook';
src: url("../../../assets/font/MMTextBook.otf") ;
}
@font-face
{
font-family: 'MMTextBook-Bold';
src: url("../../../assets/font/MMTextBook-Bold.otf") ;
}
@font-face
{
font-family: 'MMTextBook-BoldItalic';
src: url("../../../assets/font/MMTextBook-BoldItalic.otf") ;
}
@font-face
{
font-family: 'MMTextBook-Italic';
src: url("../../../assets/font/MMTextBook-Italic.otf") ;
}
@mixin tool-btn { @mixin tool-btn {
border: 1px solid #ddd; border: 1px solid #ddd;
display: flex; display: flex;
...@@ -88,37 +54,16 @@ ...@@ -88,37 +54,16 @@
border-radius: 6px; border-radius: 6px;
color: #555; color: #555;
} }
.d-flex{
display: flex;
}
.position-relative {
position: relative;
}
.flex-fill {
-webkit-box-flex: 1;
flex: 1 1 auto;
justify-content: center;
display: flex;
}
.i-dropdown-menu{ .p-title-box {
width: 15px; .p-title {
font-size: 10px;
border-left: 1px solid #ddd;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
flex: 0
}
.p-title-box .p-title {
font-size: 20px; font-size: 20px;
} }
.p-title-box input { input {
width: 300px; width: 300px;
margin-left: 10px; margin-left: 10px;
} }
}
.p-content { .p-content {
border: 1px solid #ddd; border: 1px solid #ddd;
...@@ -140,110 +85,115 @@ ...@@ -140,110 +85,115 @@
align-items: center; align-items: center;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
// save
.i-tool-save {
@include tool-btn();
color: white;
}
.i-tool-save:disabled {
color: #555;
}
// font-size
.i-tool-font-size {
@include tool-btn();
width: 37px;
} & > span {
// save
.i-tool-save {
//@include tool-btn();
color: white;
}
.i-tool-save:disabled {
color: #555;
}
// font-size
.i-tool-font-size {
//@include tool-btn();
width: 37px;
}
.i-tool-font-size:hover {
color: black;
border-color: #bbb;
}
// font-color
.i-tool-font-color, .i-tool-font-btn {
border: 1px solid #ddd;
//padding: 3px 7px;
border-radius: 6px;
width: 45px;
height: 31px;
background-color: white;
color: #555;
::ng-deep > span {
display: flex;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding: 3px 7px;
}
.i-left {
.fa-font,.fa-bold,.fa-italic,.fa-strikethrough, .fa-underline {
font-size: 10px;
position: absolute; position: absolute;
color: #555; top: -5px;
left: 8px; right: 5px;
top: 7px;
} }
.i-color { }
width: 68%; .i-tool-font-size:hover {
height: 5px; color: black;
background-color: black; border-color: #bbb;
}
// font-color
.i-tool-font-color, .i-tool-font-btn {
border: 1px solid #ddd;
//padding: 3px 7px;
border-radius: 6px;
width: 45px;
height: 31px;
background-color: white;
color: #555;
::ng-deep > span {
display: flex;
position: absolute; position: absolute;
top: 21px; left: 0;
left: 5px; right: 0;
top: 0;
bottom: 0;
padding: 3px 7px;
} }
} .i-left {
.i-dropdown-menu { .fa-font,.fa-bold,.fa-italic,.fa-strikethrough, .fa-underline {
width: 15px; font-size: 10px;
font-size: 10px; position: absolute;
border-left: 1px solid #ddd; color: #555;
display: flex; left: 8px;
align-items: center; top: 7px;
.anticon-down { }
transform: scale(0.6); .i-color {
width: 68%;
height: 5px;
background-color: black;
position: absolute;
top: 21px;
left: 5px;
}
}
.i-dropdown-menu {
width: 15px;
font-size: 10px;
border-left: 1px solid #ddd;
display: flex;
align-items: center;
.anticon-down {
transform: scale(0.6);
}
} }
} }
} .i-tool-font-btn{
.i-tool-font-btn{ width: 31px;
width: 31px; }
} .fa-icon{
.fa-icon{ width: 100%;
width: 100%; height: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
// bg-color
.i-tool-bg-color {
@include tool-btn();
padding: 0 9px;
::ng-deep > span {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
cursor: pointer;
} }
// bg-color
.i-tool-bg-color {
@include tool-btn();
padding: 0 9px;
::ng-deep > span {
display: flex;
align-items: center;
}
.i-color { .i-color {
display: block; display: block;
width: 16px; width: 16px;
height: 16px; height: 16px;
background-color: white; background-color: white;
margin-left: 10px; margin-left: 10px;
}
} }
}
// horizontal-center // horizontal-center
.i-tool-horizontal-center { .i-tool-horizontal-center {
@include tool-btn(); @include tool-btn();
width: 37px; width: 37px;
}
} }
.p-box { .p-box {
width: 1280px; width: 1280px;
height: 720px; height: 720px;
...@@ -253,7 +203,9 @@ ...@@ -253,7 +203,9 @@
overflow: hidden; overflow: hidden;
} }
.p-sentence {
@include k-no-select();
}
.p-animation-index-box { .p-animation-index-box {
.i-animation-index { .i-animation-index {
...@@ -326,6 +278,7 @@ ...@@ -326,6 +278,7 @@
::ng-deep .ant-radio-button-wrapper { ::ng-deep .ant-radio-button-wrapper {
padding: 0 10px; padding: 0 10px;
@include k-no-select();
} }
.i-toolbox { .i-toolbox {
...@@ -356,6 +309,7 @@ ...@@ -356,6 +309,7 @@
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
display: flex; display: flex;
@include k-no-select();
} }
.i-active { .i-active {
background-color: antiquewhite; background-color: antiquewhite;
......
import { import {
Component, Component,
ElementRef, ElementRef,
...@@ -11,82 +10,6 @@ import { ...@@ -11,82 +10,6 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
const editorTpl = `<html lang="en"><head><meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
<style>
@font-face{
font-family: 'BRLNSDB';
src: url("../../../assets/font/BRLNSDB.TTF") ;
}
@font-face{
font-family: 'BRLNSB';
src: url("../../../assets/font/BRLNSB.TTF") ;
}
@font-face{
font-family: 'BRLNSR';
src: url("../../../assets/font/BRLNSR.TTF") ;
}
@font-face{
font-family: 'GOTHIC';
src: url("../../../assets/font/GOTHIC.TTF") ;
}
@font-face{
font-family: 'GOTHICB';
src: url("../../../assets/font/GOTHICB.TTF") ;
}
@font-face{
font-family: 'GOTHICBI';
src: url("../../../assets/font/GOTHICBI.TTF") ;
}
@font-face{
font-family: 'GOTHICI';
src: url("../../../assets/font/GOTHICI.TTF") ;
}
@font-face{
font-family: 'MMTextBook';
src: url("../../../assets/font/MMTextBook.otf") ;
}
@font-face{
font-family: 'MMTextBook-Bold';
src: url("../../../assets/font/MMTextBook-Bold.otf") ;
}
@font-face{
font-family: 'MMTextBook-BoldItalic';
src: url("../../../assets/font/MMTextBook-BoldItalic.otf") ;
}
@font-face{
font-family: 'MMTextBook-Italic';
src: url("../../../assets/font/MMTextBook-Italic.otf") ;
}
html, body{
/*font-size: 30px;*/
}
body{
height:48px;
overflow: hidden;
margin: 0;
padding: 0 .5rem;
font-family: 'BRLNSB, BRLNSDB, BRLNSR, GOTHIC, GOTHICB, MMTextBook';
background: #FFF;
line-height: 48px;
}
</style>
</head>
<body>{{content}}</body>
</html>`;
@Component({ @Component({
selector: 'app-lesson-title-config', selector: 'app-lesson-title-config',
templateUrl: './lesson-title-config.component.html', templateUrl: './lesson-title-config.component.html',
...@@ -96,45 +19,22 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -96,45 +19,22 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
fontFamilyList = [ fontFamilyList = [
'Arial', 'Arial',
'BRLNSB', 'ARBLI'
'BRLNSDB',
'BRLNSR',
'GOTHIC',
'GOTHICB',
// "GOTHICBI",
// "GOTHICI",
'MMTextBook',
// "MMTextBook-Bold",
// "MMTextBook-Italic",
// "MMTextBook-BoldItalic",
]; ];
colorList = [ colorList = [
'#000000', '#111111',
'#ffffff', '#ffffff',
'#595959', '#595959',
'#0075c2', '#0075c2',
'#c61c1e', '#c61c1e',
'#9cbc3a', '#9cbc3a'
'#008000',
'#FF0000',
'#D2691E',
]; ];
MIN_FONT_SIZE = 1; MIN_FONT_SIZE = 1;
MAX_FONT_SIZE = 7; MAX_FONT_SIZE = 7;
isShowFontColorPane = false; isShowFontColorPane = false;
isShowBGColorPane = false; isShowBGColorPane = false;
fontSizeRange = [ fontSizeRange: number[];
// {name: '1号', value: 9},
// {name: '2号', value: 13},
// {name: '3号', value: 16},
// {name: '4号', value: 18},
// {name: '5号', value: 24},
// {name: '6号', value: 32},
];
editorContent = ''; editorContent = '';
...@@ -145,17 +45,25 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -145,17 +45,25 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
loopCnt = 0; loopCnt = 0;
maxLoops = 20; maxLoops = 20;
groupIconsCount = {
@ViewChild('titleEl', {static: true}) titleEl: ElementRef; a: Array.from(Array(11).keys()),
b: Array.from(Array(8).keys()),
c: Array.from(Array(8).keys()),
};
prevIcons = [];
prevType = '';
@ViewChild('titleEl') titleEl: ElementRef;
titleEW = null; titleEW = null;
@Input() @Input()
titleObj = { titleObj = {
type: 'a',
content: '', content: '',
icons: [],
audio_url: '' audio_url: ''
}; };
@Input()
withIcon = true;
@Output() @Output()
titleUpdated = new EventEmitter(); titleUpdated = new EventEmitter();
...@@ -176,12 +84,16 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -176,12 +84,16 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
let defObj = this.titleObj; let defObj = this.titleObj;
if (!vars.titleObj.currentValue) { if (!vars.titleObj.currentValue) {
defObj = { defObj = {
type: 'a',
content: '', content: '',
icons: [],
audio_url: '' audio_url: ''
}; };
} else { } else {
defObj = vars.titleObj.currentValue; defObj = vars.titleObj.currentValue;
} }
this.titleObj.icons = defObj.icons || [];
this.titleObj.type = defObj.type || 'a';
this.titleObj.content = defObj.content || ''; this.titleObj.content = defObj.content || '';
this.titleObj.audio_url = defObj.audio_url || ''; this.titleObj.audio_url = defObj.audio_url || '';
this.titleEW.document.body.innerHTML = this.titleObj.content; this.titleEW.document.body.innerHTML = this.titleObj.content;
...@@ -190,23 +102,33 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -190,23 +102,33 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
ngOnInit() { ngOnInit() {
if (!this.titleObj) { if (!this.titleObj) {
this.titleObj = { this.titleObj = {
type: 'a',
content: '', content: '',
icons: [],
audio_url: '' audio_url: ''
}; };
} }
this.titleObj.icons = this.titleObj.icons || [];
this.titleObj.type = this.titleObj.type || 'a';
this.titleObj.content = this.titleObj.content || ''; this.titleObj.content = this.titleObj.content || '';
this.titleObj.audio_url = this.titleObj.audio_url || ''; this.titleObj.audio_url = this.titleObj.audio_url || '';
this.editorContent = editorTpl.replace('{{content}}', this.titleObj.content) ; this.editorContent = `<html lang="en"><head><meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
</head>
<body style="height:48px;overflow: hidden;margin: 0;padding: 0 .5rem;background: #FFF;line-height: 48px;">
${this.titleObj.content}
</body>
</html>`;
this.titleEW = this.titleEl.nativeElement.contentWindow; this.titleEW = this.titleEl.nativeElement.contentWindow;
console.log('this.titleEW', this.titleEW);
const tdoc = this.titleEW.document; const tdoc = this.titleEW.document;
tdoc.designMode = 'on'; tdoc.designMode = "on";
tdoc.open('text/html', 'replace'); tdoc.open('text/html', 'replace');
tdoc.write(this.editorContent); tdoc.write(this.editorContent);
tdoc.close(); tdoc.close();
tdoc.addEventListener('keypress', this.keyPress, true); tdoc.addEventListener("keypress", this.keyPress, true);
tdoc.addEventListener('blur', () => { tdoc.addEventListener("blur", () => {
if (this.titleObj.content === this.titleEW.document.body.innerHTML.trim()) { if (this.titleObj.content === this.titleEW.document.body.innerHTML.trim()) {
return; return;
} }
...@@ -237,7 +159,30 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -237,7 +159,30 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
ngOnDestroy(): void { ngOnDestroy(): void {
} }
iconsChanges(val) {
let a = this.titleObj.icons;
let b = val;
if (a.length > b.length) {
const diff = a.filter(x => !b.includes(x));
const ti = [...this.titleObj.icons];
for (let i = 0; i < diff.length; i++) {
const d = diff[i];
const idx = ti.indexOf(d);
ti.splice(idx, 1);
}
this.titleObj.icons = ti;
} else {
const diff = b.filter(x => !a.includes(x));
this.titleObj.icons = [...this.titleObj.icons, ...diff];
}
this.shouldSave();
}
typeChange(val) {
this.titleObj.icons = [];
this.shouldSave();
}
keyPress(evt) { keyPress(evt) {
try { try {
...@@ -250,9 +195,9 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -250,9 +195,9 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
const key = String.fromCharCode(evt.charCode).toLowerCase(); const key = String.fromCharCode(evt.charCode).toLowerCase();
let cmd = ''; let cmd = '';
switch (key) { switch (key) {
case 'b': cmd = 'bold'; break; case 'b': cmd = "bold"; break;
case 'i': cmd = 'italic'; break; case 'i': cmd = "italic"; break;
case 'u': cmd = 'underline'; break; case 'u': cmd = "underline"; break;
} }
...@@ -269,13 +214,10 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -269,13 +214,10 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
alert(e); alert(e);
} }
} }
execEditorCommand(command, option?: any) { execEditorCommand(command, option?: any) {
console.log('sssss');
try { try {
this.titleEW.focus(); this.titleEW.focus();
const result = this.titleEW.document.execCommand(command, false, option); this.titleEW.document.execCommand(command, false, option);
console.log(result);
this.loopCnt = 0; this.loopCnt = 0;
return false; return false;
...@@ -287,7 +229,7 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -287,7 +229,7 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
}, 100); }, 100);
this.loopCnt += 1; this.loopCnt += 1;
} else { } else {
alert('Error executing command.'); alert("Error executing command.");
} }
} }
} }
...@@ -300,7 +242,7 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -300,7 +242,7 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
this.execEditorCommand('forecolor', this.__fontColor); this.execEditorCommand('forecolor', this.__fontColor);
} }
onChangeFontFamily(font) { onChangeFontFamily(font) {
this.execEditorCommand('fontName', font); this.execEditorCommand('fontname', font);
} }
onChangeFontSize(size?: any) { onChangeFontSize(size?: any) {
...@@ -330,10 +272,9 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, ...@@ -330,10 +272,9 @@ export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy,
this.titleUpdated.emit(this.titleObj); this.titleUpdated.emit(this.titleObj);
} }
shouldSave = () => { shouldSave = () => {
console.log('title shouldSave', this.titleObj); console.log('title shouldSave');
this.titleObj.content = this.titleEW.document.body.innerHTML.trim(); this.titleObj.content = this.titleEW.document.body.innerHTML.trim();
this.titleUpdated.emit(this.titleObj); this.titleUpdated.emit(this.titleObj);
} }
} }
@import '../../style/common_mixin.css'; @import '../../style/common_mixin';
.cmp-player-content-wrapper{ .cmp-player-content-wrapper{
max-height: 100%; max-height: 100%;
......
import { import {
AfterViewInit, AfterViewInit,
Component, Component,
ElementRef, ElementRef,
Input, Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
@Component({ @Component({
...@@ -18,21 +18,21 @@ import { ...@@ -18,21 +18,21 @@ import {
export class PlayerContentWrapperComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { export class PlayerContentWrapperComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@ViewChild('wrapperEl', {static: true }) wrapperEl: ElementRef; @ViewChild('wrapperEl') wrapperEl: ElementRef;
// // aspect ratio? // // aspect ratio?
@Input() ratio; @Input() ratio;
_w: string; _w: string;
_h: string; _h: string;
constructor() { constructor() {
if (window.innerHeight < window.innerWidth) { if (window.innerHeight < window.innerWidth) {
this._h = '100%'; this._h = '100%';
this._w = 'auto'; this._w = 'auto';
} else { } else {
this._w = '100%'; this._w = '100%';
this._h = 'auto'; this._h = 'auto';
} }
} }
ngOnInit() { ngOnInit() {
if (!this.ratio) { if (!this.ratio) {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<!--[nzBeforeUpload]="customUpload">--> <!--[nzBeforeUpload]="customUpload">-->
<div class="p-box d-flex align-items-center"> <div class="p-box d-flex align-items-center">
<div class="p-upload-icon" *ngIf="!picUrl && !uploading"> <div class="p-upload-icon" *ngIf="!picUrl && !uploading">
<i nz-icon nzType="cloud-upload" nzTheme="outline" [style.font-size]="iconSize + 'em'"></i> <i nz-icon type="cloud-upload" theme="outline" [style.font-size]="iconSize + 'em'"></i>
<div class="m-3"></div> <div class="m-3"></div>
<span>{{TIP}}</span> <span>{{TIP}}</span>
<!--<div class="mt-5 p-progress-bar" *ngIf="uploading">--> <!--<div class="mt-5 p-progress-bar" *ngIf="uploading">-->
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<div class="p-upload-progress-bg" *ngIf="uploading"> <div class="p-upload-progress-bg" *ngIf="uploading">
<div class="i-bg" [style.width]="progress+'%'"></div> <div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text"> <div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon> <fa name="cloud-upload"></fa>
Uploading... Uploading...
</div> </div>
</div> </div>
...@@ -32,6 +32,6 @@ ...@@ -32,6 +32,6 @@
nz-popconfirm nzTitle="Are you sure ?" nz-popconfirm nzTitle="Are you sure ?"
(nzOnConfirm)="onDelete()" (nzOnConfirm)="onDelete()"
> >
<i nz-icon nzType="close" nzTheme="outline"></i> <i nz-icon type="close" theme="outline"></i>
</div> </div>
</div> </div>
@import '../../style/common_mixin.css'; @import '../../style/common_mixin';
.p-image-uploader { .p-image-uploader {
position: relative; position: relative;
...@@ -52,15 +52,10 @@ ...@@ -52,15 +52,10 @@
.p-preview { .p-preview {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-size: contain;
background-repeat: no-repeat;
background-position: 50% 50%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"); //background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
@include k-img-bg();
} }
} }
.d-flex{
display: flex;
}
} }
.p-btn-delete { .p-btn-delete {
......
...@@ -29,19 +29,10 @@ export class UploadImageWithPreviewComponent implements OnDestroy, OnChanges { ...@@ -29,19 +29,10 @@ export class UploadImageWithPreviewComponent implements OnDestroy, OnChanges {
@Input() @Input()
disableUpload = false; disableUpload = false;
uploadUrl; uploadUrl = (<any> window).courseware.uploadUrl();
uploadData; uploadData = (<any> window).courseware.uploadData();
constructor(private nzMessageService: NzMessageService) { constructor(private nzMessageService: NzMessageService) {
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
} }
ngOnChanges() { ngOnChanges() {
if (!this.picItem) { if (!this.picItem) {
......
<div class="p-video-box">
<div class="up-video" style="display: flex;"> <div class="up-video" style="display: flex;">
<!--<nz-upload class="" [nzDisabled]="!showUploadBtn"--> <!--<nz-upload class="" [nzDisabled]="!showUploadBtn"-->
<!--[nzShowUploadList]="false"--> <!--[nzShowUploadList]="false"-->
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
<button type="button" nz-button nzType="default" *ngIf="showUploadBtn" [disabled]="uploading" <button type="button" nz-button nzType="default" *ngIf="showUploadBtn" [disabled]="uploading"
[nzLoading]="uploading" > [nzLoading]="uploading" >
<i nz-icon nzType="plus" nzTheme="outline"></i> <i nz-icon type="plus" theme="outline"></i>
<span>{{ uploading ? 'Uploading' : 'Select Video' }}</span> <span>{{ uploading ? 'Uploading' : 'Select Video' }}</span>
<!--<span>Select Video</span>--> <!--<span>Select Video</span>-->
</button> </button>
...@@ -55,9 +56,9 @@ ...@@ -55,9 +56,9 @@
</div> </div>
<div class="p-box d-flex align-items-center p-video-uploader"> <div class="p-box d-flex align-items-center p-video-uploader" style="top: 20px;">
<div class="p-upload-icon" *ngIf="!showUploadBtn && !videoUrl && !uploading"> <div class="p-upload-icon" *ngIf="!showUploadBtn && !videoUrl && !uploading">
<i nz-icon nzType="upload" nzTheme="outline"></i> <i nz-icon type="upload" theme="outline"></i>
<div class="m-3"></div> <div class="m-3"></div>
<span>Click here to upload video</span> <span>Click here to upload video</span>
<div class="mt-5 p-progress-bar" *ngIf="uploading"> <div class="mt-5 p-progress-bar" *ngIf="uploading">
...@@ -69,26 +70,26 @@ ...@@ -69,26 +70,26 @@
[ngClass]="{'smart-bar': showUploadBtn}" > [ngClass]="{'smart-bar': showUploadBtn}" >
<div class="i-bg" [style.width]="progress+'%'"></div> <div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text"> <div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon> <fa name="cloud-upload"></fa>
Uploading... Uploading...
</div> </div>
</div> </div>
<div class="p-upload-check-bg" *ngIf="checking"> <div class="p-upload-check-bg" *ngIf="checking">
<div class="i-bg" [style.width]="progress+'%'"></div> <div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text"> <div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon> <fa name="cloud-upload"></fa>
<i nz-icon nzType="loading" nzTheme="outline"></i>Checking... <i nz-icon type="loading" theme="outline"></i>Checking...
</div> </div>
</div> </div>
<div class="p-preview" *ngIf="!uploading && videoUrl " > <div class="p-preview" *ngIf="!showUploadBtn && !uploading && videoUrl " >
<!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>--> <!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>-->
<video [src]="safeVideoUrl(videoUrl)" controls #videoNode></video> <video [src]="safeVideoUrl(videoUrl)" controls #videoNode (loadedmetadata)="videoLoadedMetaData()"></video>
</div> </div>
</div> </div>
<div [style.display]="!checkVideoExists?'none':''"> <div [style.display]="!checkVideoExists?'none':''">
<span><i nz-icon nzType="loading" nzTheme="outline"></i> checking file to upload</span> <span><i nz-icon type="loading" theme="outline"></i> checking file to upload</span>
</div>
</div> </div>
@import '../../style/common_mixin.css'; @import '../../style/common_mixin';
/*.p-video-box{
bottom: 0;
border: 2px dashed #ddd;
border-radius: 0.5rem;
background-color: #fafafa;
text-align: center;
color: #aaa;
padding-top: 56.25%;
//font-size: 4rem;
position: relative;
.p-upload-icon{
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50% ,-50%);
}
}*/
.p-video-uploader { .p-video-uploader {
position: relative; position: relative;
display: block; display: block;
...@@ -33,56 +17,50 @@ ...@@ -33,56 +17,50 @@
background-color: #fafafa; background-color: #fafafa;
text-align: center; text-align: center;
color: #aaa; color: #aaa;
.p-upload-icon {
text-align: center;
margin: auto;
.anticon-upload {
color: #888;
font-size: 5rem;
}
.p-progress-bar {
position: relative;
width: 20rem;
height: 1.5rem;
border: 1px solid #ccc;
border-radius: 1rem;
.p-progress-bg {
background-color: #1890ff;
border-radius: 1rem;
height: 100%;
}
.p-progress-value {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-shadow: 0 0 4px #000;
color: white;
text-align: center;
font-size: 0.9rem;
line-height: 1.5rem;
}
}
}
.p-preview {
width: 100%;
height: 100%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
video{
max-height: 100%;
max-width: 100%;
}
}
} }
} }
.p-upload-icon {
text-align: center;
margin: auto;
}
.p-upload-icon .anticon-upload {
color: #888;
font-size: 5rem;
}
p-progress-bar {
position: relative;
width: 20rem;
height: 1.5rem;
border: 1px solid #ccc;
border-radius: 1rem;
}
.p-progress-bar .p-progress-bg {
background-color: #1890ff;
border-radius: 1rem;
height: 100%;
}
.p-progress-bar .p-progress-value {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-shadow: 0 0 4px #000;
color: white;
text-align: center;
font-size: 0.9rem;
line-height: 1.5rem;
}
.p-preview {
width: 100%;
height: 100%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
}
.p-preview video{
max-height: 100%;
max-width: 100%;
position: absolute;
display: flex;
}
.p-btn-delete { .p-btn-delete {
position: absolute; position: absolute;
right: -0.5rem; right: -0.5rem;
......
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SecurityContext, ViewChild} from '@angular/core'; import {Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SecurityContext, ViewChild} from '@angular/core';
import {NzMessageService, UploadChangeParam, UploadFile, UploadFileStatus} from 'ng-zorro-antd'; import {NzMessageService, UploadFile} from 'ng-zorro-antd';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
...@@ -24,7 +24,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -24,7 +24,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
@Input() @Input()
videoUrl = ''; videoUrl = '';
@ViewChild('videoNode', {static: true }) @ViewChild('videoNode')
videoNode: ElementRef; videoNode: ElementRef;
...@@ -47,8 +47,8 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -47,8 +47,8 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
item: any; item: any;
// videoItem = null; // videoItem = null;
uploadUrl; uploadUrl = (<any> window).courseware.uploadUrl();
uploadData; uploadData = (<any> window).courseware.uploadData();
constructor(private nzMessageService: NzMessageService, constructor(private nzMessageService: NzMessageService,
private sanitization: DomSanitizer private sanitization: DomSanitizer
...@@ -58,16 +58,6 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -58,16 +58,6 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
this.uploading = false; this.uploading = false;
this.videoFile = null; this.videoFile = null;
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
} }
ngOnChanges() { ngOnChanges() {
// if (!this.videoFile || this.showUploadBtn) { // if (!this.videoFile || this.showUploadBtn) {
...@@ -81,7 +71,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -81,7 +71,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
} }
safeVideoUrl(url) { safeVideoUrl(url) {
console.log(url); console.log(url)
return this.sanitization.bypassSecurityTrustResourceUrl(url); // `${url}`; return this.sanitization.bypassSecurityTrustResourceUrl(url); // `${url}`;
} }
videoLoadedMetaData() { videoLoadedMetaData() {
...@@ -89,7 +79,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -89,7 +79,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
} }
handleChange(info: UploadChangeParam/* { type: string, file: UploadFile, event: any }*/): void { handleChange(info: { type: string, file: UploadFile, event: any }): void {
console.log('info:' , info); console.log('info:' , info);
...@@ -119,7 +109,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -119,7 +109,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
break; break;
case 'progress': case 'progress':
this.progress = info.event.percent; this.progress = parseInt(info.event.percent, 10);
this.doProgress(this.progress); this.doProgress(this.progress);
break; break;
} }
...@@ -162,9 +152,9 @@ export class UploadVideoComponent implements OnChanges, OnDestroy { ...@@ -162,9 +152,9 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
if (duration) { if (duration) {
duration = duration * 1000; duration = duration * 1000;
} }
file.height = height; file['height'] = height;
file.width = width; file['width'] = width;
file.duration = duration; file['duration'] = duration;
vid.preload = 'none'; vid.preload = 'none';
vid.src = ''; vid.src = '';
vid.remove(); vid.remove();
......
@import '../style/common_mixin.css';
.model-content {
width: 100%;
height: 100%;
}
<div class="body">
<div ng-app class="model-content">
<div class="title">
<h2>
标题
</h2>
<p style="margin: 5px 0;">标题:</p>
<input class="input_button" type="text" [(ngModel)]="item.contentObj.title.text" (ngModelChange)="save()">
<p style="margin: 5px 0;">题号:</p>
<input style="width: 100px;" type="text" [(ngModel)]="item.contentObj.title.number" (ngModelChange)="save()">
<p style="margin: 5px 0;">标题-音频:</p>
<app-audio-recorder class="audio_load"
[audioUrl]="item.contentObj.title.audio_url"
(audioUploaded)="onAudioUploadSuccessByItem($event, item.contentObj.title)">
</app-audio-recorder>
<p style="margin: 5px 0;">背景音乐-音频</p>
<app-audio-recorder class="audio_load"
[audioUrl]="item.contentObj.audio_url"
(audioUploaded)="onAudioUploadSuccessByItem($event, item.contentObj)">
</app-audio-recorder>
<button *ngIf="item.contentObj.values.length != 4" class="button_add" (click)="addValue()">
添加一句内容
</button>
</div>
<div class="upload_items">
<div class="load_values_item" *ngFor="let value of values; let i = index" style="display: block;">
<h3>第{{i + 1}}句</h3>
<nz-radio-group [(ngModel)]="value.number" (ngModelChange)="save()" class="gruop_load">
显示序号:
<label nz-radio nzValue="true"></label>
<label nz-radio nzValue="false"></label>
</nz-radio-group>
<p style="margin: 5px 0;">句子文本</p>
<input class="input_button" type="text" [(ngModel)]="value.text" (ngModelChange)="sentence_handle(i)">
<p style="margin: 5px 0;">答案</p>
<nz-select nzMode="multiple" nzShowSearch nzAllowClear nzPlaceHolder="输入可快速查询" [(ngModel)]="value.answer" (ngModelChange)= "sentence_handle(i)">
<div *ngFor="let text of value.word;let j = index">
<nz-option [nzValue]="j" [nzLabel]= "j+1+ '-' + value.word[j]"></nz-option>
</div>
</nz-select>
<p style="margin: 5px 0;">答案颜色</p>
<nz-select [(ngModel)]="value.answer_color" (ngModelChange)="save()">
<nz-option nzValue="C6161E" nzLabel="红色"></nz-option>
<nz-option nzValue="BACF41" nzLabel="绿色"></nz-option>
<nz-option nzValue="6D3E21" nzLabel="巧克力色"></nz-option>
<nz-option nzValue="000000" nzLabel="黑色"></nz-option>
</nz-select>
<div class="colorview"></div>
<p style="margin: 5px 0;">点击音效</p>
<app-audio-recorder
[audioUrl]="value.audio_url"
(audioUploaded)="onAudioUploadSuccessByItem($event, value)"
>
</app-audio-recorder>
<h5 style="margin-top: 10px;margin-bottom: 10px;">图片</h5>
<app-upload-image-with-preview class="img_load"
[picUrl]="value.img_url"
(imageUploaded)="onImageUploadSuccessByItem($event, value)"
></app-upload-image-with-preview>
<div class="model-content"> <button style="margin-left: 10px;" class="button_remove" nz-button nzType="danger" (click)="deleteValue(value)">
删除
</button>
<div style="position: absolute; left: 200px; top: 100px; width: 800px;">
<input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
<app-upload-image-with-preview
[picUrl]="item.pic_url"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')"
></app-upload-image-with-preview>
<app-audio-recorder
[audioUrl]="item.audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
></app-audio-recorder>
<app-custom-hot-zone></app-custom-hot-zone>
<app-upload-video></app-upload-video>
<app-lesson-title-config></app-lesson-title-config>
</div> </div>
</div>
\ No newline at end of file
</div>
\ No newline at end of file
@import '../style/common_mixin';
p{
margin: 0;
padding: 0;
color: #707F88;
}
nz-select{
width: 100%;
height: 50px;
// border:1px solid #dbdbdb;
// background-color: #ffffff;
border: 0px;
// outline: 1px solid rgb(204,204,204);
}
.body{
background-color: #C2D9FF;
height: 1200px;
}
input{
width:100%;
height:50px;
border:1px solid #dbdbdb;
outline:none;
font-size:20px;
text-indent:10px;
}
.model-content{
width: 80%;
margin: 0 auto;
min-width: 1200px;
padding-top: 20px;
.gruop_load{
display: block;
}
.title{
width: 800px;
background-color: #ffffff;
padding: 30px;
border-radius: 10px;
margin: 0 auto;
box-shadow: 5px 5px 10px #9DC3F1;
position: relative;
}
.button_add{
border: 2px solid #ddd;
margin: 20px;
width: 200px;
height: 100px;
border-radius: 5px;
background-color: #fafafa;
outline: none;
position: absolute;
right :50px;
top: 150px;
}
}
.upload_items{
display: flex;
flex-wrap: wrap;
margin-top: 20px;
.load_values_item{
border-radius: 10px;
width: 24%;
padding: 30px;
margin: 0 0.5%;
box-sizing: border-box;
position: relative;
box-shadow: 5px 5px 20px #9DC3F1;
background-color: #ffffff;
}
.button_remove{
position: absolute;
top: 20px;
right: 25px;
}
}
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core'; import {
Component,
EventEmitter,
Input,
OnDestroy,
OnChanges,
OnInit,
Output,
ApplicationRef,
ChangeDetectorRef
} from '@angular/core';
@Component({ @Component({
selector: 'app-form', selector: 'app-form',
templateUrl: './form.component.html', templateUrl: './form.component.html',
styleUrls: ['./form.component.css'] styleUrls: ['./form.component.scss']
}) })
export class FormComponent implements OnInit, OnChanges, OnDestroy { export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用 KEY = 'hw-24';
saveKey = "test_0011";
// 储存对象
item;
values = [];
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) { _item: any;
set item(item) {
this._item = item;
// this.init();
}
get item() {
return this._item;
} }
@Output()
update = new EventEmitter();
ngOnInit() { constructor(private appRef: ApplicationRef,
public changeDetectorRef: ChangeDetectorRef){
}
this.item = {}; init(){
if ( !this.item.contentObj ) {
this.item.contentObj = {};
}
if (this.item.contentObj.values) {
this.values = this.item.contentObj.values;
} else {
const arr = [];
this.values = arr;
this.item.contentObj.values = this.values;
}
if(!this.item.contentObj.title){
this.item.contentObj.title = {
text:"",
audio_url:"",
number: ""
};
}
if(!this.item.audio_url){
this.item.audio_url = "";
}
}
// 获取存储的数据 onImageUploadSuccessByItem(e, item, id = null) {
(<any> window).courseware.getData((data) => {
if (data) { if (id != null) {
this.item = data; item[id + '_img_url'] = e.url;
} } else {
item.img_url = e.url;
}
this.init(); this.save();
this.changeDetectorRef.markForCheck(); }
this.changeDetectorRef.detectChanges();
this.refresh(); onAudioUploadSuccessByItem(e, item, id = null) {
}, this.saveKey); if (id != null) {
item[id + '_audio_url'] = e.url;
} else {
item.audio_url = e.url;
}
this.save();
} }
addValue() {
this.values.push({
number : "false",
text: "",
answer: [],
audio_url: "",
img_url: "",
answer_color:"000000",
word : []
});
console.log('values', this.values);
ngOnChanges() { this.save();
} }
ngOnDestroy() { deleteValue(data) {
console.log("删除");
const index = this.values.indexOf(data);
if (index !== -1) {
this.values.splice(index, 1);
}
this.save();
} }
ngOnInit() {
this.item = {};
this.item.contentObj = {};
this.item.contentObj.audio_url= "";
this.item.contentObj.title = {
text:"",
audio_url:"",
number:"",
};
init() {
} if(!this.item.contentObj.values){
const arr = [];
this.values = arr;
this.item.contentObj.values = this.values;
}
/**
* 储存图片数据
* @param e
*/
onImageUploadSuccess(e, key) {
this.item[key] = e.url; const getData = (<any> window).courseware.getData;
this.save(); getData((data) => {
}
if (data) {
this.item = data;
} else {
this.item = {};
}
if ( !this.item.contentObj ) {
this.item.contentObj = {};
}
this.refresh();
this.init();
/** this.changeDetectorRef.markForCheck();
* 储存音频数据 this.changeDetectorRef.detectChanges();
* @param e
*/
onAudioUploadSuccess(e, key) { }, this.KEY);
}
this.item[key] = e.url; saveItem() {
console.log(' in saveItem');
this.save(); this.save();
} }
sentence_handle(i){
let Arr = this.item.contentObj.values[i].text;
if(Arr == ""){
this.item.contentObj.values[i].word = [];
this.item.contentObj.values[i].answer = [];
}
else{
let value = Arr.split(/\s+/);
this.item.contentObj.values[i].word = value;
}
if(this.item.contentObj.values[i].word[this.item.contentObj.values[i].word.length - 1] == "")
this.item.contentObj.values[i].word.splice(this.item.contentObj.values[i].word.length - 1,1);
for(let j = 0;j < this.item.contentObj.values[i].answer.length;j++){
if(this.item.contentObj.values[i].answer[j] + 1 > this.item.contentObj.values[i].word.length){
this.item.contentObj.values[i].answer[j] = "";
}
}
if(this.item.contentObj.values[i].answer == [])
this.item.contentObj.values[i].answer = [];
this.item.contentObj.values[i].answer.sort(function(a, b){return a - b});
this.save();
}
/**
* 储存数据
*/
save() { save() {
(<any> window).courseware.setData(this.item, null, this.saveKey); (<any> window).courseware.setData(this.item, null, this.KEY);
this.refresh(); this.refresh();
} }
/**
* 刷新 渲染页面
*/
refresh() { refresh() {
setTimeout(() => { setTimeout(() => {
this.appRef.tick(); this.appRef.tick();
}, 1); }, 1);
} }
ngOnDestroy(): void {
throw new Error("Method not implemented.");
}
ngOnChanges(changes: import("@angular/core").SimpleChanges): void {
throw new Error("Method not implemented.");
}
} }
This diff is collapsed.
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #ffffff; background: #ffffff;
background-repeat: no-repeat;
background-size: cover; background-size: cover;
overflow: hidden;
} }
#canvas { #canvas {
......
This diff is collapsed.
const res = [ const res = [
// ['bg', "assets/play/bg.jpg"], ['bg', "assets/play/bg.png"],
['btn_left', "assets/play/btn_left.png"], ['btn_left', "assets/play/btn_left.png"],
['btn_right', "assets/play/btn_right.png"], ['btn_right', "assets/play/btn_right.png"],
// ['text_bg', "assets/play/text_bg.png"], ['text_bg', "assets/play/text_bg.png"],
['play',"assets/play/play.png"],
['stop',"assets/play/stop.png"],
['title_bg',"assets/play/bg_top.png"]
]; ];
...@@ -11,7 +15,10 @@ const res = [ ...@@ -11,7 +15,10 @@ const res = [
const resAudio = [ const resAudio = [
['click', "assets/play/music/click.mp3"],
// ['click', "assets/play/music/click.mp3"],
]; ];
......
import { TestBed } from '@angular/core/testing';
import { ConfigService } from './config.service';
describe('ConfigService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ConfigService = TestBed.get(ConfigService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable} from "rxjs";
@Injectable({
providedIn: 'root'
})
export class ConfigService {
configUrl = 'proxy.conf.json';
constructor( private http: HttpClient ) { }
getConfig() {
return this.http.get(this.configUrl)
.pipe(
);
}
showConfig() {
this.getConfig()
.subscribe((data) => {
console.log('data:', data);
});
}
test(data) {
// return new Promise<any> {
// return new Promise((resolve, reject) => {
// this.http.get(url).subscribe((res) => {
// resolve(res);
// }, reject);
// });
//
return this.get('/api/login');
// return this.http.post('/login', data);
}
get(url: string): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(url).subscribe((res) => {
resolve(res);
}, reject);
});
}
// login(user): Observable<any> {
//
//
// return this.http.post('/login', user).pipe(
// tap((data => {
// console.log('login', data)
// localStorage.setItem('token', _.get(data, 'token'));
// delete data['token'];
// localStorage.setItem('info', JSON.stringify(data));
// this.getInfoPromise = this.load('/user/getInfo');
// }))
// );
// }
}
@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;
}
.anticon{
vertical-align: .1em!important;
}
File mode changed from 100755 to 100644
File mode changed from 100644 to 100755
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>NgOne</title> <title>NgOne</title>
<base href="/"> <base href="/">
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">-->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<script type="text/javascript" src="https://staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script> <script type="text/javascript" src="http://teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
......
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>NgOne</title>
<base href="/">
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script type="text/javascript" src="https://staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
</head>
<body>
Hello World!!
</body>
</html>
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