├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── LICENSE
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── mock
│ ├── index.js
│ └── json-helper.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
├── webpack.prod.conf.js
└── webpack.test.conf.js
├── config
├── dev.env.js
├── index.js
├── prod.env.js
└── test.env.js
├── index.html
├── mock
├── bg.jpg
├── bg1.png
├── bg2.jpg
├── bg3.jpg
├── bg4.jpg
├── bg5.png
├── db.json
├── image1.jpg
├── image2.png
├── image3.png
├── image4.jpg
├── image5.jpg
├── image6.png
├── image7.png
└── image8.png
├── package.json
├── readme
├── preview.gif
└── right24x24.png
├── src
├── App.vue
├── assets
│ └── logo.png
├── common
│ ├── event
│ │ ├── EventDispatcher.ts
│ │ └── GlsEvent.ts
│ ├── render
│ │ ├── Renderer.spec.ts
│ │ └── Renderer.ts
│ ├── tooltip
│ │ └── _tooltip.scss
│ ├── utils.spec.ts
│ ├── utils.ts
│ └── utils
│ │ └── Debounce.ts
├── main.ts
├── modules
│ └── editor
│ │ ├── common
│ │ ├── button
│ │ │ └── Button.vue
│ │ ├── colorPicker
│ │ │ ├── Color.ts
│ │ │ ├── ColorPicker.vue
│ │ │ └── vue-color
│ │ │ │ ├── .babelrc
│ │ │ │ ├── .eslintrc.js
│ │ │ │ ├── .gitignore
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── intro.png
│ │ │ │ ├── package.json
│ │ │ │ ├── src
│ │ │ │ ├── components
│ │ │ │ │ ├── Chrome.vue
│ │ │ │ │ ├── Compact.vue
│ │ │ │ │ ├── Material.vue
│ │ │ │ │ ├── Photoshop.vue
│ │ │ │ │ ├── Sketch.vue
│ │ │ │ │ ├── Slider.vue
│ │ │ │ │ ├── Swatches.vue
│ │ │ │ │ └── common
│ │ │ │ │ │ ├── Alpha.vue
│ │ │ │ │ │ ├── Checkboard.vue
│ │ │ │ │ │ ├── EditableInput.vue
│ │ │ │ │ │ ├── Hue.vue
│ │ │ │ │ │ └── Saturation.vue
│ │ │ │ ├── index.js
│ │ │ │ └── mixin
│ │ │ │ │ └── color.js
│ │ │ │ └── yarn.lock
│ │ ├── dialog
│ │ │ ├── Dialog.vue
│ │ │ ├── Modal.vue
│ │ │ ├── Resizer.vue
│ │ │ ├── index.js
│ │ │ ├── parser.js
│ │ │ └── util.js
│ │ ├── dragable
│ │ │ ├── Draggable.ts
│ │ │ └── IDraggableResult.ts
│ │ ├── interact
│ │ │ ├── Draggable.ts
│ │ │ └── IDragableOptions.ts
│ │ ├── overlayPanel
│ │ │ └── OverlayPanel.vue
│ │ ├── panel
│ │ │ └── Panel.vue
│ │ ├── scss
│ │ │ ├── _animate.scss
│ │ │ ├── _background.scss
│ │ │ ├── _border-radius.scss
│ │ │ ├── _border.scss
│ │ │ ├── _cursor.scss
│ │ │ ├── _float.scss
│ │ │ ├── _input.scss
│ │ │ ├── _padding.scss
│ │ │ ├── _shadow.scss
│ │ │ ├── _size.scss
│ │ │ ├── _skin.scss
│ │ │ ├── _transition.scss
│ │ │ ├── _variable.scss
│ │ │ ├── icons
│ │ │ │ ├── iconfont.eot
│ │ │ │ ├── iconfont.scss
│ │ │ │ ├── iconfont.svg
│ │ │ │ ├── iconfont.ttf
│ │ │ │ └── iconfont.woff
│ │ │ └── index.scss
│ │ ├── slider
│ │ │ └── Slider.vue
│ │ └── stepper
│ │ │ └── Stepper.vue
│ │ ├── componentEditor
│ │ ├── ComponentEditor.vue
│ │ ├── ComponentEditorEvent.ts
│ │ ├── componentBar
│ │ │ ├── ComponentBar.vue
│ │ │ └── IconLabel.vue
│ │ ├── header
│ │ │ └── Header.vue
│ │ ├── multiSelectEditor
│ │ │ └── MultiSelectEditor.vue
│ │ ├── sceneEditor
│ │ │ ├── SceneEditor.vue
│ │ │ ├── header.png
│ │ │ └── workspace
│ │ │ │ ├── WorkSpace.vue
│ │ │ │ ├── WorkSpaceEvent.ts
│ │ │ │ ├── assets
│ │ │ │ └── rotate-cursor.png
│ │ │ │ ├── displayComponentEditor
│ │ │ │ ├── DisplayComponentEditor.vue
│ │ │ │ ├── DisplayComponentSelect.vue
│ │ │ │ └── DisplayComponentSelectClasses.ts
│ │ │ │ ├── image
│ │ │ │ ├── ComponentImageDisplay.vue
│ │ │ │ └── ComponentImageEditor.vue
│ │ │ │ ├── resourceManager
│ │ │ │ ├── ResourceManager.vue
│ │ │ │ └── ResourceManagerEvent.ts
│ │ │ │ ├── stage
│ │ │ │ ├── ComponentStageDisplay.vue
│ │ │ │ └── ComponentStageEditor.vue
│ │ │ │ └── text
│ │ │ │ ├── ComponentTextDisplay.vue
│ │ │ │ └── ComponentTextEditor.vue
│ │ ├── sceneManager
│ │ │ ├── SceneManager.vue
│ │ │ └── SceneNameEdit.vue
│ │ ├── singleSelectEditor
│ │ │ ├── SingleSelectEditor.vue
│ │ │ └── animationEditor
│ │ │ │ └── AnimationEditor.vue
│ │ └── templatePanel
│ │ │ └── TemplatePanel.vue
│ │ └── core
│ │ ├── device
│ │ └── Devices.ts
│ │ ├── display
│ │ ├── DisplayComponent.spec.ts
│ │ ├── DisplayComponent.ts
│ │ ├── DisplayComponentContainer.spec.ts
│ │ ├── DisplayComponentContainer.ts
│ │ ├── ICloneable.ts
│ │ ├── config
│ │ │ └── AnimationConfig.ts
│ │ ├── image
│ │ │ └── ComponentImage.ts
│ │ ├── property
│ │ │ └── Properties.ts
│ │ ├── stage
│ │ │ └── Stage.ts
│ │ ├── style
│ │ │ └── IStyle.ts
│ │ └── text
│ │ │ └── ComponentText.ts
│ │ ├── factorys
│ │ ├── display
│ │ │ ├── DisplayComponentFactory.spec.ts
│ │ │ ├── DisplayComponentFactory.ts
│ │ │ └── DisplayComponentRegisterInfo.ts
│ │ └── scsne
│ │ │ └── SceneService.ts
│ │ ├── geom
│ │ ├── Rect.spec.ts
│ │ └── Rect.ts
│ │ ├── parse
│ │ ├── CopyParsteManager.spec.ts
│ │ └── CopyPasteManager.ts
│ │ ├── project
│ │ ├── Project.spec.ts
│ │ ├── Project.ts
│ │ └── ProjectService.ts
│ │ └── scene
│ │ ├── ISceneProperties.ts
│ │ ├── Scene.ts
│ │ └── SceneInfo.ts
├── polyfills
│ ├── PolyfillClassList.ts
│ └── Polyfills.ts
├── router
│ └── index.ts
└── vue-shims.d.ts
├── static
└── .gitkeep
├── test
├── e2e
│ ├── custom-assertions
│ │ └── elementCount.js
│ ├── nightwatch.conf.js
│ ├── runner.js
│ └── specs
│ │ └── test.js
└── unit
│ ├── .eslintrc
│ ├── index.js
│ └── karma.conf.js
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module'
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
13 | extends: 'standard',
14 | // required to lint *.vue files
15 | plugins: [
16 | 'html'
17 | ],
18 | // add your custom rules here
19 | 'rules': {
20 | // allow paren-less arrow functions
21 | 'arrow-parens': 0,
22 | // allow async-await
23 | 'generator-star-spacing': 0,
24 | // allow debugger during development
25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 | test/e2e/reports
9 | selenium-debug.log
10 |
11 | # Editor directories and files
12 | .idea
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 qq386232894
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # h5-editor
2 |
3 | > 一款仿易企秀的h5编辑器,此项目还在持续开发中。这也是人生中第五个编辑器了,也是第一个使用VUE制作的作品。这里你会学到面向对象编程,设计模式,TDD。[博客地址](http://www.cnblogs.com/geilishu/p/7677949.html)
4 |
5 | ## 预览
6 |
7 |
8 |
9 | ## Install
10 |
11 | npm install
12 | npm run mock
13 | 打开另一个CMD
14 | npm run start
15 |
16 | ## 开发计划
17 |
18 | ### 编辑器部分
19 | *
拖拉拽,旋转,大小,位置
20 | *
组件单选多选
21 | *
组件复制,黏贴
22 | *
组件拖拽过程中自动吸附其他组件
23 | *
多选组件的情况下,排序对齐。包括左对齐,上对齐,右对齐,下对齐,垂直居中对齐,水平居中对齐,垂直均分,水平均分
24 | *
组件常用样式编辑,阴影编辑,这里要引入多个UI库,甚至要自己写UI库,有点蛋疼
25 | >*
按钮
26 | >*
属性面板
27 | >*
拖拽指令gls-dragable
28 | >*
页签
29 | >*
手风琴
30 | >*
颜色选择器
31 | >*
滑动条
32 | >*
下拉框
33 | >*
弹出框
34 | >*
单选框
35 | >*
复选框
36 | >*
单行文本框
37 | >*
多行文本框
38 | >*
数字步增
39 | >*
tooltip
40 | *
组件动画编辑
41 | * 场景编辑
42 | >*
新建场景
43 | >*
删除场景
44 | >*
复制场景
45 | >*
场景拖拽排序
46 | >*
场景名称编辑
47 | *
增加背景编辑
48 | *
增加图片组件
49 | *
增加图片素材管理器
50 | * 文本双击之后的内容编辑
51 | * 增加音频组件
52 | * 增加音频素材管理
53 | * 吸附效果可视化,可配置化
54 | * 整合引擎,实现预览
55 | * 撤销,恢复
56 | * 实现页面模板
57 | ### 引擎部分
58 | * 项目内容的读取
59 | * 读取场景数据并渲染
60 | * 预加载场景数据
61 | * 引入swiper实现滑动换页
62 | 。。。
63 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | if (stats.hasErrors()) {
30 | console.log(chalk.red(' Build failed with errors.\n'))
31 | process.exit(1)
32 | }
33 |
34 | console.log(chalk.cyan(' Build complete.\n'))
35 | console.log(chalk.yellow(
36 | ' Tip: built files are meant to be served over an HTTP server.\n' +
37 | ' Opening index.html over file:// won\'t work.\n'
38 | ))
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | }
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var config = require('../config')
4 | if (!process.env.NODE_ENV) {
5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6 | }
7 |
8 | var opn = require('opn')
9 | var path = require('path')
10 | var express = require('express')
11 | var webpack = require('webpack')
12 | var proxyMiddleware = require('http-proxy-middleware')
13 | var webpackConfig = (process.env.NODE_ENV === 'testing' || process.env.NODE_ENV === 'production')
14 | ? require('./webpack.prod.conf')
15 | : require('./webpack.dev.conf')
16 | // default port where dev server listens for incoming traffic
17 | var port = process.env.PORT || config.dev.port
18 | // automatically open browser, if not set will be false
19 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
20 | // Define HTTP proxies to your custom API backend
21 | // https://github.com/chimurai/http-proxy-middleware
22 | var proxyTable = config.dev.proxyTable
23 |
24 | var app = express()
25 |
26 | var compiler = webpack(webpackConfig)
27 |
28 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
29 | publicPath: webpackConfig.output.publicPath,
30 | quiet: true
31 | })
32 |
33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
34 | log: false,
35 | heartbeat: 2000
36 | })
37 | // force page reload when html-webpack-plugin template changes
38 | compiler.plugin('compilation', function (compilation) {
39 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
40 | hotMiddleware.publish({action: 'reload'})
41 | cb()
42 | })
43 | })
44 |
45 | // proxy api requests
46 | Object.keys(proxyTable).forEach(function (context) {
47 | var options = proxyTable[context]
48 | if (typeof options === 'string') {
49 | options = {target: options}
50 | }
51 | app.use(proxyMiddleware(options.filter || context, options))
52 | })
53 |
54 | // handle fallback for HTML5 history API
55 | app.use(require('connect-history-api-fallback')())
56 |
57 | // serve webpack bundle output
58 | app.use(devMiddleware)
59 |
60 | // enable hot-reload and state-preserving
61 | // compilation error display
62 | app.use(hotMiddleware)
63 |
64 | // serve pure static assets
65 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
66 | app.use(staticPath, express.static('./static'))
67 |
68 | var uri = 'http://localhost:' + port
69 |
70 | var _resolve
71 | var readyPromise = new Promise(resolve => {
72 | _resolve = resolve
73 | })
74 |
75 | console.log('> Starting dev server...')
76 | devMiddleware.waitUntilValid(() => {
77 | console.log('> Listening at ' + uri + '\n')
78 | // when env is testing, don't need open it
79 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
80 | opn(uri)
81 | }
82 | _resolve()
83 | })
84 |
85 | var server = app.listen(port)
86 | module.exports = {
87 | ready: readyPromise,
88 | close: () => {
89 | server.close()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/build/mock/json-helper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/14/014.
3 | */
4 | var fs = require("fs")
5 | module
6 | .exports = class JsonHelper {
7 | static load(file) {
8 | return JSON.parse(fs.readFileSync(file));
9 | }
10 |
11 | static save(file, data) {
12 | fs.writeFileSync(file, JSON.stringify(data, null, 2));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: options.sourceMap
20 | }
21 | },
22 | postcssLoader = {
23 | loader: 'postcss-loader',
24 | options: {
25 | sourceMap: true,
26 | }
27 | }
28 |
29 | // generate loader string to be used with extract text plugin
30 | function generateLoaders (loader, loaderOptions) {
31 | var loaders = [cssLoader,postcssLoader]
32 | if (loader) {
33 | loaders.push({
34 | loader: loader + '-loader',
35 | options: Object.assign({}, loaderOptions, {
36 | sourceMap: options.sourceMap
37 | })
38 | })
39 | }
40 |
41 | // Extract CSS when that option is specified
42 | // (which is the case during production build)
43 | if (options.extract) {
44 | return ExtractTextPlugin.extract({
45 | use: loaders,
46 | fallback: 'vue-style-loader'
47 | })
48 | } else {
49 | return ['vue-style-loader'].concat(loaders)
50 | }
51 | }
52 |
53 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
54 | return {
55 | css: generateLoaders(),
56 | postcss: generateLoaders(),
57 | less: generateLoaders('less'),
58 | sass: generateLoaders('sass', { indentedSyntax: true }),
59 | scss: generateLoaders('sass'),
60 | stylus: generateLoaders('stylus'),
61 | styl: generateLoaders('stylus')
62 | }
63 | }
64 |
65 | // Generate loaders for standalone style files (outside of .vue)
66 | exports.styleLoaders = function (options) {
67 | var output = []
68 | var loaders = exports.cssLoaders(options)
69 | for (var extension in loaders) {
70 | var loader = loaders[extension]
71 | output.push({
72 | test: new RegExp('\\.' + extension + '$'),
73 | use: loader
74 | })
75 | }
76 | return output
77 | }
78 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | }),
12 | transformToRequire: {
13 | video: 'src',
14 | source: 'src',
15 | img: 'src',
16 | image: 'xlink:href'
17 | },
18 | esModule: true
19 | }
20 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 |
6 | function resolve(dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: ['./src/polyfills/Polyfills.ts', './src/main.ts']
13 | },
14 | output: {
15 | path: config.build.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: process.env.NODE_ENV === 'production'
18 | ? config.build.assetsPublicPath
19 | : config.dev.assetsPublicPath
20 | },
21 | resolve: {
22 | extensions: ['.js', '.vue', '.json', '.ts'],
23 | alias: {
24 | 'vue$': 'vue/dist/vue.esm.js',
25 | '@': resolve('src'),
26 | },
27 | },
28 | module: {
29 | rules: [
30 | // {
31 | // test: /\.(js|vue)$/,
32 | // loader: 'eslint-loader',
33 | // enforce: 'pre',
34 | // include: [resolve('src'), resolve('test')],
35 | // options: {
36 | // formatter: require('eslint-friendly-formatter')
37 | // }
38 | // },
39 | {
40 | test: /\.vue$/,
41 | loader: 'vue-loader',
42 | options: vueLoaderConfig
43 | },
44 | {
45 | test: /\.js$/,
46 | loader: 'babel-loader',
47 | include: [resolve('src'), resolve('test')]
48 | },
49 | {
50 | test: /\.ts$/,
51 | loader: 'ts-loader',
52 | options: {
53 | appendTsSuffixTo: [/\.vue$/]
54 | }
55 | },
56 | {
57 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
58 | loader: 'url-loader',
59 | options: {
60 | limit: 10000,
61 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
62 | }
63 | },
64 | {
65 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
66 | loader: 'url-loader',
67 | options: {
68 | limit: 10000,
69 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
70 | }
71 | },
72 | {
73 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
74 | loader: 'url-loader',
75 | options: {
76 | limit: 10000,
77 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
78 | }
79 | }
80 | ]
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 |
9 | // add hot-reload related code to entry chunks
10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
12 | })
13 |
14 | module.exports = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
17 | },
18 | // cheap-module-eval-source-map is faster for development
19 | devtool: '#cheap-module-eval-source-map',
20 | plugins: [
21 | new webpack.DefinePlugin({
22 | 'process.env': config.dev.env
23 | }),
24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
25 | new webpack.HotModuleReplacementPlugin(),
26 | new webpack.NoEmitOnErrorsPlugin(),
27 | // https://github.com/ampedandwired/html-webpack-plugin
28 | new HtmlWebpackPlugin({
29 | filename: 'index.html',
30 | template: 'index.html',
31 | inject: true
32 | }),
33 | new FriendlyErrorsPlugin()
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 |
12 | var env = process.env.NODE_ENV === 'testing'
13 | ? require('../config/test.env')
14 | : config.build.env
15 |
16 | var webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true
21 | })
22 | },
23 | devtool: config.build.productionSourceMap ? '#source-map' : false,
24 | output: {
25 | path: config.build.assetsRoot,
26 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
28 | },
29 | plugins: [
30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
31 | new webpack.DefinePlugin({
32 | 'process.env': env
33 | }),
34 | new webpack.optimize.UglifyJsPlugin({
35 | compress: {
36 | warnings: false
37 | },
38 | sourceMap: true
39 | }),
40 | // extract css into its own file
41 | new ExtractTextPlugin({
42 | filename: utils.assetsPath('css/[name].[contenthash].css')
43 | }),
44 | // Compress extracted CSS. We are using this plugin so that possible
45 | // duplicated CSS from different components can be deduped.
46 | new OptimizeCSSPlugin({
47 | cssProcessorOptions: {
48 | safe: true
49 | }
50 | }),
51 | // generate dist index.html with correct asset hash for caching.
52 | // you can customize output by editing /index.html
53 | // see https://github.com/ampedandwired/html-webpack-plugin
54 | new HtmlWebpackPlugin({
55 | filename: process.env.NODE_ENV === 'testing'
56 | ? 'index.html'
57 | : config.build.index,
58 | template: 'index.html',
59 | inject: true,
60 | minify: {
61 | removeComments: true,
62 | collapseWhitespace: true,
63 | removeAttributeQuotes: true
64 | // more options:
65 | // https://github.com/kangax/html-minifier#options-quick-reference
66 | },
67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
68 | chunksSortMode: 'dependency'
69 | }),
70 | // keep module.id stable when vender modules does not change
71 | new webpack.HashedModuleIdsPlugin(),
72 | // split vendor js into its own file
73 | new webpack.optimize.CommonsChunkPlugin({
74 | name: 'vendor',
75 | minChunks: function (module, count) {
76 | // any required modules inside node_modules are extracted to vendor
77 | return (
78 | module.resource &&
79 | /\.js$/.test(module.resource) &&
80 | module.resource.indexOf(
81 | path.join(__dirname, '../node_modules')
82 | ) === 0
83 | )
84 | }
85 | }),
86 | // extract webpack runtime and module manifest to its own file in order to
87 | // prevent vendor hash from being updated whenever app bundle is updated
88 | new webpack.optimize.CommonsChunkPlugin({
89 | name: 'manifest',
90 | chunks: ['vendor']
91 | }),
92 | // copy custom static assets
93 | new CopyWebpackPlugin([
94 | {
95 | from: path.resolve(__dirname, '../static'),
96 | to: config.build.assetsSubDirectory,
97 | ignore: ['.*']
98 | }
99 | ])
100 | ]
101 | })
102 |
103 | if (config.build.productionGzip) {
104 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
105 |
106 | webpackConfig.plugins.push(
107 | new CompressionWebpackPlugin({
108 | asset: '[path].gz[query]',
109 | algorithm: 'gzip',
110 | test: new RegExp(
111 | '\\.(' +
112 | config.build.productionGzipExtensions.join('|') +
113 | ')$'
114 | ),
115 | threshold: 10240,
116 | minRatio: 0.8
117 | })
118 | )
119 | }
120 |
121 | if (config.build.bundleAnalyzerReport) {
122 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
123 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
124 | }
125 |
126 | module.exports = webpackConfig
127 |
--------------------------------------------------------------------------------
/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | // This is the webpack config used for unit tests.
2 |
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseConfig = require('./webpack.base.conf')
7 |
8 | var webpackConfig = merge(baseConfig, {
9 | // use inline sourcemap for karma-sourcemap-loader
10 | module: {
11 | rules: utils.styleLoaders()
12 | },
13 | devtool: '#inline-source-map',
14 | resolveLoader: {
15 | alias: {
16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
18 | 'scss-loader': 'sass-loader'
19 | }
20 | },
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': require('../config/test.env')
24 | })
25 | ]
26 | })
27 |
28 | // no need for app entry during tests
29 | delete webpackConfig.entry
30 |
31 | module.exports = webpackConfig
32 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 8080,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable:{
31 | '/api/': {
32 | target: 'http://localhost:4000',
33 | changeOrigin: true
34 | }
35 | },
36 | // CSS Sourcemaps off by default because relative paths are "buggy"
37 | // with this option, according to the CSS-Loader README
38 | // (https://github.com/webpack/css-loader#sourcemaps)
39 | // In our experience, they generally work as expected,
40 | // just be aware of this issue when enabling this option.
41 | cssSourceMap: false
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var devEnv = require('./dev.env')
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"'
6 | })
7 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | h5-editor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/mock/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg.jpg
--------------------------------------------------------------------------------
/mock/bg1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg1.png
--------------------------------------------------------------------------------
/mock/bg2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg2.jpg
--------------------------------------------------------------------------------
/mock/bg3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg3.jpg
--------------------------------------------------------------------------------
/mock/bg4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg4.jpg
--------------------------------------------------------------------------------
/mock/bg5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/bg5.png
--------------------------------------------------------------------------------
/mock/image1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image1.jpg
--------------------------------------------------------------------------------
/mock/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image2.png
--------------------------------------------------------------------------------
/mock/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image3.png
--------------------------------------------------------------------------------
/mock/image4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image4.jpg
--------------------------------------------------------------------------------
/mock/image5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image5.jpg
--------------------------------------------------------------------------------
/mock/image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image6.png
--------------------------------------------------------------------------------
/mock/image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image7.png
--------------------------------------------------------------------------------
/mock/image8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/mock/image8.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "h5-editor",
3 | "version": "1.0.0",
4 | "description": "一款仿易企秀的h5编辑器",
5 | "author": "给力叔",
6 | "private": false,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "node build/dev-server.js",
10 | "build": "node build/build.js",
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --auto-watch",
12 | "e2e": "node test/e2e/runner.js",
13 | "test": "npm run test:unit && npm run test:e2e",
14 | "test:unit": "npm run unit",
15 | "test:e2e": "npm run e2e",
16 | "mock": "node build/mock/index.js",
17 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
18 | },
19 | "dependencies": {
20 | "angular2-decorators-for-vue": "1.0.2",
21 | "axios": "0.16.2",
22 | "babel-polyfill": "6.26.0",
23 | "bootstrap": "4.0.0-beta.2",
24 | "bootstrap-vue": "1.3.0",
25 | "core-js": "2.5.1",
26 | "interactjs": "1.2.9",
27 | "popper.js": "1.12.9",
28 | "vue": "2.5.9",
29 | "vue-color": "2.4.2",
30 | "vue-router": "3.0.1",
31 | "vue-validator": "3.0.0-alpha.2",
32 | "vuedraggable": "2.14.1"
33 | },
34 | "devDependencies": {
35 | "@types/jasmine": "2.8.2",
36 | "animate.css": "3.5.2",
37 | "autoprefixer": "6.7.7",
38 | "babel-core": "6.26.0",
39 | "babel-eslint": "5.0.4",
40 | "babel-loader": "7.1.2",
41 | "babel-plugin-istanbul": "4.1.5",
42 | "babel-plugin-transform-runtime": "6.23.0",
43 | "babel-preset-env": "1.6.1",
44 | "babel-preset-stage-2": "6.24.1",
45 | "babel-register": "6.26.0",
46 | "chai": "4.1.2",
47 | "chalk": "2.3.0",
48 | "chromedriver": "2.33.2",
49 | "compression": "1.7.1",
50 | "connect-history-api-fallback": "1.5.0",
51 | "copy-webpack-plugin": "4.2.3",
52 | "cross-env": "5.1.1",
53 | "cross-spawn": "5.1.0",
54 | "css-loader": "0.28.7",
55 | "cssnano": "3.10.0",
56 | "eslint": "^3.19.0",
57 | "eslint-config-standard": "^6.2.1",
58 | "eslint-friendly-formatter": "^3.0.0",
59 | "eslint-loader": "^1.7.1",
60 | "eslint-plugin-html": "^3.0.0",
61 | "eslint-plugin-promise": "^3.4.0",
62 | "eslint-plugin-standard": "^2.0.1",
63 | "eventsource-polyfill": "0.9.6",
64 | "express": "4.16.2",
65 | "extract-text-webpack-plugin": "2.1.2",
66 | "file-loader": "0.11.2",
67 | "friendly-errors-webpack-plugin": "1.6.1",
68 | "html-webpack-plugin": "2.30.1",
69 | "http-proxy-middleware": "0.17.4",
70 | "inject-loader": "3.0.1",
71 | "install": "0.10.2",
72 | "jasmine-core": "2.8.0",
73 | "karma": "1.7.1",
74 | "karma-coverage": "1.1.1",
75 | "karma-jasmine": "1.1.0",
76 | "karma-mocha": "1.3.0",
77 | "karma-phantomjs-launcher": "1.0.4",
78 | "karma-phantomjs-shim": "1.5.0",
79 | "karma-sinon-chai": "1.3.3",
80 | "karma-sourcemap-loader": "0.3.7",
81 | "karma-spec-reporter": "0.0.31",
82 | "karma-webpack": "2.0.6",
83 | "mocha": "3.0.0-2",
84 | "nightwatch": "0.9.16",
85 | "node-sass": "4.7.2",
86 | "npm": "5.5.1",
87 | "opn": "5.1.0",
88 | "optimize-css-assets-webpack-plugin": "2.0.0",
89 | "ora": "1.3.0",
90 | "phantomjs-prebuilt": "2.1.16",
91 | "postcss": "6.0.14",
92 | "postcss-cssnext": "3.0.2",
93 | "postcss-import": "11.0.0",
94 | "postcss-loader": "2.0.9",
95 | "rimraf": "2.6.2",
96 | "sass-loader": "6.0.6",
97 | "selenium-server": "3.7.1",
98 | "semver": "5.4.1",
99 | "shelljs": "0.7.8",
100 | "sinon": "2.0.0-pre.6",
101 | "sinon-chai": "2.14.0",
102 | "ts-loader": "2.3.7",
103 | "typescript": "2.6.2",
104 | "url-loader": "0.6.2",
105 | "vue-loader": "13.5.0",
106 | "vue-style-loader": "3.0.3",
107 | "vue-template-compiler": "2.5.9",
108 | "webpack": "2.7.0",
109 | "webpack-bundle-analyzer": "2.9.1",
110 | "webpack-dev-middleware": "1.12.2",
111 | "webpack-hot-middleware": "2.21.0",
112 | "webpack-merge": "4.1.1"
113 | },
114 | "engines": {
115 | "node": ">= 4.0.0",
116 | "npm": ">= 3.0.0"
117 | },
118 | "browserslist": [
119 | "> 1%",
120 | "last 2 versions",
121 | "not ie <= 8"
122 | ]
123 | }
124 |
--------------------------------------------------------------------------------
/readme/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/readme/preview.gif
--------------------------------------------------------------------------------
/readme/right24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/readme/right24x24.png
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
31 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/assets/logo.png
--------------------------------------------------------------------------------
/src/common/event/EventDispatcher.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/13
3 | * 事件监听和抛出
4 | * 原作者 mrdoob / http://mrdoob.com/
5 | * 改成typescript版本
6 | */
7 | import {GlsEvent} from "./GlsEvent";
8 |
9 | export type Listener = (event?: GlsEvent) => void;
10 |
11 | export class EventDispatcher {
12 | _listeners: { [key: string]: Array };
13 |
14 | addEventListener(type: string, listener: Listener): () => void {
15 | if (this._listeners === undefined) this._listeners = {};
16 | var listeners = this._listeners;
17 | if (listeners[type] === undefined) {
18 | listeners[type] = [];
19 | }
20 | if (listeners[type].indexOf(listener) === -1) {
21 | listeners[type].push(listener);
22 | }
23 | return () => {
24 | this.removeEventListener(type, listener);
25 | }
26 | }
27 |
28 | hasEventListener(type: string, listener: Listener) {
29 | if (this._listeners === undefined) return false;
30 | var listeners = this._listeners;
31 | return listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1;
32 | }
33 |
34 | removeEventListener(type: string, listener: Listener) {
35 | if (this._listeners === undefined) return;
36 | var listeners = this._listeners;
37 | var listenerArray = listeners[type];
38 | if (listenerArray !== undefined) {
39 | var index = listenerArray.indexOf(listener);
40 | if (index !== -1) {
41 | listenerArray.splice(index, 1);
42 | }
43 | }
44 | }
45 |
46 | dispatchEvent(event: GlsEvent) {
47 | if (this._listeners === undefined) return;
48 | var listeners = this._listeners;
49 | var listenerArray = listeners[event.type];
50 | if (listenerArray !== undefined) {
51 | event.target = this;
52 | var array = listenerArray.slice(0);
53 | for (var i = 0, l = array.length; i < l; i++) {
54 | array[i].call(this, event);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/common/event/GlsEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/11/011.
3 | * 事件的基础类
4 | */
5 | export class GlsEvent {
6 | type: string; //事件的类型
7 | target: any; //触发事件的对象
8 | data: any; //附带的数据
9 | private _isStopped:boolean = false; //事件是不是被停止掉了,停止掉了就不能再被抛出了
10 |
11 | public static LOADED:string = "loaded";
12 |
13 | constructor(type: string, target: any, data?: any) {
14 | this.type = type;
15 | this.target = target;
16 | this.data = data;
17 | }
18 |
19 | stopPropagation(){
20 | this._isStopped = true;
21 | }
22 |
23 | get isStopped(){
24 | return this._isStopped;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/common/render/Renderer.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/13
3 | */
4 | import {Renderer} from "./Renderer";
5 | describe("Renderer",function () {
6 | document.body.innerHTML = `
7 |
10 | `
11 | let outer = document.getElementById("outer");
12 | let inner = document.getElementById("inner");
13 |
14 |
15 | describe("findCloseElementByClass",function () {
16 | it("找到outer节点",function () {
17 | expect(Renderer.findCloseElementByClass(inner,"outer")).toEqual(outer);
18 | })
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/common/tooltip/_tooltip.scss:
--------------------------------------------------------------------------------
1 | //拷贝自 https://github.com/hekigan/vue-directive-tooltip
2 | $popper-background-color: #434B62 !default;
3 |
4 | .vue-tooltip {
5 | background-color: $popper-background-color;
6 | box-sizing: border-box;
7 | color: #fff;
8 | // min-width: 120px;
9 | max-width: 320px;
10 | padding: 6px 10px;
11 | border-radius: 3px;
12 | z-index: 10000;//修正跟弹窗冲突的问题
13 | box-shadow: 2px 2px 3px rgba(#000, 0.4);
14 | font-size:12px; //减少字体大小
15 |
16 | .vue-tooltip-content {
17 | text-align: center;
18 | }
19 | .tooltip-arrow {
20 | content: '';
21 | width: 0;
22 | height: 0;
23 | border-style: solid;
24 | position: absolute;
25 | margin: 5px;
26 | }
27 |
28 | &[x-placement^="top"] {
29 | margin-bottom: 5px;
30 |
31 | .tooltip-arrow {
32 | border-width: 5px 5px 0 5px;
33 | border-top-color: $popper-background-color;
34 | border-bottom-color: transparent !important;
35 | border-left-color: transparent !important;
36 | border-right-color: transparent !important;
37 | bottom: -5px;
38 | margin-top: 0;
39 | margin-bottom: 0;
40 | }
41 | }
42 |
43 | &[x-placement^="bottom"] {
44 | margin-top: 5px;
45 |
46 | .tooltip-arrow {
47 | border-width: 0 5px 5px 5px;
48 | border-bottom-color: $popper-background-color;
49 | border-top-color: transparent !important;
50 | border-left-color: transparent !important;
51 | border-right-color: transparent !important;
52 | top: -5px;
53 | margin-top: 0;
54 | margin-bottom: 0;
55 | }
56 | }
57 |
58 | &[x-placement^="right"] {
59 | margin-left: 5px;
60 |
61 | .tooltip-arrow {
62 | border-width: 5px 5px 5px 0;
63 | border-right-color: $popper-background-color;
64 | border-top-color: transparent !important;
65 | border-left-color: transparent !important;
66 | border-bottom-color: transparent !important;
67 | left: -5px;
68 | margin-left: 0;
69 | margin-right: 0;
70 | }
71 | }
72 |
73 | &[x-placement^="left"] {
74 | margin-right: 5px;
75 |
76 | .tooltip-arrow {
77 | border-width: 5px 0 5px 5px;
78 | border-left-color: $popper-background-color;
79 | border-top-color: transparent !important;
80 | border-right-color: transparent !important;
81 | border-bottom-color: transparent !important;
82 | right: -5px;
83 | margin-left: 0;
84 | margin-right: 0;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/common/utils.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 黎祥发 on 2017/1/22.
3 | * utils的单元测试
4 | */
5 | import {utils} from './utils';
6 |
7 | describe('utils', () => {
8 | it('utils.isArray', () => {
9 | expect(utils.isArray([])).toBe(true);
10 | expect(utils.isArray({})).toBe(false);
11 | expect(utils.isArray(1)).toBe(false);
12 | expect(utils.isArray("")).toBe(false);
13 | expect(utils.isArray(true)).toBe(false);
14 | expect(utils.isArray(undefined)).toBe(false);
15 | expect(utils.isArray(null)).toBe(false);
16 | });
17 |
18 | it("utils.isNumber",()=>{
19 | expect(utils.isNumber(1)).toBe(true);
20 | expect(utils.isNumber("")).toBe(false);
21 | expect(utils.isNumber(true)).toBe(false);
22 | expect(utils.isNumber(null)).toBe(false);
23 | expect(utils.isNumber(undefined)).toBe(false);
24 | });
25 |
26 |
27 | it("utils.isDefined",()=>{
28 | expect(utils.isDefined(1)).toBe(true);
29 | expect(utils.isDefined("")).toBe(true);
30 | expect(utils.isDefined(true)).toBe(true);
31 | expect(utils.isDefined(null)).toBe(true);
32 | expect(utils.isDefined(undefined)).toBe(false);
33 | });
34 |
35 | it("utils.isUndefined",()=>{
36 | expect(utils.isUndefined(1)).toBe(false);
37 | expect(utils.isUndefined("")).toBe(false);
38 | expect(utils.isUndefined(true)).toBe(false);
39 | expect(utils.isUndefined(null)).toBe(false);
40 | expect(utils.isUndefined(undefined)).toBe(true);
41 | });
42 |
43 | it("utils.isString",()=>{
44 | expect(utils.isString(1)).toBe(false);
45 | expect(utils.isString("")).toBe(true);
46 | expect(utils.isString(true)).toBe(false);
47 | expect(utils.isString(null)).toBe(false);
48 | expect(utils.isString(undefined)).toBe(false);
49 | });
50 |
51 | it("utils.toInt",()=>{
52 | expect(utils.toInt(1)).toBe(1);
53 | expect(utils.toInt("10")).toBe(10);
54 | expect(utils.toInt("-1")).toBe(-1);
55 | expect(utils.toInt("10.5")).toBe(10);
56 | });
57 |
58 | it("utils.isNaN",()=>{
59 | expect(utils.isNaN(Number.NaN)).toBe(true);
60 | expect(utils.isNaN(1)).toBe(false);
61 | });
62 |
63 | it("utils.isObject",()=>{
64 | expect(utils.isObject({})).toBe(true);
65 | expect(utils.isObject(null)).toBe(false);
66 | expect(utils.isObject(undefined)).toBe(false);
67 | expect(utils.isObject(1)).toBe(false);
68 | expect(utils.isObject(true)).toBe(false);
69 | expect(utils.isObject("")).toBe(false);
70 | });
71 |
72 | it("utils.isBlankObject",()=>{
73 | function test(){
74 |
75 | }
76 | var t = new test();
77 | expect(utils.isBlankObject({})).toBe(false);
78 | expect(utils.isBlankObject(t)).toBe(false);
79 | });
80 |
81 | it("utils.isDate",function () {
82 | expect(utils.isDate(new Date())).toBe(true);
83 | expect(utils.isDate({})).toBe(false);
84 | });
85 |
86 | it("utils.isWindow",function () {
87 | expect(utils.isWindow(window)).toBe(true);
88 | expect(utils.isWindow({})).toBe(false);
89 | })
90 |
91 |
92 | it("utils.lowercase",function () {
93 | expect(utils.lowercase("Aa")).toBe("aa");
94 | })
95 |
96 | it("utils.uppercase",function () {
97 | expect(utils.uppercase("aa")).toBe("AA");
98 | });
99 |
100 | it("utils.copy",function () {
101 | var a = utils.copy({name:{a:1}},{});
102 | expect(a.name.a).toBe(1);
103 | });
104 |
105 |
106 | it("utils.extend",function () {
107 | var a = utils.extend({},{name:1},{b:{k:2}});
108 | expect(a.name).toBe(1);
109 | expect(a.b.k).toBe(2);
110 | });
111 |
112 | it("utils.isBoolean",()=>{
113 | expect(utils.isBoolean(true)).toBe(true);
114 | expect(utils.isBoolean(false)).toBe(true);
115 | expect(utils.isBoolean(undefined)).toBe(false);
116 | expect(utils.isBoolean(1)).toBe(false);
117 | expect(utils.isBoolean("")).toBe(false);
118 | expect(utils.isBoolean(null)).toBe(false);
119 | });
120 |
121 | it("utils.isRegExp",()=>{
122 | expect(utils.isRegExp(/a/)).toBe(true);
123 | expect(utils.isRegExp(false)).toBe(false);
124 | });
125 |
126 | it("utils.toCamelCase",()=>{
127 | expect(utils.toCamelCase("")).toBe("");
128 | expect(utils.toCamelCase("a-b")).toBe("AB");
129 | expect(utils.toCamelCase("ab")).toBe("Ab");
130 | });
131 |
132 | it("utils.equals",()=>{
133 | let o1 : any = {};
134 | let o2 : any = {};
135 | expect(utils.equals(o1,o2)).toBe(true);
136 |
137 | o1.name = 1;
138 | expect(utils.equals(o1,o2)).toBe(false);
139 |
140 | o2.name = 1;
141 | expect(utils.equals(o1,o2)).toBe(true);
142 | })
143 |
144 | it("utils.isEmptyObject",()=>{
145 | expect(utils.isEmptyObject({})).toBe(true);
146 | expect(utils.isEmptyObject({name:1})).toBe(false);
147 | expect(utils.isEmptyObject(null)).toBe(false);
148 | });
149 |
150 | it("utils.getRequestPath",()=>{
151 | expect(utils.getRequestPath("/getlishu")).toBe("/getlishu");
152 | expect(utils.getRequestPath("/getlishu",{name:1})).toBe("/getlishu?name=1");
153 | });
154 | });
155 |
--------------------------------------------------------------------------------
/src/common/utils/Debounce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/14
3 | * 函数节流。
4 | * 例子2,时间一到会强制执行:
5 | * let debounce = new Debounce(100);
6 | * debounce.handle(function(){
7 | * console.log("给力叔");
8 | * })
9 | * 例子1,不会强制执行:
10 | * let debounce = new Debounce(100);
11 | * debounce.handle(function(){
12 | * console.log("给力叔");
13 | * })
14 | */
15 | export class Debounce{
16 | private _timeoutIndex:number; //setTimeout的序号
17 | private _last:number;
18 | private _time:number; //多久执行一次,单位是毫秒
19 | private _force:boolean = false; //是否强制执行
20 | private _handler:Function;
21 | constructor(time:number = 4,force?:boolean){
22 | this._last = + new Date();
23 | this._time = time;
24 | this._force = force;
25 | }
26 |
27 | handle(handler:Function){
28 | let current;
29 | this._handler = handler;
30 | //强制执行
31 | if(this._force && (current = +new Date()) - this._last > this._time){
32 | this._last = current;
33 | handler();
34 | this._last = current;
35 | }
36 | //不管怎么样都会延迟的
37 | this._delayHandle();
38 | }
39 | //延迟执行
40 | private _delayHandle(){
41 | clearTimeout(this._timeoutIndex);
42 | this._timeoutIndex = setTimeout( ()=>{
43 | this._handler();
44 | this.clearTimeout();
45 | },this._time);
46 | }
47 |
48 | clearTimeout(){
49 | clearTimeout(this._timeoutIndex);
50 | this._timeoutIndex = -1;
51 | }
52 |
53 | //直接执行,不要延迟了
54 | doHandle(){
55 | if(this._timeoutIndex != -1){//有延迟执行的情况下,才执行
56 | this._handler && this._handler();
57 | this.clearTimeout();
58 | }
59 | }
60 |
61 | destroy(){
62 | this.clearTimeout();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App.vue'
5 | import router from './router'
6 | import VModal from './modules/editor/common/dialog/index.js'
7 | import BootstrapVue from 'bootstrap-vue'
8 | import 'bootstrap-vue/dist/bootstrap-vue.css'
9 | import 'bootstrap/dist/css/bootstrap.css'
10 | import './modules/editor/common/dragable/Draggable'
11 |
12 | import axios from 'axios';
13 | (Vue.prototype).$axios=axios;
14 |
15 | Vue.use(BootstrapVue);
16 | Vue.use(VModal)
17 | Vue.config.productionTip = false
18 |
19 | /* eslint-disable no-new */
20 | new Vue({
21 | el: '#app',
22 | router,
23 | template: '',
24 | components: { App }
25 | })
26 |
--------------------------------------------------------------------------------
/src/modules/editor/common/button/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{label}}
4 |
5 |
6 |
7 |
8 |
23 |
24 |
65 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/Color.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/29
3 | * 颜色的元数据,这个数据很臃肿
4 | */
5 | export class Color {
6 | hsl: {
7 | h: number,
8 | s: number,
9 | l: number,
10 | a: number
11 | } = {
12 | "h": 255.00000000000006,
13 | "s": 0,
14 | "l": 1,
15 | "a": 1
16 | };
17 |
18 | hex: string = "#FFFFFF";
19 |
20 | rgba: {
21 | r: number,
22 | g: number,
23 | b: number,
24 | a: number
25 | } = {
26 | "r": 255,
27 | "g": 255,
28 | "b": 255,
29 | "a": 1
30 | }
31 |
32 | hsv: {
33 | h: number,
34 | s: number,
35 | v: number,
36 | a: number
37 | } = {
38 | "h": 255.00000000000006,
39 | "s": 0,
40 | "v": 1,
41 | "a": 1
42 | }
43 |
44 | a: number = 1;
45 |
46 | /**
47 | * 转换成CSS支持的rgba颜色样式
48 | * @param {Color} color
49 | * @returns {string}
50 | */
51 | public static toRGBA(color:Color){
52 | let rgba = color.rgba;
53 | return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/ColorPicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
112 |
113 |
132 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
4 | extends: 'standard',
5 | // required to lint *.vue files
6 | plugins: [
7 | 'html'
8 | ],
9 | // add your custom rules here
10 | 'rules': {
11 | // allow paren-less arrow functions
12 | 'arrow-parens': 0,
13 | // allow debugger during development
14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | .idea
5 | example-dist
6 | .vscode
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 greyby
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/README.md:
--------------------------------------------------------------------------------
1 | # vue-color
2 |
3 | [](https://www.npmjs.com/package/vue-color)
4 |
5 | Color Pickers for Sketch, Photoshop, Chrome & more with Vue.js(vue2.0).
6 |
7 | ## [Live demo](http://xiaokaike.github.io/vue-color/)
8 |
9 | 
10 |
11 | ## Installation
12 |
13 | ### NPM
14 | ```bash
15 | $ npm install vue-color
16 | ```
17 |
18 | ### CommonJS
19 | ```js
20 | var Photoshop = require('vue-color/src/Photoshop.vue');
21 |
22 | new Vue({
23 | components: {
24 | 'Photoshop': Photoshop
25 | }
26 | })
27 | ```
28 |
29 | ### ES6
30 | ```js
31 | import { Photoshop } from 'vue-color'
32 |
33 | new Vue({
34 | components: {
35 | 'photoshop-picker': Photoshop
36 | }
37 | })
38 | ```
39 |
40 | ### Browser globals
41 | The `dist` folder contains `vue-color.js` and `vue-color.min.js` with all components exported in the window.VueColor
object. These bundles are also available on NPM packages.
42 |
43 | ```html
44 |
45 |
46 |
49 | ```
50 |
51 | ## Local setup
52 |
53 | ```
54 | npm install
55 | npm run dev
56 | ```
57 |
58 | ## Usage
59 | ```js
60 |
61 | var defaultProps = {
62 | hex: '#194d33',
63 | hsl: {
64 | h: 150,
65 | s: 0.5,
66 | l: 0.2,
67 | a: 1
68 | },
69 | hsv: {
70 | h: 150,
71 | s: 0.66,
72 | v: 0.30,
73 | a: 1
74 | },
75 | rgba: {
76 | r: 25,
77 | g: 77,
78 | b: 51,
79 | a: 1
80 | },
81 | a: 1
82 | }
83 |
84 | new Vue({
85 | el: '#app',
86 | components: {
87 | 'material-picker': material,
88 | 'compact-picker': compact,
89 | 'swatches-picker': swatches,
90 | 'slider-picker': slider,
91 | 'sketch-picker': sketch,
92 | 'chrome-picker': chrome,
93 | 'photoshop-picker': photoshop
94 | },
95 | data: {
96 | colors: defaultProps
97 | }
98 | })
99 |
100 | ```
101 |
102 | ```html
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | ```
112 |
113 | OR
114 |
115 | ```html
116 |
117 | ```
118 |
119 |
120 | ## TODO
121 | -[] docs
122 | -[] more components
123 |
124 |
125 | ## License
126 |
127 | vue-color is licensed under [The MIT License](LICENSE).
128 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/common/colorPicker/vue-color/intro.png
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-color",
3 | "version": "2.4.0",
4 | "description": "Color of Vue Components",
5 | "keywords": [
6 | "color",
7 | "vuejs"
8 | ],
9 | "main": "dist/vue-color.min.js",
10 | "scripts": {
11 | "example": "node build/build.js",
12 | "dev": "node build/dev-server.js",
13 | "gh": "npm run example && node build/deploy",
14 | "lint": "eslint --ext .js,.vue src",
15 | "release": "npm run lint && webpack --progress --hide-modules --config ./build/webpack.release.js && webpack --progress --hide-modules --config ./build/webpack.release.min.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/xiaokaike/vue-color"
20 | },
21 | "author": "xiaokai ",
22 | "dependencies": {
23 | "lodash.throttle": "^4.0.0",
24 | "material-colors": "^1.0.0",
25 | "tinycolor2": "^1.1.2"
26 | },
27 | "devDependencies": {
28 | "babel-core": "^6.22.x",
29 | "babel-loader": "^7.1.0",
30 | "babel-plugin-transform-runtime": "^6.22.0",
31 | "babel-preset-es2015": "^6.0.0",
32 | "babel-preset-stage-2": "^6.22.0",
33 | "chalk": "^2.0.1",
34 | "connect-history-api-fallback": "^1.1.0",
35 | "css-loader": "^0.28.0",
36 | "eslint": "^3.19.0",
37 | "eslint-config-standard": "^6.2.0",
38 | "eslint-friendly-formatter": "^2.0.7",
39 | "eslint-loader": "^1.7.1",
40 | "eslint-plugin-html": "^2.0.0",
41 | "eslint-plugin-promise": "^3.4.2",
42 | "eslint-plugin-standard": "^2.0.1",
43 | "eventsource-polyfill": "^0.9.6",
44 | "express": "^4.14.0",
45 | "extract-text-webpack-plugin": "^3.0.0",
46 | "file-loader": "^0.11.1",
47 | "friendly-errors-webpack-plugin": "^1.6.1",
48 | "gh-pages": "^1.0.0",
49 | "html-webpack-plugin": "^2.28.x",
50 | "http-proxy-middleware": "^0.17.0",
51 | "ora": "^1.2.0",
52 | "shelljs": "^0.7.0",
53 | "url-loader": "^0.5.7",
54 | "vue": "^2.3.3",
55 | "vue-hot-reload-api": "^1.3.x",
56 | "vue-loader": "^13.0.2",
57 | "vue-style-loader": "^3.0.1",
58 | "vue-template-compiler": "^2.3.3",
59 | "webpack": "^3.3.0",
60 | "webpack-dev-middleware": "^1.10.0",
61 | "webpack-hot-middleware": "^2.18.0",
62 | "webpack-merge": "^4.1.0"
63 | },
64 | "license": "MIT"
65 | }
66 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/Compact.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
57 |
58 |
100 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/Material.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
58 |
59 |
102 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/Slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
59 |
60 |
107 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/Swatches.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
75 |
76 |
120 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/common/Alpha.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
85 |
86 |
131 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/common/Checkboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
83 |
84 |
94 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/common/EditableInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | {{label}}
9 | {{desc}}
10 |
11 |
12 |
13 |
89 |
90 |
103 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/common/Hue.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
136 |
137 |
173 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/components/common/Saturation.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
101 |
102 |
133 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/index.js:
--------------------------------------------------------------------------------
1 | import Compact from './components/Compact.vue'
2 | import Material from './components/Material.vue'
3 | import Slider from './components/Slider.vue'
4 | import Swatches from './components/Swatches.vue'
5 | import Photoshop from './components/Photoshop.vue'
6 | import Sketch from './components/Sketch.vue'
7 | import Chrome from './components/Chrome.vue'
8 | import Alpha from './components/common/Alpha.vue'
9 | import Checkboard from './components/common/Checkboard.vue'
10 | import EditableInput from './components/common/EditableInput.vue'
11 | import Hue from './components/common/Hue.vue'
12 | import Saturation from './components/common/Saturation.vue'
13 | import ColorMixin from './mixin/color.js'
14 |
15 | const VueColor = {
16 | version: '2.4.0',
17 | Compact,
18 | Material,
19 | Slider,
20 | Swatches,
21 | Photoshop,
22 | Sketch,
23 | Chrome,
24 | Alpha,
25 | Checkboard,
26 | EditableInput,
27 | Hue,
28 | Saturation,
29 | ColorMixin
30 | }
31 |
32 | module.exports = VueColor
33 |
--------------------------------------------------------------------------------
/src/modules/editor/common/colorPicker/vue-color/src/mixin/color.js:
--------------------------------------------------------------------------------
1 | import tinycolor from 'tinycolor2'
2 |
3 | function _colorChange (data, oldHue) {
4 | var alpha = data && data.a
5 | var color
6 |
7 | // hsl is better than hex between conversions
8 | if (data && data.hsl) {
9 | color = tinycolor(data.hsl)
10 | } else if (data && data.hex && data.hex.length > 0) {
11 | color = tinycolor(data.hex)
12 | } else {
13 | color = tinycolor(data)
14 | }
15 |
16 | if (color && (color._a === undefined || color._a === null)) {
17 | color.setAlpha(alpha || 1)
18 | }
19 |
20 | var hsl = color.toHsl()
21 | var hsv = color.toHsv()
22 |
23 | if (hsl.s === 0) {
24 | hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0
25 | }
26 |
27 | // when the hsv.v is less than 0.0164 (base on test)
28 | // because of possible loss of precision
29 | // the result of hue and saturation would be miscalculated
30 | if (hsv.v < 0.0164) {
31 | hsv.h = data.h || (data.hsv && data.hsv.h) || 0
32 | hsv.s = data.s || (data.hsv && data.hsv.s) || 0
33 | }
34 |
35 | if (hsl.l < 0.01) {
36 | hsl.h = data.h || (data.hsl && data.hsl.h) || 0
37 | hsl.s = data.s || (data.hsl && data.hsl.s) || 0
38 | }
39 |
40 | return {
41 | hsl: hsl,
42 | hex: color.toHexString().toUpperCase(),
43 | rgba: color.toRgb(),
44 | hsv: hsv,
45 | oldHue: data.h || oldHue || hsl.h,
46 | source: data.source,
47 | a: data.a || color.getAlpha()
48 | }
49 | }
50 |
51 | export default {
52 | props: ['value'],
53 | data () {
54 | return {
55 | val: _colorChange(this.value)
56 | }
57 | },
58 | computed: {
59 | colors: {
60 | get () {
61 | return this.val
62 | },
63 | set (newVal) {
64 | this.val = newVal
65 | this.$emit('input', newVal)
66 | }
67 | }
68 | },
69 | watch: {
70 | value (newVal) {
71 | this.val = _colorChange(newVal)
72 | }
73 | },
74 | methods: {
75 | colorChange (data, oldHue) {
76 | this.oldHue = this.colors.hsl.h
77 | this.colors = _colorChange(data, oldHue || this.oldHue)
78 | },
79 | isValidHex (hex) {
80 | return tinycolor(hex).isValid()
81 | },
82 | simpleCheckForValidColor (data) {
83 | var keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v']
84 | var checked = 0
85 | var passed = 0
86 |
87 | for (var i = 0; i < keysToCheck.length; i++) {
88 | var letter = keysToCheck[i]
89 | if (data[letter]) {
90 | checked++
91 | if (!isNaN(data[letter])) {
92 | passed++
93 | }
94 | }
95 | }
96 |
97 | if (checked === passed) {
98 | return data
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dialog/Dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
87 |
157 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dialog/Resizer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
110 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dialog/index.js:
--------------------------------------------------------------------------------
1 | import Modal from './Modal.vue'
2 | import Dialog from './Dialog.vue'
3 |
4 | const defaultComponentName = 'modal'
5 |
6 | const Plugin = {
7 | install (Vue, options = {}) {
8 | /**
9 | * Makes sure that plugin can be insstalled only once
10 | */
11 | if (this.installed) {
12 | return
13 | }
14 |
15 | this.installed = true
16 | this.event = new Vue()
17 | /**
18 | * Plugin API
19 | */
20 | Vue.prototype.$modal = {
21 | show (name, params) {
22 | Plugin.event.$emit('toggle', name, true, params)
23 | },
24 |
25 | hide (name, params) {
26 | Plugin.event.$emit('toggle', name, false, params)
27 | },
28 |
29 | toggle (name, params) {
30 | Plugin.event.$emit('toggle', name, undefined, params)
31 | }
32 | }
33 | /**
34 | * Sets custom component name (if provided)
35 | */
36 | const componentName = options.componentName || defaultComponentName
37 | Vue.component(componentName, Modal)
38 | /**
39 | * Registration of component
40 | */
41 | if (options.dialog) {
42 | Vue.component('v-dialog', Dialog)
43 | }
44 | }
45 | }
46 |
47 | export default Plugin
48 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dialog/parser.js:
--------------------------------------------------------------------------------
1 | const floatRegexp = '[-+]?[0-9]*.?[0-9]+'
2 |
3 | const types = [
4 | {
5 | name: 'px',
6 | regexp: new RegExp(`^${floatRegexp}px$`)
7 | },
8 | {
9 | name: '%',
10 | regexp: new RegExp(`^${floatRegexp}%$`)
11 | },
12 | /**
13 | * Fallback optopn
14 | * If no suffix specified, assigning "px"
15 | */
16 | {
17 | name: 'px',
18 | regexp: new RegExp(`^${floatRegexp}$`)
19 | }
20 | ]
21 |
22 | var getType = (value) => {
23 | if (value === 'auto') {
24 | return {
25 | type: value,
26 | value: 0
27 | }
28 | }
29 |
30 | for (var i = 0; i < types.length; i++) {
31 | let type = types[i]
32 | if (type.regexp.test(value)) {
33 | return {
34 | type: type.name,
35 | value: parseFloat(value)
36 | }
37 | }
38 | }
39 |
40 | return {
41 | type: '',
42 | value: value
43 | }
44 | }
45 |
46 | export const parse = (value) => {
47 | switch (typeof value) {
48 | case 'number':
49 | return { type: 'px', value }
50 | case 'string':
51 | return getType(value)
52 | default:
53 | return { type: '', value }
54 | }
55 | }
56 |
57 | export default parse
58 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dialog/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @param {Number} from Lower limit
4 | * @param {Number} to Upper limit
5 | * @param {Number} value Checked number value
6 | *
7 | * @return {Number} Either source value itself or limit value if range limits
8 | * are exceeded
9 | */
10 | export const inRange = (from, to, value) => {
11 | if (value < from) {
12 | return from
13 | }
14 |
15 | if (value > to) {
16 | return to
17 | }
18 |
19 | return value
20 | // lol
21 | // return value < from ? from : (value > to ? to : value)
22 | }
23 |
24 | export default { inRange }
25 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dragable/Draggable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/27
3 | * 实现拖拽的指令
4 | * todo 此类其实可以继续封装成完全通用的
5 | */
6 | import Vue from 'vue'
7 | import {IDragableOptions} from "../interact/IDragableOptions";
8 | import {Draggable} from "../interact/Draggable";
9 |
10 | export default Vue.directive('draggable', {
11 | inserted: function (el, binding) {
12 | let value: IDragableOptions = binding.value || {};
13 | value.target = el;
14 | (binding).instance = new Draggable(binding.value);
15 | },
16 | unbind(el, binding) {
17 | binding && (binding).instance && (binding).instance.destroy();
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/src/modules/editor/common/dragable/IDraggableResult.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/27
3 | */
4 | import {Draggable} from "../interact/Draggable";
5 |
6 | export interface IDraggableResult {
7 | event: Event;
8 | position: { x: number, y: number };
9 | draggable: Draggable;
10 | }
11 |
--------------------------------------------------------------------------------
/src/modules/editor/common/interact/Draggable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/27
3 | * 封装拖拽的业务方法
4 | */
5 | import {IDragableOptions} from "./IDragableOptions";
6 | import {utils} from "../../../../common/utils";
7 | import interact from 'interactjs'
8 | import {IDraggableResult} from "../dragable/IDraggableResult";
9 |
10 | export class Draggable {
11 | _options: IDragableOptions;
12 | _interact: any;
13 | _currentX = 0;
14 | _currentY = 0;
15 | _isDragging: boolean = false;
16 |
17 | constructor(options: IDragableOptions) {
18 | options = options || {} as any;
19 | this._options = options;
20 |
21 | let allowForm = options.allowForm ? utils.isString(options.allowForm) ? "#" + options.allowForm : options.allowForm : undefined,
22 | el = options.target,
23 | autoScroll = options.autoScroll;
24 | this._interact = interact(el, {
25 | allowForm: allowForm,
26 | autoScroll: autoScroll
27 | })
28 | .draggable({
29 | // 保持组件在父节点里面
30 | restrict: {
31 | restriction: "parent",
32 | elementRect: options.elementRect
33 | },
34 | onmove: (event) => {
35 | if (options.customMove) {
36 | options.customMove(event, this);
37 | } else {
38 | // 算出拖拽位置
39 | var
40 | x = this._currentX + event.dx,
41 | y = this._currentY + event.dy;
42 |
43 | this.setPosition(x, y);
44 | }
45 | options.onMove && options.onMove(this.getDragResult(event));
46 | },
47 | onend: (event) => {
48 | this._isDragging = false;
49 | options.onEnd && options.onEnd(this.getDragResult(event));
50 | },
51 | onstart: (event) => {
52 | this._isDragging = true;
53 | options.onStart && options.onStart(this.getDragResult(event));
54 | }
55 | });
56 | }
57 |
58 | get target() {
59 | return this._options.target;
60 | }
61 |
62 | get currentX() {
63 | return this._currentX;
64 | }
65 |
66 | get currentY() {
67 | return this._currentY;
68 | }
69 |
70 | /**
71 | * 是不是正在拖拽中啊
72 | * @returns {boolean}
73 | */
74 | get isDragging(): boolean {
75 | return this._isDragging;
76 | }
77 |
78 | getDragResult(event): IDraggableResult {
79 | return {event: event, position: {x: this._currentX, y: this._currentY}, draggable: this}
80 | }
81 |
82 | setPosition(x: number, y: number, offsetX: number = 0, offsetY: number = 0) {
83 | let target = this._options.target;
84 | // 更新元素的位置
85 | target.style.webkitTransform =
86 | target.style.transform =
87 | 'translate(' + (x + offsetX) + 'px, ' + (y + offsetY) + 'px)';
88 |
89 | // 记录位置,不记录就拖拽不了
90 | this._currentX = x;
91 | this._currentY = y;
92 | }
93 |
94 | getPosition() {
95 | return {x: this._currentX, y: this._currentY};
96 | }
97 |
98 |
99 | destroy() {
100 | this._interact.unset();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/modules/editor/common/interact/IDragableOptions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/27
3 | */
4 | import {IDraggableResult} from "../dragable/IDraggableResult";
5 | import {Draggable} from "./Draggable";
6 |
7 | export interface IDragableOptions{
8 | allowForm?:any,//就是点击哪里才能触发拖拽,但是无效,奇葩,demo里面又有效 todo 阅读interactjs的源码
9 | elementRect?:{top,right,bottom,left},
10 | onMove?:(result:IDraggableResult)=>void;
11 | onEnd?:(result:IDraggableResult)=>void;
12 | onStart?:(result:IDraggableResult)=>void;
13 | customMove?:(event,dragabble:Draggable)=>void;
14 | target:any;
15 | autoScroll?:boolean;
16 | }
17 |
--------------------------------------------------------------------------------
/src/modules/editor/common/overlayPanel/OverlayPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
85 |
86 |
95 |
--------------------------------------------------------------------------------
/src/modules/editor/common/panel/Panel.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
44 |
45 |
72 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_background.scss:
--------------------------------------------------------------------------------
1 | .ui-background-white{
2 | background-color: white;
3 | }
4 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_border-radius.scss:
--------------------------------------------------------------------------------
1 | //常用的圆角类
2 | .ui-border-radius-0{
3 | border-radius: 0!important;
4 | }
5 |
6 | .ui-border-radius-3{
7 | border-radius: 3px!important;
8 | }
9 |
10 | .ui-border-radius-5{
11 | border-radius: 5px!important;
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_border.scss:
--------------------------------------------------------------------------------
1 | .ui-no-border {
2 | border: none !important;
3 | }
4 |
5 | $gray_border: 1px solid rgba(0, 0, 0, 0.125);
6 |
7 | .ui-border-top-gray {
8 | border-top: $gray_border;
9 | }
10 |
11 | .ui-border-bottom-gray {
12 | border-bottom: $gray_border;
13 | }
14 |
15 | .ui-border-right-gray {
16 | border-right: $gray_border;
17 | }
18 |
19 | .ui-border-left-gray {
20 | border-left: $gray_border;
21 | }
22 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_cursor.scss:
--------------------------------------------------------------------------------
1 | .ui-cursor-pointer{
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_float.scss:
--------------------------------------------------------------------------------
1 | //一些常用的浮动样式
2 | .ui-float-left {
3 | float: left;
4 | }
5 |
6 | .ui-float-right {
7 | float: right;
8 | }
9 |
10 | .ui-clear-both {
11 | clear: both;
12 | }
13 |
14 | .ui-clear-left {
15 | clear: left;
16 | }
17 |
18 | .ui-clear-right {
19 | clear: right;
20 | }
21 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_input.scss:
--------------------------------------------------------------------------------
1 | @import "_transition.scss";
2 | input{
3 | padding: 6px;
4 | outline: none!important;
5 | border: 1px solid #e6ebed;
6 | @include uiTransitionAll;
7 | box-sizing: border-box;
8 | border-radius: 3px;
9 | &:focus{
10 | border-color: #49C7F9;
11 | }
12 | }
13 |
14 | textarea{
15 | @extend input;
16 | resize: none;//多行文本的
17 | height:50px;
18 | }
19 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_padding.scss:
--------------------------------------------------------------------------------
1 | .no-padding{
2 | padding: 0;
3 | }
4 |
5 | .ui-padding-md{
6 | padding: 10px;
7 | }
8 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_shadow.scss:
--------------------------------------------------------------------------------
1 | @mixin grayShadow(){
2 | box-shadow: 2px 2px 12px 4px rgba(50,50,101,.3);
3 | }
4 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_size.scss:
--------------------------------------------------------------------------------
1 | .full-width{
2 | width:100%;
3 | }
4 |
5 | .full-height{
6 | height: 100%;
7 | }
8 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_skin.scss:
--------------------------------------------------------------------------------
1 | //分隔条的颜色
2 | $devide_color: #CDCFD0;
3 | //鼠标滑过的时候,文本的颜色
4 | $hover_color: #59C7F9;
5 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_transition.scss:
--------------------------------------------------------------------------------
1 | @mixin uiTransitionAll(){
2 | transition: all .3s linear 0s
3 | }
4 | .ui-transition-all{
5 | @include uiTransitionAll;
6 | }
7 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/_variable.scss:
--------------------------------------------------------------------------------
1 | $header_height:50px;
2 |
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/icons/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/common/scss/icons/iconfont.eot
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/icons/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/common/scss/icons/iconfont.ttf
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/icons/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/common/scss/icons/iconfont.woff
--------------------------------------------------------------------------------
/src/modules/editor/common/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import "_skin.scss";
2 | @import "_variable.scss";
3 | @import "_transition.scss";
4 | @import "_shadow.scss";
5 | @import "_border-radius.scss";
6 | @import "_float.scss";
7 | @import "_input.scss";
8 | @import "_padding.scss";
9 | @import "_size.scss";
10 | @import "_border.scss";
11 | @import "_background.scss";
12 | @import "_cursor.scss";
13 | @import "_animate.scss";
14 |
--------------------------------------------------------------------------------
/src/modules/editor/common/slider/Slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
141 |
142 |
176 |
--------------------------------------------------------------------------------
/src/modules/editor/common/stepper/Stepper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
59 |
60 |
65 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/ComponentEditorEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/12/012.
3 | */
4 | export class ComponentEditorEvent{
5 | public static showResourceDialog = "showResourceDialog"; //显示图片素材窗口
6 | }
7 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/componentBar/ComponentBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
53 |
54 |
59 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/componentBar/IconLabel.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
24 |
25 |
45 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/header/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Editor
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
42 |
43 |
85 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/multiSelectEditor/MultiSelectEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
62 |
63 |
93 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/SceneEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
86 |
87 |
177 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/componentEditor/sceneEditor/header.png
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/WorkSpaceEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/12/012.
3 | * 工作空间的事件
4 | */
5 | export class WorkSpaceEvent{
6 | public static componentSelectUpdate = 'componentSelectUpdate'; //让‘组件选择’组件更新大小的事件
7 | }
8 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/assets/rotate-cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/src/modules/editor/componentEditor/sceneEditor/workspace/assets/rotate-cursor.png
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/displayComponentEditor/DisplayComponentEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
52 |
53 |
58 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/displayComponentEditor/DisplayComponentSelectClasses.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/13
3 | * DisplayComponentEditor里面各个元素的类名
4 | */
5 | export const GLS_RESIZE_POINT = 'gls-resize-point';
6 | export const GLS_ROTATE_POINT = 'gls-rotate-point';
7 |
8 | export const N_RESIZE = 'n-resize';
9 | export const NE_RESIZE = 'ne-resize';
10 | export const E_RESIZE = 'e-resize';
11 | export const SE_RESIZE = 'se-resize';
12 | export const S_RESIZE = 's-resize';
13 | export const SW_RESIZE = 'sw-resize';
14 | export const W_RESIZE = 'w-resize';
15 | export const NW_RESIZE = 'nw-resize';
16 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/image/ComponentImageDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
29 |
30 |
36 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/image/ComponentImageEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
28 |
29 |
31 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/resourceManager/ResourceManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 图片库
7 | 我的购买
8 | 我的上传
9 | 最近使用
10 |
11 |
12 |
13 |
47 |
48 |
49 |
50 |
82 |
83 |
201 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/resourceManager/ResourceManagerEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/12/012.
3 | */
4 | export class ResourceManagerEvent{
5 | public static imageSelected:string = "imageSelected"; //图片被选中了
6 | }
7 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/stage/ComponentStageDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
54 |
55 |
61 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/stage/ComponentStageEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
45 |
46 |
48 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/text/ComponentTextDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
36 |
37 |
40 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneEditor/workspace/text/ComponentTextEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
41 |
42 |
44 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/sceneManager/SceneNameEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{scene.props.name}}
4 |
6 |
7 |
8 |
9 |
35 |
36 |
38 |
--------------------------------------------------------------------------------
/src/modules/editor/componentEditor/templatePanel/TemplatePanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
18 |
19 |
60 |
--------------------------------------------------------------------------------
/src/modules/editor/core/device/Devices.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/11
3 | * ES6居然不支持枚举,本来这里应该是枚举才对,被ts毒害了
4 | *
5 | * 记录样式支持的平台,暂时只有PC,这个编辑器以后支持FLEX布局等,什么页面都能做
6 | */
7 | export enum Devices{
8 | PC
9 | }
10 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/DisplayComponent.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/10/3
3 | **/
4 | import {DisplayComponent} from "./DisplayComponent";
5 | import {AnimationConfig} from "./config/AnimationConfig";
6 |
7 | describe('DisplayComponent', () => {
8 | document.body.innerHTML = ''
9 | let displayComponent = new DisplayComponent();
10 | displayComponent.element = document.getElementById("test");
11 | it('#isAnimationConfigValid', () => {
12 | let animationConfig = new AnimationConfig();
13 | expect(displayComponent.isAnimationConfigValid(animationConfig)).toBeFalsy();
14 | animationConfig.animationType = "bounce";
15 | animationConfig.duration = 3;
16 | expect(displayComponent.isAnimationConfigValid(animationConfig)).toBeTruthy();
17 | });
18 |
19 | it("#playNext",function () {
20 | let animationConfig = new AnimationConfig();
21 | animationConfig.animationType = "bounce";
22 | animationConfig.duration = 3;
23 | displayComponent.props.animationConfigs.push(animationConfig);
24 | expect(displayComponent.playNext()).toBeTruthy();
25 | })
26 | });
27 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/DisplayComponent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {Properties} from "./property/Properties";
5 | import {utils} from "../../../../common/utils";
6 | import {IStyle} from "./style/IStyle";
7 | import {Devices} from "../device/Devices";
8 | import {AnimationConfig} from "./config/AnimationConfig";
9 | import {Scene} from "../scene/Scene";
10 | import {EventDispatcher} from "../../../../common/event/EventDispatcher";
11 | import {ICloneable} from "./ICloneable";
12 | import {DisplayComponentFactory} from "../factorys/display/DisplayComponentFactory";
13 | import {Project} from "../project/Project";
14 |
15 | export class DisplayComponent extends EventDispatcher implements ICloneable {
16 | clone(project:Project): DisplayComponent {
17 | let clone:DisplayComponent = DisplayComponentFactory.getInstance().createComponent(project,this.props.type);
18 | clone.initFromComponent(this);
19 | clone.style.left += 5;
20 | clone.style.top += 5;
21 | return clone;
22 | }
23 |
24 | /**
25 | * 组件的所有属性,这个属性最终会保存在服务器,其他属性都不会保存在服务器.
26 | * @type {Properties}
27 | */
28 | props = new Properties();
29 | parent = null;
30 | element: HTMLElement;
31 | playingIndex = 0; //动画有多个,标记动画播放的位置
32 | playingTimeout: number = -1; //每个动画播放,使用setTimeout
33 | scene: Scene = null; //组件所属的场景,动态编译的时候会补上这个属性
34 |
35 | /**
36 | * 渲染出组件的位置,大小,角度
37 | */
38 | renderBounding() {
39 | this.element.style.cssText = this.boundingStyle;
40 | }
41 |
42 | //组件生命钩子之大小变化
43 | onResize() {
44 |
45 | }
46 |
47 | //组件生命钩子之旋转
48 | onRotate() {
49 |
50 | }
51 |
52 | /**
53 | * 从另外一个组件进行初始化
54 | * @param {DisplayComponent} target
55 | */
56 | initFromComponent(target: DisplayComponent) {
57 | let props: Properties = utils.copy(target.props);
58 | props.id = this.props.id;
59 | Object.assign(this.props, props);
60 | this.props.selected = true;
61 | }
62 |
63 | /**
64 | * 获取组件的样式,todo 目前只支持PC样式
65 | */
66 | get style(): IStyle {
67 | return this.props.style;
68 | }
69 |
70 | set style(value) {
71 | this.props.style = value;
72 | }
73 |
74 | /**
75 | * 获取大小位置,角度的样式
76 | * @returns {string}
77 | */
78 | get boundingStyle(): string {
79 | let style = this.style;
80 | let resetTranslate = " translate3d(0,0,0)";
81 | return `
82 | position:absolute;
83 | top:${style.top}px;
84 | left:${style.left}px;
85 | width:${style.width}px;
86 | height:${style.height}px;
87 | transform:rotateZ(${style.rotate}deg)${resetTranslate};
88 | -ms-transform:rotateZ(${style.rotate}deg)${resetTranslate};
89 | -moz-transform:rotateZ(${style.rotate}deg)${resetTranslate};
90 | -webkit-transform:rotateZ(${style.rotate}deg)${resetTranslate};
91 | -o-transform:rotateZ(${style.rotate}deg)${resetTranslate};
92 | `;
93 | }
94 |
95 | get componentSelectStyle(): string {
96 | let style = this.style;
97 | return `
98 | position:absolute;
99 | top:0px;
100 | left:0px;
101 | width:${style.width}px;
102 | height:${style.height}px;
103 | `;
104 | }
105 |
106 | /**
107 | * 播放指定的动画
108 | * @param {number} duration
109 | * @param {number} delay
110 | * @param {number} time
111 | */
112 | animate(animationType: string, duration: number, delay: number, time: number = 1, loop: boolean = false) {
113 | let animationStyle = `${animationType} ${duration}s ease ${delay}s ${loop ? 'infinite' : time} normal both`;
114 | this.element.style.webkitAnimation = animationStyle
115 | this.element.style.animation = animationStyle;
116 | }
117 |
118 | /**
119 | * 停止播放动画
120 | */
121 | stop() {
122 | this.element.style.webkitAnimation = "";
123 | this.element.style.animation = "";
124 | this.playingIndex = 0;
125 | clearTimeout(this.playingTimeout);
126 | }
127 |
128 | /**
129 | * 开始播放动画
130 | */
131 | play() {
132 | this.stop();
133 | this.playNext();
134 | }
135 |
136 | /***
137 | * 播放下一个动画
138 | * @returns {boolean}
139 | */
140 | playNext() {
141 | let animationConfigs = this.props.animationConfigs;
142 | for (; this.playingIndex < this.props.animationConfigs.length; this.playingIndex++) {
143 | let animationConfig = animationConfigs[this.playingIndex];
144 | if (this.isAnimationConfigValid(animationConfig)) {
145 | this.animate(animationConfig.animationType, animationConfig.duration, animationConfig.delay, animationConfig.time, animationConfig.loop);
146 | if (!animationConfig.loop) {
147 | clearTimeout(this.playingTimeout);
148 | this.playingTimeout = setTimeout(() => {
149 | this.playNext();
150 | }, (animationConfig.duration + animationConfig.delay) * 1000 * animationConfig.time);
151 | }
152 | this.playingIndex++;
153 | return true;
154 | }
155 | }
156 | return false;
157 | }
158 |
159 | /**
160 | * 是不是有效的动画
161 | * @param {AnimationConfig} animationConfg
162 | * @returns {string | number}
163 | */
164 | isAnimationConfigValid(animationConfg: AnimationConfig) {
165 | if (animationConfg) {
166 | return animationConfg.animationType && animationConfg.duration && animationConfg.delay >= 0;
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/DisplayComponentContainer.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponent} from "./DisplayComponent";
5 | import {DisplayComponentContainer} from "./DisplayComponentContainer";
6 |
7 | describe('DisplayComponentContainer', () => {
8 | let child = new DisplayComponent();
9 | let parent = new DisplayComponentContainer();
10 | describe("#addChild", () => {
11 | parent.addChild(child);
12 | it("不能添加自己为子节点", function () {
13 | expect(function () {
14 | parent.addChild(parent)
15 | }).toThrow();
16 | })
17 |
18 | it("不能重复添加", function () {
19 | expect(function () {
20 | parent.addChild(child)
21 | }).toThrow();
22 | })
23 | })
24 |
25 | describe("#getChildAt", function () {
26 | it("正常获取子节点", function () {
27 | expect(parent.getChildAt(0)).toEqual(child);
28 | })
29 | it("不能超出范围", function () {
30 | expect(function (value) {
31 | parent.getChildAt(10);
32 | }).toThrow();
33 | expect(function () {
34 | parent.getChildAt(-1);
35 | }).toThrow();
36 | })
37 | })
38 |
39 | describe("#hasChild", function () {
40 | it('包含child', () => {
41 | expect(parent.hasChild(child)).toEqual(true);
42 | })
43 | })
44 |
45 | describe("#numChildrens", function () {
46 | it("子节点数为1", () => {
47 | expect(parent.numChildrens).toEqual(1);
48 | });
49 | })
50 |
51 | describe("#removeChild", function () {
52 | it("移除子节点成功,返回true", function () {
53 | expect(parent.removeChild(child)).toEqual(true);
54 | })
55 |
56 | it("子节点数为0", function () {
57 | expect(parent.numChildrens).toEqual(0);
58 | })
59 |
60 | it("再次移除子节点,返回false", function () {
61 | expect(parent.removeChild(child)).toEqual(false);
62 | })
63 |
64 | it("参数child不能为空", function () {
65 | expect(function () {
66 | parent.removeChild(null)
67 | }).toThrow();
68 | })
69 | })
70 | describe("#addChildAt", function () {
71 | it("参数index不能小于0", function () {
72 | expect(function () {
73 | parent.addChildAt(child, -1)
74 | }).toThrow();
75 | })
76 | it("参数index不能超过最大值", function () {
77 | expect(function () {
78 | parent.addChildAt(child, this.numChildrens + 1)
79 | }).toThrow();
80 | })
81 |
82 | it("正常添加子节点", function () {
83 | expect(parent.addChildAt(child, 0)).toEqual(true);
84 |
85 | expect(parent.hasChild(child)).toEqual(true);
86 | })
87 | })
88 | })
89 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/DisplayComponentContainer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponent} from "./DisplayComponent";
5 | export class DisplayComponentContainer extends DisplayComponent {
6 | /**
7 | * 存储所有的子节点
8 | * @type {Array}
9 | */
10 | children = [];
11 | addChild(child){
12 | return this.addChildAt(child,this.numChildrens);
13 | }
14 |
15 | /**
16 | * 获取子节点的总数
17 | * @return {Number}
18 | */
19 | get numChildrens(){
20 | return this.children.length;
21 | }
22 |
23 | /**
24 | * 在指定的位置添加子节点
25 | * @param child {DisplayComponent}
26 | * @param index {number}
27 | * @return {boolean}
28 | */
29 | addChildAt(child,index){
30 | if(index < 0 || index > this.numChildrens){
31 | throw new RangeError("添加的位置超出范围");
32 | }
33 |
34 | if(child == this){
35 | throw new Error("不能添加自己为子节点");
36 | }
37 |
38 | this.checkChild(child);
39 |
40 | if(!this.hasChild(child)){
41 | this.children.splice(index,0,child);
42 | child.parent = this;
43 | return true;
44 | }else{
45 | throw new Error("子节点已经包含在该容器中");
46 | }
47 | }
48 |
49 | /**
50 | * 移除子节点
51 | * @param child
52 | * @return {boolean}
53 | */
54 | removeChild(child){
55 | this.checkChild(child);
56 | if(this.hasChild(child)){
57 | delete child.parent;
58 | this.children.splice(this.getChildIndex(child),1);
59 | return true;
60 | }else{
61 | return false;
62 | }
63 | }
64 |
65 | /**
66 | * 获取子节点的序号
67 | * @param {DisplayComponent} child
68 | * @returns {number}
69 | */
70 | getChildIndex(child:DisplayComponent){
71 | return this.children.indexOf(child);
72 | }
73 |
74 | /**
75 | * 检查是否是支持的子节点
76 | * @param child {DisplayComponent}
77 | */
78 | checkChild(child){
79 | if(!(child instanceof DisplayComponent)){
80 | throw new Error("只支持DisplayComponent");
81 | }
82 | }
83 |
84 | /**
85 | * 获取指定位置的子节点
86 | * @param index
87 | * @return {DisplayComponent}
88 | */
89 | getChildAt(index){
90 | if(index < 0 || index >= this.children.length){
91 | throw new Error("获取的位置超出范围");
92 | }
93 | return this.children[index];
94 | }
95 |
96 | /**
97 | * 判断是否包含指定的子节点
98 | * @param child
99 | * @return {DisplayComponent}
100 | */
101 | hasChild(child){
102 | this.checkChild(child);
103 | return this.children.includes(child);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/ICloneable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/19/019.
3 | * 原型模式,超级简单的一个模式
4 | */
5 | import {Project} from "../project/Project";
6 |
7 | export interface ICloneable{
8 | clone:(project:Project)=>T;
9 | }
10 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/config/AnimationConfig.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | * 动画的配置
4 | */
5 | export class AnimationConfig {
6 | animationType: string; //播放哪个动画啊
7 | duration: number = 1; //播放的时间长度,单位是秒
8 | delay: number = 0; //延迟多久播放啊,单位是秒
9 | time: number = 1; //播放次数
10 | loop: boolean = false; //循环播放吗
11 | }
12 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/image/ComponentImage.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponent} from "../DisplayComponent";
5 | import {Properties} from "../property/Properties";
6 | import {utils} from "../../../../../common/utils";
7 | import {GlsEvent} from "../../../../../common/event/GlsEvent";
8 |
9 | export const GLS_COMPONENT_IMAGE = 'gls-component-image';
10 |
11 | export class ComponentImageProperties extends Properties {
12 | src = ""; //图片的路径
13 | originalWidth: number = -1; //图片原本的宽度
14 | originalHeight: number = -1; //图片原本的高度
15 | hasGetImageSize: string = ""; //是不是获取过图片的大小
16 | width: number = 100;
17 | height: number = 100;
18 | }
19 |
20 | export class ComponentImage extends DisplayComponent {
21 | props = new ComponentImageProperties();
22 |
23 | constructor() {
24 | super();
25 | this.style = {
26 | left: 0,
27 | top: 0,
28 | width: 100,
29 | height: 100,
30 | rotate: 0
31 | }
32 | }
33 |
34 | //这里跟易企秀的不一样,感觉这样的图片好用点,算法是让图片永远不超框
35 | onResize() {
36 | super.onResize();
37 | this.caculateImageSize();
38 | }
39 |
40 | caculateImageSize() {
41 | let props = this.props,
42 | style = this.style;
43 | if (props.originalWidth == -1) {
44 | props.width = style.width;
45 | props.height = style.height;
46 | } else {
47 | let radio = style.width / style.height;
48 | let originalRadio = props.originalWidth / props.originalHeight;
49 | let width, height;
50 | if (radio > originalRadio) {
51 | height = style.height;
52 | width = height * originalRadio;
53 | } else {
54 | width = style.width;
55 | height = style.width / originalRadio;
56 | }
57 | props.width = width;
58 | props.height = height;
59 | }
60 | }
61 |
62 | get src() {
63 | return this.props.src;
64 | }
65 |
66 | set src(value) {
67 | let props = this.props;
68 | if (value != props.src && value) {
69 | utils.getImageSize(value).then((image: any) => {
70 | if (props.hasGetImageSize != value) {
71 | props.originalWidth = image.width;
72 | props.originalHeight = image.height;
73 |
74 | this.style.width = Math.max(this.style.width, 150);
75 | this.style.height = this.style.width / props.originalWidth * props.originalHeight;
76 |
77 | this.caculateImageSize();
78 |
79 | this.renderBounding();
80 |
81 | props.hasGetImageSize = value;
82 |
83 | this.dispatchEvent(new GlsEvent(GlsEvent.LOADED, this));
84 | }
85 | })
86 | }
87 | props.src = value;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/property/Properties.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | * 组件的属性,最终会存储在服务器里,不要在这个类里面增加任何方法,因为这个类最终会被JSON数据覆盖掉
4 | */
5 | import {Devices} from "../../device/Devices";
6 | import {IStyle} from "../style/IStyle";
7 | import {AnimationConfig} from "../config/AnimationConfig";
8 |
9 | export class Properties {
10 | id = ""; //id
11 | alias = ""; //别称
12 | type = ""; //类型
13 | selected = false; //是不是选中了
14 | style = {} as IStyle; //样式
15 | animationConfigs: Array = []; //动画的配置
16 | resizeable:boolean = true; //可以放大缩小吗
17 | moveable:boolean = true; //可以移动吗
18 | rotateable:boolean = true; //可以旋转吗
19 | sceneId:number; //这个场景id从组件创建的时候就已经确定,千万不要改动它
20 | copyable:boolean = true; //可以拷贝吗
21 | }
22 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/stage/Stage.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | * 每个场景一个舞台
4 | */
5 | import {DisplayComponentContainer} from "../DisplayComponentContainer";
6 | import {Properties} from "../property/Properties";
7 |
8 | export const GLS_COMPONENT_STAGE = 'gls-component-stage';
9 |
10 | export class ComponentStageProperties extends Properties {
11 | selectAble = false;
12 | moveable = false;
13 | resizeable = false;
14 | rotateable = false;
15 | copyable = false;
16 | }
17 |
18 | export class Stage extends DisplayComponentContainer {
19 | props = new ComponentStageProperties();
20 |
21 | constructor() {
22 | super();
23 | this.style = {
24 | top: 0,
25 | left: 0,
26 | width: 320,
27 | height: 486,
28 | rotate: 0,
29 | backgroundImage:""
30 | }
31 |
32 | this.props.resizeable = false;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/style/IStyle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/14.
3 | * 设定组件支持的样式
4 | */
5 | import {Color} from "../../../common/colorPicker/Color";
6 |
7 | type StyleValue = string & number;
8 | export interface IStyle{
9 | width:number;
10 | top:number;
11 | left:number;
12 | height:number;
13 | rotate:number;
14 | color?:Color;
15 | lineHeight?:number;
16 | backgroundImage?:string;
17 | }
18 |
--------------------------------------------------------------------------------
/src/modules/editor/core/display/text/ComponentText.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponent} from "../DisplayComponent";
5 | import {Properties} from "../property/Properties";
6 | export const GLS_COMPONENT_TEXT = 'gls-component-text';
7 |
8 | /**
9 | * 文本的属性
10 | */
11 | export class ComponentTextProperties extends Properties{
12 | type = GLS_COMPONENT_TEXT;
13 | html = `双击此处进行编辑`;
14 | }
15 |
16 | export class ComponentText extends DisplayComponent {
17 | /**
18 | * ComponentTextProperties
19 | * @type {Properties & ComponentTextProperties}
20 | */
21 | props = new ComponentTextProperties();
22 |
23 | constructor(){
24 | super();
25 | this.style = {
26 | left:0,
27 | top:0,
28 | width:200,
29 | height:38,
30 | rotate:0,
31 | color:null,
32 | lineHeight:1
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/modules/editor/core/factorys/display/DisplayComponentFactory.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponentFactory} from "./DisplayComponentFactory";
5 | import {ComponentText,GLS_COMPONENT_TEXT} from "../../display/text/ComponentText";
6 | import {Project} from "../../project/Project";
7 | describe('DisplayComponentFactory', () => {
8 | let project = new Project();
9 | let instance = DisplayComponentFactory.getInstance();
10 | describe("#heckType",function () {
11 | it("参数类型必须是有效的字符串",()=>{
12 | expect(function () {
13 | instance.checkType(null);
14 | }).toThrow();
15 |
16 | expect(function () {
17 | instance.checkType(undefined);
18 | }).toThrow();
19 |
20 | expect(function () {
21 | instance.checkType(0);
22 | }).toThrow();
23 |
24 | expect(function () {
25 | instance.checkType(true);
26 | }).toThrow();
27 |
28 | expect(function () {
29 | instance.checkType("");
30 | }).toThrow();
31 | });
32 | })
33 |
34 | describe("检查单例",function () {
35 | it('不能实例化DisplayComponentFactory', () => {
36 | expect(function () {
37 | new DisplayComponentFactory();
38 | }).toThrow();
39 | });
40 |
41 | it("#getInstance",function () {
42 | expect(instance == DisplayComponentFactory.getInstance()).toEqual(true);
43 | })
44 | })
45 |
46 | describe("#registerComponent",function () {
47 | it("参数type必须是有效的字符串",function () {
48 | expect(function () {
49 | instance.registerComponent();
50 | }).toThrow();
51 | })
52 |
53 | it("参数component不能为空",function () {
54 | expect(function () {
55 | instance.registerComponent(GLS_COMPONENT_TEXT);
56 | }).toThrow();
57 | })
58 |
59 | it("正常注册组件",function () {
60 | instance.registerComponent(GLS_COMPONENT_TEXT,ComponentText,"文本");
61 | })
62 | })
63 |
64 | describe("#getRegisterComponent",function () {
65 | it("参数type必须是有效的字符串",()=>{
66 | expect(function () {
67 | instance.getRegisterComponent()
68 | }).toThrow();
69 |
70 | expect(function () {
71 | instance.getRegisterComponent("")
72 | }).toThrow();
73 | });
74 |
75 | it("获取到ComponentText",()=>{
76 | expect(instance.getRegisterComponent(GLS_COMPONENT_TEXT)).toBeDefined();
77 | });
78 | })
79 |
80 | describe("#createComponent",function () {
81 | it("创建ComponentText成功",()=>{
82 | expect(instance.createComponent(project,GLS_COMPONENT_TEXT) instanceof ComponentText).toBeTruthy();
83 | });
84 | })
85 |
86 | describe("#getCreatedComponent",function () {
87 | it("正常获取创建的组件",function () {
88 | let component = instance.createComponent(project,GLS_COMPONENT_TEXT);
89 | expect(instance.getCreatedComponent(component.props.id)).toEqual(component);
90 | expect(function () {
91 | instance.getCreatedComponent(111)
92 | }).toThrow();
93 | })
94 | })
95 | });
96 |
97 |
--------------------------------------------------------------------------------
/src/modules/editor/core/factorys/display/DisplayComponentFactory.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponentRegisterInfo} from "./DisplayComponentRegisterInfo";
5 | import {DisplayComponent} from "../../display/DisplayComponent";
6 | import {Properties} from "../../display/property/Properties";
7 | import {Project} from "../../project/Project";
8 |
9 | export class DisplayComponentFactory {
10 | /**
11 | * @type {DisplayComponentFactory}
12 | * @private
13 | */
14 | static _instance = null;
15 |
16 | _registerComponents: { [key: string]: DisplayComponentRegisterInfo } = {};
17 |
18 | /**
19 | * 已经创建的组件的哈希表
20 | * @type {Map<{[key:string]:DisplayComponent}>}
21 | */
22 | _createdComponents = new Map();
23 |
24 | constructor() {
25 | if (DisplayComponentFactory._instance) {
26 | throw new Error("DisplayComponentFactory不能实例化!");
27 | }
28 | DisplayComponentFactory._instance = this;
29 | }
30 |
31 | /**
32 | * 获取工厂的单例
33 | * @return {DisplayComponentFactory}
34 | */
35 | static getInstance() {
36 | if (!DisplayComponentFactory._instance) {
37 | return new DisplayComponentFactory();
38 | }
39 | return DisplayComponentFactory._instance;
40 | }
41 |
42 | /**
43 | * 注册组件
44 | * @param {String} type
45 | * @param {DisplayComponent} component
46 | */
47 | registerComponent(type: string, component: DisplayComponent, alias: string = "组件") {
48 | this.checkType(type);
49 |
50 | if (this.getRegisterComponent(type)) {
51 | throw new Error("该组件已经注册过");
52 | }
53 |
54 | if (!component) {
55 | throw new Error("component不能为空");
56 | }
57 |
58 | let info = new DisplayComponentRegisterInfo();
59 | info.type = type;
60 | info.component = component;
61 | info.alias = alias;
62 | this._registerComponents[type] = info;
63 | }
64 |
65 | /**
66 | * 检查组件类型
67 | */
68 | checkType(type: string) {
69 | if (typeof type !== "string" || !type) {
70 | throw new Error("请提供有效的类型");
71 | }
72 | }
73 |
74 | /**
75 | * 为某个项目创建组件
76 | * @param {string} type
77 | * @returns {DisplayComponent}
78 | */
79 | createComponent(project:Project,type: string,id:string = ""): DisplayComponent {
80 | this.checkType(type);
81 | if (this.getRegisterComponent(type)) {
82 | let info = this._registerComponents[type];
83 | let component = new info.component();
84 | let props: Properties = component.props;
85 | props.type = type; //这一行必须在id之前,因为id要根据组件类型来生成
86 | props.id = id || this.generateUUID(component,project);
87 | this._createdComponents.set(component.props.id, component);
88 | return component;
89 | } else {
90 | throw new Error(`组件未注册,不能创建:${type}`);
91 | }
92 | }
93 |
94 | /**
95 | * @param component {DisplayComponent}
96 | */
97 | generateUUID(component,project:Project) {
98 | return component.props.type + (++ project.numComponentCreated);//TODO 由于project的数据不是一直会保存在服务器,所以这里会有BUG
99 | }
100 |
101 | /**
102 | * 获取注册过的组件
103 | * @param type
104 | * @return {DisplayComponentRegisterInfo}
105 | */
106 | getRegisterComponent(type) {
107 | this.checkType(type);
108 | return this._registerComponents[type];
109 | }
110 |
111 | /**
112 | * 根据id获取创建的组件
113 | * @param id
114 | */
115 | getCreatedComponent(id) {
116 | if (!this._createdComponents.has(id)) {
117 | throw new Error(`id为${id}的组件没创建过`);
118 | }
119 | return this._createdComponents.get(id);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/modules/editor/core/factorys/display/DisplayComponentRegisterInfo.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {DisplayComponent} from "../../display/DisplayComponent";
5 |
6 | export class DisplayComponentRegisterInfo {
7 | /**
8 | * 组件的类型
9 | */
10 | type: string = "";
11 |
12 | /**
13 | * 组件的类
14 | */
15 | component: any = null;
16 |
17 | alias: string;
18 | }
19 |
--------------------------------------------------------------------------------
/src/modules/editor/core/factorys/scsne/SceneService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/10/4/004.
3 | */
4 | import {Project} from "../../project/Project";
5 | import {Scene} from "../../scene/Scene";
6 | import {ISceneProperties} from "../../scene/ISceneProperties";
7 | import {GLS_COMPONENT_STAGE, Stage} from "../../display/stage/Stage";
8 | import {DisplayComponentFactory} from "../display/DisplayComponentFactory";
9 | import {utils} from "../../../../../common/utils";
10 | import {DisplayComponent} from "../../display/DisplayComponent";
11 | import axios from 'axios';
12 | import {Properties} from "../../display/property/Properties";
13 | import {DisplayComponentContainer} from "../../display/DisplayComponentContainer";
14 | import {Debounce} from "../../../../../common/utils/Debounce";
15 | import {Injectable} from "angular2-decorators-for-vue";
16 | export interface IDisplayComponentData {
17 | props: Properties;
18 | children?: Array;
19 | }
20 |
21 | @Injectable()
22 | export class SceneService {
23 | private _debounce = new Debounce(50);
24 | createScene(project: Project, sceneProperties?: ISceneProperties) {
25 | let scene = new Scene();
26 | if (!sceneProperties) {
27 | scene.props.id = (++project.numSceneCreated).toString();
28 | scene.props.name = `场景${scene.props.id}`;
29 | } else {
30 | for (let key of Object.keys(sceneProperties)) {
31 | scene.props[key] = sceneProperties[key];
32 | }
33 | }
34 | scene.stage = DisplayComponentFactory.getInstance().createComponent(project, GLS_COMPONENT_STAGE);
35 | return scene;
36 | }
37 |
38 | load(project: Project, scene: Scene) {
39 | scene.destroy();
40 |
41 | axios.get("/api/getScene?id=" + scene.props.id).then((result) => {
42 | let data: IDisplayComponentData = result.data;
43 | let stage;
44 | if (data.props) {
45 | stage = this.createComponent(project, data);
46 | } else {
47 | stage = DisplayComponentFactory.getInstance().createComponent(project, GLS_COMPONENT_STAGE);
48 | }
49 | scene.stage = stage as Stage;
50 | })
51 | }
52 |
53 | createComponent(project: Project, data: IDisplayComponentData): DisplayComponent {
54 | let component = DisplayComponentFactory.getInstance().createComponent(project, data.props.type, data.props.id);
55 |
56 | //继承服务器的属性
57 | for (let key of Object.keys(data.props)) {
58 | component.props[key] = data.props[key];
59 | }
60 |
61 | if (utils.isArray(data.children)) {
62 | data.children.forEach((childData: IDisplayComponentData) => {
63 | let child = this.createComponent(project, childData);
64 | (component).addChild(child);
65 | })
66 | }
67 | return component;
68 | }
69 |
70 | /**
71 | * 保存场景
72 | * @param {Project} project
73 | * @param {Scene} scene
74 | * @returns {AxiosPromise}
75 | */
76 | save(project: Project, scene: Scene) {
77 | let formData = utils.mapTree(scene.stage, function (component: DisplayComponent) {
78 | return {props: component.props};
79 | });
80 | formData.id = scene.props.id;
81 | formData.projectId = project.id;
82 | return axios.put("/api/saveScene", formData);
83 | }
84 |
85 | /**
86 | * 删除场景
87 | * @param {Project} project
88 | * @returns {Promise}
89 | */
90 | deleteScene(project: Project) {
91 | return axios.delete(utils.getRequestPath('/api/deleteScene', {id: project.selectedScene.props.id})).then((result) => {
92 | if (result.data !== false) {
93 | project.removeSelectedScene();
94 | }
95 | });
96 | }
97 |
98 | /**
99 | * 新增一个空白场景
100 | * @param {Project} project
101 | */
102 | addScene(project: Project) {
103 | let index = project.scenes.indexOf(project.selectedScene) + 1;
104 | axios.put(utils.getRequestPath('/api/addScene', {index: index, projectId: project.id})).then(() => {
105 | let scene = this.createScene(project);
106 | let scenes = project.scenes;
107 | scenes.splice(index, 0, scene);
108 | });
109 | }
110 |
111 | /**
112 | * 拷贝场景
113 | * @param {Project} project
114 | */
115 | copyScene(project: Project) {
116 | let selectedScene = project.selectedScene;
117 | axios.put(utils.getRequestPath('/api/copyScene', {id: selectedScene.props.id})).then((result) => {
118 | let data = result.data;
119 | let scene = this.createScene(project);
120 | scene.props.id = data.id;
121 | scene.props.name = data.name;
122 | let scenes = project.scenes;
123 | scenes.splice(project.scenes.indexOf(selectedScene) + 1, 0, scene);
124 | });
125 | }
126 |
127 | /**
128 | * 更换场景的位置
129 | * @param {number} oldIndex
130 | * @param {number} newIndex
131 | */
132 | changeScene(oldIndex:number,newIndex:number){
133 | axios.post(utils.getRequestPath('/api/changeSceneIndex', {oldIndex: oldIndex,newIndex:newIndex})).catch((result) => {
134 | //todo 失败就是要回档咯
135 | });
136 | }
137 |
138 | /**
139 | * 修改场景的名称
140 | * @param {Scene} scene
141 | */
142 | changeSceneName(scene:Scene){
143 | this._debounce.handle(()=>{
144 | axios.put(utils.getRequestPath('/api/changeSceneName', {id: scene.props.id,name:scene.props.name}));
145 | })
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/modules/editor/core/geom/Rect.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/18
3 | */
4 | import {Rect} from "./Rect";
5 |
6 | describe("Rect",function () {
7 | document.body.style.margin = "0";
8 | document.body.innerHTML = ``;
9 | let element = document.getElementById("rect");
10 | let clientRect = element.getBoundingClientRect();
11 | let rect = new Rect(clientRect);
12 | describe("#top",function () {
13 | it("top相等",function () {
14 | expect(rect.top).toEqual(clientRect.top);
15 | })
16 | })
17 |
18 | describe("#right",function () {
19 | it("right相等",function () {
20 | expect(rect.right).toEqual(clientRect.right);
21 | });
22 | })
23 |
24 | describe("#bottom",function () {
25 | it("bottom相等",function () {
26 | expect(rect.bottom).toEqual(clientRect.bottom);
27 | });
28 | })
29 |
30 | describe("#left",function () {
31 | it("left相等",function () {
32 | expect(rect.left).toEqual(clientRect.left);
33 | });
34 | })
35 |
36 | describe("#centerX",function () {
37 | it("centerX为150",function () {
38 | expect(rect.centerX).toEqual(150);
39 | });
40 | })
41 |
42 | describe("#centerY",function () {
43 | it("centerY为150",function () {
44 | expect(rect.centerY).toEqual(150);
45 | });
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/src/modules/editor/core/geom/Rect.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/18
3 | * @设计模式-装饰者模式,给ClientRect拓展出centerX,centerY.让top,left,width,height变成可写的.
4 | */
5 | export class Rect {
6 | _rect: { top: number, left: number, width: number, height: number };
7 |
8 | constructor(top: number | any, left?: number, width?: number, height?: number) {
9 | if (typeof top == "object") {
10 | let rect = top;
11 | this._rect = {
12 | top: rect.top,
13 | left: rect.left,
14 | width: rect.width,
15 | height: rect.height
16 | } as any;
17 | } else {
18 | this._rect = {
19 | top: top,
20 | left: left,
21 | width: width,
22 | height: height
23 | } as any;
24 | }
25 | }
26 |
27 | get top() {
28 | return this._rect.top;
29 | }
30 |
31 | set top(value) {
32 | this._rect.top = value;
33 | }
34 |
35 | get left() {
36 | return this._rect.left;
37 | }
38 |
39 | set left(value) {
40 | this._rect.left = value;
41 | }
42 |
43 | get width() {
44 | return this._rect.width;
45 | }
46 |
47 | set width(value) {
48 | this._rect.width = value;
49 | }
50 |
51 | get height() {
52 | return this._rect.height;
53 | }
54 |
55 | set height(value) {
56 | this._rect.height = value;
57 | }
58 |
59 | get right() {
60 | return this._rect.left + this._rect.width;
61 | }
62 |
63 | get bottom() {
64 | return this._rect.top + this._rect.height;
65 | }
66 |
67 | get centerX() {
68 | return (this.left + this.right) / 2;
69 | }
70 |
71 | get centerY() {
72 | return (this.top + this.bottom) / 2;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/modules/editor/core/parse/CopyParsteManager.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/21
3 | */
4 | import {DisplayComponentFactory} from "../factorys/display/DisplayComponentFactory";
5 | import {Project} from "../project/Project";
6 | import {Scene} from "../scene/Scene";
7 | import {Stage} from "../display/stage/Stage";
8 | import {DisplayComponent} from "../display/DisplayComponent";
9 | import {CopyPasteManager} from "./CopyPasteManager";
10 | import {ComponentImage, GLS_COMPONENT_IMAGE} from "../display/image/ComponentImage";
11 | import {Inject} from "angular2-decorators-for-vue";
12 |
13 | describe("CopyPasteManager",function () {
14 | DisplayComponentFactory.getInstance().registerComponent(GLS_COMPONENT_IMAGE, ComponentImage)
15 | let project = new Project();
16 | let scene = new Scene();
17 | project.scenes.push(scene);
18 | let stage = scene.stage = new Stage();
19 | let image:DisplayComponent = DisplayComponentFactory.getInstance().createComponent(project,GLS_COMPONENT_IMAGE);
20 | stage.addChild(image);
21 | image.props.selected = true;
22 |
23 | class Tester{
24 | @Inject(CopyPasteManager)
25 | CopyPasteManager:CopyPasteManager;
26 | }
27 |
28 | let tester = new Tester();
29 |
30 | describe("init",function () {
31 | it("没初始化,拷贝就出错",function () {
32 | expect(function () {
33 | tester.CopyPasteManager.copy();
34 | }).toThrow();
35 | })
36 | it("没初始化,粘贴就出错",function () {
37 | expect(function () {
38 | tester.CopyPasteManager.paste();
39 | }).toThrow();
40 | })
41 | it("根据项目初始化",function () {
42 | tester.CopyPasteManager.init(project);
43 | })
44 | })
45 |
46 | describe("copy",function () {
47 | it("正常拷贝",function () {
48 | tester.CopyPasteManager.copy();
49 | })
50 | })
51 |
52 | describe("copy",function () {
53 | it("正常粘贴",function () {
54 | tester.CopyPasteManager.paste();
55 | })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/src/modules/editor/core/parse/CopyPasteManager.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/21
3 | */
4 | import {DisplayComponent} from "../display/DisplayComponent";
5 | import {Project} from "../project/Project";
6 | import {Injectable} from "angular2-decorators-for-vue";
7 |
8 | @Injectable()
9 | export class CopyPasteManager {
10 | //已经拷贝的内容
11 | copies: Array = [];
12 |
13 | _project: Project;
14 |
15 | /**
16 | * 必须初始化项目,不然不能复制东西
17 | * @param project
18 | */
19 | init(project) {
20 | this._project = project;
21 | }
22 |
23 | get project():Project{
24 | if(!this._project){
25 | throw new Error("CopyPasteManager必須使用CopyPasteManager.getInstance().init(project)初始化项目进来");
26 | }
27 | return this._project;
28 | }
29 |
30 | /**
31 | * 是不是有复制东西
32 | * @returns {boolean}
33 | */
34 | hasCopies(): boolean {
35 | return !!this.copies.length;
36 | }
37 |
38 | /**
39 | * 复制
40 | */
41 | copy() {
42 | if (this.project && this.project.selectedScene) {
43 | this.copies = this.project.selectedScene.selectedComponents.filter(function (component:DisplayComponent) {
44 | return component.props.copyable;
45 | });
46 | }
47 | }
48 |
49 | /**
50 | * 粘贴
51 | */
52 | paste() {
53 | if (this.project && this.project.selectedScene) {
54 | let selectedScene = this.project.selectedScene;
55 | selectedScene.clearSelection();
56 | this.copies.forEach((component:DisplayComponent)=>{
57 | let clone = component.clone(this.project);
58 | this.project.selectedScene.stage.addChild(clone);
59 | clone.props.selected = true;
60 | })
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/modules/editor/core/project/Project.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/10/5/005
3 | **/
4 | import {Project} from "./Project";
5 | import {SceneService} from "../factorys/scsne/SceneService";
6 | import {GLS_COMPONENT_STAGE, Stage} from "../display/stage/Stage";
7 | import {DisplayComponentFactory} from "../factorys/display/DisplayComponentFactory";
8 | import {Inject} from 'angular2-decorators-for-vue';
9 |
10 | describe('Project', () => {
11 | DisplayComponentFactory.getInstance().registerComponent(GLS_COMPONENT_STAGE, Stage)
12 | it('#removeSelectedScene', () => {
13 |
14 | class Test {
15 | @Inject(SceneService)
16 | SceneService: SceneService;
17 |
18 | constructor() {
19 | let project = new Project();
20 | for (let index = 0; index < 3; index++) {
21 | project.scenes.push(this.SceneService.createScene(project));
22 | }
23 |
24 | project.selectedScene = project.scenes[1];
25 | expect(project.removeSelectedScene().props.id).toEqual("2");
26 | expect(project.removeSelectedScene().props.id).toEqual("3");
27 | expect(project.removeSelectedScene()).toEqual(null);
28 | }
29 | }
30 |
31 | new Test();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/modules/editor/core/project/Project.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import {Scene} from "../scene/Scene";
5 |
6 | export class Project {
7 | id: string = ""; //id
8 | name: string = ""; //名称
9 | creator: string = ""; //创建者
10 | numSceneCreated: number = 0; //该项目创建过多少个场景
11 | numComponentCreated: number = 0; //该项目创建过多少个组件啊
12 | /**
13 | * 一个也没由多个场景组成
14 | * @type {Array}
15 | */
16 | scenes: Array = [];
17 |
18 | /**
19 | * 所选的场景
20 | * @type {Scene}
21 | */
22 | selectedScene: Scene = null;
23 |
24 | /**
25 | * 删除所选的场景
26 | * @returns {boolean}
27 | */
28 | removeSelectedScene(): Scene {
29 | if (this.scenes.length == 1) {
30 | return null;
31 | }
32 | //找出相邻的场景
33 | let removedScene = this.selectedScene;
34 | let index = this.scenes.indexOf(this.selectedScene);
35 | let selectScene = this.scenes[index + 1] || this.scenes[index - 1];
36 | //选中相邻的场景
37 | this.selectedScene = selectScene;
38 | //删除当前场景
39 | this.scenes.splice(index, 1);
40 | return removedScene;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/modules/editor/core/project/ProjectService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | import axios from "axios";
5 | import {Project} from "./Project";
6 | import {SceneService} from "../factorys/scsne/SceneService";
7 | import {Scene} from "../scene/Scene";
8 | import {Inject,Injectable} from "angular2-decorators-for-vue";
9 |
10 | @Injectable()
11 | export class ProjectService {
12 |
13 | @Inject(SceneService)
14 | SceneService: SceneService;
15 |
16 | /**
17 | * @type {Project}
18 | */
19 | currentProject = null;
20 |
21 | fetchById(projectId: string) {
22 | let promise = new Promise((resolve, reject)=>{
23 | axios.get("/api/getProject?id=" + projectId).then((result) => {
24 | let project = new Project();
25 | let data = result.data;
26 | project.numSceneCreated = data.numSceneCreated;
27 | project.numComponentCreated = data.numComponentCreated;
28 | project.creator = data.creator;
29 | project.name = data.name;
30 | project.id = data.id;
31 | result.data.scenes.forEach((sceneData) => {
32 | let scene = this.SceneService.createScene(project, sceneData);
33 | project.scenes.push(scene)
34 | })
35 | resolve(project);
36 | }).catch(function () {
37 | reject();
38 | })
39 | })
40 | return promise;
41 | }
42 |
43 | save(project: Project) {
44 | let data = {
45 | numSceneCreated: project.numSceneCreated,
46 | numComponentCreated: project.numComponentCreated,
47 | id: project.id,
48 | name: project.name,
49 | creator: project.creator,
50 | scenes: []
51 | };
52 |
53 | data.scenes = project.scenes.map(function (scene: Scene) {
54 | return {id: scene.props.id, name: scene.props.name};
55 | });
56 |
57 | return axios.put("/api/getProject?id=" + project.id, JSON.stringify(data), {
58 | headers: {'Content-Type': "application/json"}
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/modules/editor/core/scene/ISceneProperties.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | * 场景配置的接口
4 | */
5 | export interface ISceneProperties {
6 | id: string; //场景id
7 | name: string; //场景名称
8 | }
9 |
--------------------------------------------------------------------------------
/src/modules/editor/core/scene/SceneInfo.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by 给力叔 on 2017/9/8.
3 | */
4 | export class SceneInfo {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/polyfills/PolyfillClassList.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/13
3 | */
4 | if (!("classList" in document.documentElement)) {
5 | Object.defineProperty(HTMLElement.prototype, 'classList', {
6 | get: function() {
7 | var self = this;
8 | function update(fn) {
9 | return function(value) {
10 | var classes = self.className.split(/\s+/g),
11 | index = classes.indexOf(value);
12 |
13 | fn(classes, index, value);
14 | self.className = classes.join(" ");
15 | }
16 | }
17 |
18 | return {
19 | add: update(function(classes, index, value) {
20 | if (!~index) classes.push(value);
21 | }),
22 |
23 | remove: update(function(classes, index) {
24 | if (~index) classes.splice(index, 1);
25 | }),
26 |
27 | toggle: update(function(classes, index, value) {
28 | if (~index)
29 | classes.splice(index, 1);
30 | else
31 | classes.push(value);
32 | }),
33 |
34 | contains: function(value) {
35 | return !!~self.className.split(/\s+/g).indexOf(value);
36 | },
37 |
38 | item: function(i) {
39 | return self.className.split(/\s+/g)[i] || null;
40 | }
41 | };
42 | }
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/polyfills/Polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * create by 给力叔 2017/9/13
3 | */
4 | import 'core-js/es6/symbol';
5 | import 'core-js/es6/object';
6 | import 'core-js/es6/function';
7 | import 'core-js/es6/parse-int';
8 | import 'core-js/es6/parse-float';
9 | import 'core-js/es6/number';
10 | import 'core-js/es6/math';
11 | import 'core-js/es6/string';
12 | import 'core-js/es6/date';
13 | import 'core-js/es6/array';
14 | import 'core-js/es6/regexp';
15 | import 'core-js/es6/map';
16 | import 'core-js/es6/set';
17 | import 'core-js/es6/weak-map';
18 | import 'core-js/es6/weak-set';
19 | import 'core-js/es6/typed';
20 | import 'core-js/es6/reflect';
21 | import 'core-js/es7/reflect';
22 | import 'core-js/es7/array';
23 | import 'core-js/es7/promise';
24 | import './PolyfillClassList';
25 | import 'babel-polyfill'
26 | if (!("classList" in document.documentElement)) {
27 | Object.defineProperty(HTMLElement.prototype, 'classList', {
28 | get: function() {
29 | var self = this;
30 | function update(fn) {
31 | return function(value) {
32 | var classes = self.className.split(/\s+/g),
33 | index = classes.indexOf(value);
34 |
35 | fn(classes, index, value);
36 | self.className = classes.join(" ");
37 | }
38 | }
39 |
40 | return {
41 | add: update(function(classes, index, value) {
42 | if (!~index) classes.push(value);
43 | }),
44 |
45 | remove: update(function(classes, index) {
46 | if (~index) classes.splice(index, 1);
47 | }),
48 |
49 | toggle: update(function(classes, index, value) {
50 | if (~index)
51 | classes.splice(index, 1);
52 | else
53 | classes.push(value);
54 | }),
55 |
56 | contains: function(value) {
57 | return !!~self.className.split(/\s+/g).indexOf(value);
58 | },
59 |
60 | item: function(i) {
61 | return self.className.split(/\s+/g)[i] || null;
62 | }
63 | };
64 | }
65 | });
66 | }
67 | export const Polyfills = {
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import ComponentEditor from '@/modules/editor/componentEditor/ComponentEditor.vue'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'Editor',
12 | component: ComponentEditor
13 | }
14 | ]
15 | })
16 |
--------------------------------------------------------------------------------
/src/vue-shims.d.ts:
--------------------------------------------------------------------------------
1 | //修正导入.vue报错
2 | declare module "*.vue" {
3 | import Vue from "vue";
4 | export default Vue;
5 | }
6 |
7 | declare var require: (files:Array | string,resolve?:any)=>any;
8 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq386232894/h5-editor/05ba8c5d23fcfcb4b1268828072b15f270f7590b/static/.gitkeep
--------------------------------------------------------------------------------
/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // the name of the method is the filename.
3 | // can be used in tests like this:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // for how to write custom assertions see
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 | exports.assertion = function (selector, count) {
10 | this.message = 'Testing if element <' + selector + '> has count: ' + count
11 | this.expected = count
12 | this.pass = function (val) {
13 | return val === this.expected
14 | }
15 | this.value = function (res) {
16 | return res.value
17 | }
18 | this.command = function (cb) {
19 | var self = this
20 | return this.api.execute(function (selector) {
21 | return document.querySelectorAll(selector).length
22 | }, [selector], function (res) {
23 | cb.call(self, res)
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var config = require('../../config')
3 |
4 | // http://nightwatchjs.org/gettingstarted#settings-file
5 | module.exports = {
6 | src_folders: ['test/e2e/specs'],
7 | output_folder: 'test/e2e/reports',
8 | custom_assertions_path: ['test/e2e/custom-assertions'],
9 |
10 | selenium: {
11 | start_process: true,
12 | server_path: require('selenium-server').path,
13 | host: '127.0.0.1',
14 | port: 4444,
15 | cli_args: {
16 | 'webdriver.chrome.driver': require('chromedriver').path
17 | }
18 | },
19 |
20 | test_settings: {
21 | default: {
22 | selenium_port: 4444,
23 | selenium_host: 'localhost',
24 | silent: true,
25 | globals: {
26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
27 | }
28 | },
29 |
30 | chrome: {
31 | desiredCapabilities: {
32 | browserName: 'chrome',
33 | javascriptEnabled: true,
34 | acceptSslCerts: true
35 | }
36 | },
37 |
38 | firefox: {
39 | desiredCapabilities: {
40 | browserName: 'firefox',
41 | javascriptEnabled: true,
42 | acceptSslCerts: true
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing'
3 | var server = require('../../build/dev-server.js')
4 |
5 | server.ready.then(() => {
6 | // 2. run the nightwatch test suite against it
7 | // to run in additional browsers:
8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
9 | // 2. add it to the --env flag below
10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
11 | // For more information on Nightwatch's config file, see
12 | // http://nightwatchjs.org/guide#settings-file
13 | var opts = process.argv.slice(2)
14 | if (opts.indexOf('--config') === -1) {
15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
16 | }
17 | if (opts.indexOf('--env') === -1) {
18 | opts = opts.concat(['--env', 'chrome'])
19 | }
20 |
21 | var spawn = require('cross-spawn')
22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
23 |
24 | runner.on('exit', function (code) {
25 | server.close()
26 | process.exit(code)
27 | })
28 |
29 | runner.on('error', function (err) {
30 | server.close()
31 | throw err
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function (browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.elementPresent('.hello')
15 | .assert.containsText('h1', 'Welcome to Your Vue.js App')
16 | .assert.elementCount('img', 1)
17 | .end()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 | Error.stackTraceLimit = Infinity;
5 | //修正使用es6,es7语法报错的问题
6 | require('core-js/es6');
7 | require('core-js/es7');
8 | // // require all test files (files that ends with .spec.js)
9 | // const testsContext = require.context('./specs', true, /\.spec$/)
10 | // testsContext.keys().forEach(testsContext)
11 |
12 | var srcSpecContext = require.context('../../src', true, /\.spec\.ts/)
13 | srcSpecContext.keys().forEach(srcSpecContext)
14 |
15 | // require all src files except main.js for coverage.
16 | // you can also change this to match only the subset of files that
17 | // you want coverage for.
18 | // const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
19 | // srcContext.keys().forEach(srcContext)
20 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['jasmine'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | browserNoActivityTimeout:0
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "es5",
6 | "es6",
7 | "es7",
8 | "es2015.promise"
9 | ],
10 | "module": "es2015",
11 | "moduleResolution": "node",
12 | "target": "es5",
13 | "sourceMap": true,
14 | "emitDecoratorMetadata": true,
15 | "experimentalDecorators": true,
16 | "allowSyntheticDefaultImports": true,
17 | "allowJs": true,
18 | "typeRoots": [
19 | "node_modules/@types"
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------