Commit b6bd85db authored by 劳工's avatar 劳工

Merge branch 'feature/huhu' into 'master'

Feature/huhu

See merge request !2
parents 5b7783cb a945a697
......@@ -54,12 +54,19 @@ function execute(document) {
var newName = layer.name.replace(mergeFlagReg, '');
layer.name = newName;
mergeLayers.push(newName);
try {
layer.merge();
mergeLayers.push(newName);
}catch (e) {
layer.merge();
}
return 1;
} else if (layer.name.match(excludeFlagReg)) {
layer.remove();
try {
layer.remove();
}catch (e) {
alert('移除图层' + layer.name + '失败,可能含有锁的层');
}
return 2;
}
return 0;
......
{
"name": "zeroing-editor",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"upload": "cp dist/index.html ../duiba-aurora-node/src/public/index.html&&ali-oss-publish -c oss.config.js -e dist",
"build:upload": "npm run build&&npm run upload",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
},
"dependencies": {
"cookie": "^0.4.0",
"copy-to-clipboard": "^3.2.0",
"core-js": "^2.6.5",
"element-ui": "^2.4.5",
"jszip": "^3.2.2",
"moment": "^2.24.0",
"path": "^0.12.7",
"querystringify": "^2.1.1",
"splitpanes": "^1.14.5",
"string-width": "^4.1.0",
"uuid": "^3.3.3",
"vue": "^2.6.10",
"vue-i18n": "^8.0.0",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
},
"devDependencies": {
"@kazupon/vue-i18n-loader": "^0.3.0",
"@vue/cli-plugin-babel": "^3.11.0",
"@vue/cli-service": "^3.11.0",
"ali-oss-publish": "^0.3.0",
"lodash": "^4.17.15",
"node-sass": "^4.9.2",
"sass": "^1.22.9",
"sass-loader": "^7.2.0",
"vue-cli-plugin-element": "^1.0.1",
"vue-cli-plugin-i18n": "^0.6.0",
"vue-draggable-resizable": "^2.0.1",
"vue-template-compiler": "^2.6.10"
},
"appVersion": "0.1.0"
"name": "zeroing-editor",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"upload": "node scripts/copy-index.js && ali-oss-publish -c oss.config.js -e dist",
"build:upload": "npm run build&&npm run upload",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
},
"dependencies": {
"codemirror": "^5.50.0",
"cookie": "^0.4.0",
"copy-to-clipboard": "^3.2.0",
"core-js": "^2.6.5",
"element-ui": "^2.4.5",
"indexdbwrapper": "^1.0.4",
"jszip": "^3.2.2",
"moment": "^2.24.0",
"monaco-editor": "^0.19.0",
"monaco-editor-webpack-plugin": "^1.8.1",
"path": "^0.12.7",
"querystringify": "^2.1.1",
"semver": "^7.1.1",
"socket.io-client": "^2.3.0",
"splitpanes": "^1.14.5",
"string-width": "^4.1.0",
"uuid": "^3.3.3",
"vue": "^2.6.10",
"vue-codemirror": "^4.0.6",
"vue-i18n": "^8.0.0",
"vue-router": "^3.0.3",
"vuex": "^3.0.1",
"zeroing-code-divider": "http://gitlab2.dui88.com/laoqifeng/zeroing-code-divider.git"
},
"devDependencies": {
"@kazupon/vue-i18n-loader": "^0.3.0",
"@vue/cli-plugin-babel": "^3.11.0",
"@vue/cli-service": "^3.11.0",
"ali-oss-publish": "^0.3.0",
"lodash": "^4.17.15",
"node-sass": "^4.9.2",
"sass": "^1.22.9",
"sass-loader": "^7.2.0",
"vue-cli-plugin-element": "^1.0.1",
"vue-cli-plugin-i18n": "^0.6.0",
"vue-draggable-resizable": "^2.0.1",
"vue-template-compiler": "^2.6.10"
},
"appVersion": "0.1.0"
}
......@@ -6,6 +6,9 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>烽火台</title>
<script src="https://cdn.bootcss.com/jshint/2.10.2/jshint.min.js"></script>
<script src="https://cdn.bootcss.com/jsonlint/1.6.0/jsonlint.min.js"></script>
</head>
<body>
<noscript>
......@@ -14,8 +17,9 @@
</noscript>
<script>
window.__data = {
token : "<%= process.env.NODE_ENV === 'development' ? 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjA4NSwibmFtZSI6IuWKs-eQquWzsCIsImFjY291bnQiOiJsYW9xaWZlbmciLCJlbWFpbCI6Imxhb3FpZmVuZ0BkdWliYS5jb20uY24iLCJtb2JpbGUiOiIxMzQ1Njc3NDE1MyIsImRpbmdVc2VySWQiOiI1MDY5MDYyOTIxMjkxMDAxIiwibW9kZWxBdXRob3JpemUiOmZhbHNlLCJsb2dpblRpbWVvdXQiOjc5MTUwLCJnbXRDcmVhdGUiOjE1MzQ0MTIwMTUwMDAsImdtdE1vZGlmaWVkIjoxNTczODgyMDk2MDAwLCJzeXN0ZW1JZFNldCI6WzAsMSwzNCwyMjcsMzYsMzcsMjMxLDIwMywxOSwyMSwyNDYsMjMsMjUsMjQ5LDI3LDMwXSwiaWF0IjoxNTc0NzU4OTk5fQ.tlRIqNZ3t1-5ZjWBuB7sASQuDBQ03gjfv0aDC_Ba_3k' : '$TOKEN$' %>",
apiHost : "<%= process.env.NODE_ENV === 'development' ? 'http://beacon.duibadev.com.cn' : '$API_HOST$' %>"
token : "<%= process.env.NODE_ENV === 'development' ? 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjU0NywibmFtZSI6IuWNnum-meS6rSIsImFjY291bnQiOiJiaWFubG9uZ3RpbmciLCJlbWFpbCI6ImJpYW5sb25ndGluZ0BkdWliYS5jb20uY24iLCJtb2JpbGUiOiIxNTg2OTE3NDMxMSIsImRpbmdVc2VySWQiOiIwMTE1MTgyMTEyMjE3OTYzNzAiLCJtb2RlbEF1dGhvcml6ZSI6ZmFsc2UsImxvZ2luVGltZW91dCI6NzczNjEsImdtdENyZWF0ZSI6MTU1OTUyNjcxNDAwMCwiZ210TW9kaWZpZWQiOjE1NzM3NDk1OTEwMDAsInN5c3RlbUlkU2V0IjpbMCwxLDM0LDM2LDM3LDIzMSwyMDMsNDQsMTksMjEsMjQ2LDIzLDI0OSwyNSwyNywzMF0sImlhdCI6MTU3Nzk1MjgxNH0.GNeP25ZZK5ca7rvwtj2BaQ3GZatGNSR3CthSN98PSD4' : '$TOKEN$' %>",
apiHost : "<%= process.env.NODE_ENV === 'development' ? 'http://beacon.duibadev.com.cn' : '$API_HOST$' %>",
types: `$TYPES$`
}
</script>
<div id="app"></div>
......
/**
* Created by rockyl on 2019-12-26.
*/
const fs = require('fs');
fs.copyFileSync('dist/index.html', '../duiba-aurora-node/src/public/index.html');
const now = Date.now();
fs.copyFileSync('dist/index.html', `dist/index.${now}.html`);
console.log('node update-editor.js', now);
......@@ -21,29 +21,30 @@ class ApiError extends Error {
}
}
export async function fetchApi(uri, {params, method = 'get', contentType = 'json', errMessage} = {
export async function fetchApi(uri, {params, method = 'get', auth = true, judgeSuccess = true, contentType = 'json', errMessage} = {
method: 'get',
contentType: 'json'
}) {
let url = API_HOST + uri;
let url = uri.startsWith('http') || uri.startsWith('//') ? uri : API_HOST + uri;
const options = {
method,
headers: {
authorization: 'Bearer ' + window['zeroing_token'],
},
headers: {},
//credentials: 'include',
};
if (auth) {
options.headers.authorization = 'Bearer ' + window['zeroing_token'];
}
if (params) {
if (method.toLowerCase() === 'post') {
switch(contentType){
switch (contentType) {
case 'form-data':
let formData = new FormData();
for (let key in params) {
let value = params[key];
if(value instanceof File){
if (value instanceof File) {
formData.append(key, value, value.name);
}else{
} else {
formData.append(key, value);
}
}
......@@ -59,7 +60,8 @@ export async function fetchApi(uri, {params, method = 'get', contentType = 'json
break;
}
} else {
url += (url.indexOf('?') < 0 ? '?' : '') + (url.endsWith('&') ? '' : '&') + stringify(params);
url += (url.indexOf('?') < 0 ? '?' : '');
url += (url.endsWith('?') ? '' : '&') + stringify(params);
}
}
......@@ -70,8 +72,12 @@ export async function fetchApi(uri, {params, method = 'get', contentType = 'json
const jsonObj = await response.json();
//console.log(jsonObj);
if (jsonObj.success) {
return jsonObj.data;
if (judgeSuccess) {
if (jsonObj.success) {
return jsonObj.data;
}
} else {
return jsonObj;
}
throw new ApiError('call api failed', jsonObj.code, errMessage);
......
/**
* Created by rockyl on 2019-09-19.
*
* 包相关api
*/
import {fetchApi} from "./common";
export async function fetchPackages(ids) {
return await fetchApi('/api/package/query', {
params: {
ids,
},
errMessage: 'Failed to fetch packages',
})
}
......@@ -6,8 +6,9 @@
import {fetchApi} from "./common";
export async function fetchAll() {
export async function fetchAll(currentPage, pageSize, onlyMine) {
return await fetchApi('/api/project/query', {
params: {currentPage, pageSize, isAll: onlyMine ? 0 : 1},
errMessage: 'Failed to fetch projects',
})
}
......@@ -44,7 +45,24 @@ export async function fetchOne(id) {
})
}
export async function saveOne(project) {
export async function fetchOneFromDataUrl(dataUrl) {
return await fetchApi(dataUrl, {
auth: false,
judgeSuccess: false,
errMessage: 'Failed to fetch project',
})
}
export async function fetchHistory(id, currentPage, pageSize) {
return await fetchApi('/api/project/history', {
params: {id, currentPage, pageSize},
method: 'get',
errMessage: 'Failed to history',
})
}
export async function saveOne(project, remark) {
project.remark = remark;
return await fetchApi('/api/project/update', {
params: project,
method: 'post',
......@@ -52,9 +70,9 @@ export async function saveOne(project) {
})
}
export async function pack(id) {
export async function pack(id, debug, packedAssets) {
return await fetchApi('/api/project/pack', {
params: {id},
params: {id, debug, packedAssets},
method: 'post',
errMessage: 'Failed to pack project',
})
......@@ -73,11 +91,13 @@ export async function importView(file) {
return response;
}
export async function uploadFile(file) {
export async function uploadFile(file, compress = false) {
let params = {file};
if (compress) {
params.compress = true;
}
const response = await fetchApi('/api/uploadFile', {
params: {
file,
},
params,
method: 'post',
contentType: 'form-data',
errMessage: 'Failed to upload file',
......
/**
* Created by rockyl on 2019-12-25.
*/
import io from 'socket.io-client';
import events from './global-events';
let socket;
export let codeSyncServeEnabled = false;
export function startCodeSyncServe(config) {
if (socket) {
socket.close();
}
socket = io(`http://${config.ip}:${config.port}`);
socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);
socket.on('edit-save', onEditSave);
events.$on('edit-open', editCode);
}
export function stop() {
}
export function editCode(code) {
if(socket && socket.connected){
socket.emit('edit-open', code);
}
}
function onConnect(t) {
codeSyncServeEnabled = true;
events.$emit('code-sync-start');
}
function onDisconnect() {
codeSyncServeEnabled = false;
events.$emit('code-sync-stop');
}
function onEditSave(data) {
events.$emit('edit-save', data)
}
<template>
<div style="width: 100%; height: 100%" ref="container"></div>
</template>
<script>
import * as monaco from 'monaco-editor';
export default {
name: "MonacoEditor",
data() {
return {
}
},
props: {
value: {
type: String,
default: '',
},
language: {
type: String,
default: 'javascript',
},
theme: {
type: String,
default: 'vs',
},
editorOptions: {
type: Object,
default: function(){
return {
selectOnLineNumbers: true,
roundedSelection: false,
readOnly: false, // 只读
cursorStyle: 'line', //光标样式
automaticLayout: false, //自动布局
glyphMargin: true, //字形边缘
useTabStops: false,
fontSize: 28, //字体大小
autoIndent:true,//自动布局
}
},
},
},
mounted() {
this.initEditor();
},
watch: {
value(value){
if(this.editor && value !== this.editor.getValue()){
this.editor.setValue(value);
}
}
},
methods: {
initEditor() {
this.editor = monaco.editor.create(this.$refs.container, {
value: this.value,
language: this.language,
theme: this.theme,
lineNumbers: 'on',
tabSize: 2,
insertSpaces: false,
editorOptions: this.editorOptions,
});
this.editor.onDidChangeModelContent(event => {
const value = this.editor.getValue();
this.$emit('input', value);
});
this.editor.updateOptions({
})
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -2,7 +2,7 @@
<div class="menu">
<el-badge class="menu-item" v-for="(item, key) of data" :key="key" is-dot :hidden="!menuBadge(key)">
<el-link @click="clickItem(key)" :icon="item.icon" :disabled="item.disabled">
{{item.text}}
{{item.label || ''}}
</el-link>
</el-badge>
</div>
......
// import { set, lensPath } from 'ramda';
import properties from '../../../utils/properties';
export default {
component: require('./index.vue'),
properties: {
...properties.node,
...properties.image
},
props: {
type: 'image'
},
title: 'image'
};
<template>
<div class="zero-custom-picture"></div>
</template>
<style>
/* .zero-custom-picture {
height: 100%;
background-position: center;
} */
</style>
<script>
export default {
name: 'customImage',
props: {
properties: {
type: Object,
default: () => {}
}
}
};
</script>
// import { set, lensPath } from 'ramda';
import properties from '../../../utils/properties';
export default {
component: require('./index.vue'),
properties: {
...properties.node,
...properties.label
},
props: {
type: 'label'
},
title: 'label'
};
<template>
<div
class="zero-custom-text"
:class="showPlaceholder && 'placeholder'"
v-html="addNBSP + selfText"
></div>
</template>
<style>
.zero-custom-text.placeholder:after {
content: '请输入';
font-style: italic;
font-size: 12px;
}
</style>
<script>
export default {
name: 'customLabel',
props: {
properties: {
type: Object,
default: () => {}
},
isTyping: Boolean
},
data() {
return {
};
},
computed: {
showPlaceholder() {
return (
!this.isTyping &&
typeof this.properties.text !== 'undefined' &&
!this.properties.text.replace(/\s|(&nbsp;)/g, '')
);
},
addNBSP() {
return this.selfText.replace(/\s/g, '') ? '' : '&nbsp;';
},
selfText() {
return this.properties.text || '文字';
}
}
};
</script>
<template>
<div :style="customStyle" class="zero-custom-cmp zero-custom-node">
<div :style="customStyle" @click.self.stop="clickViewHandle(view)" class="zero-custom-cmp zero-custom-node">
<p class="custom-node-html" v-if="selfText">{{selfText}}</p>
<slot></slot>
</div>
......@@ -30,12 +30,21 @@ export default {
properties: {
type: Object,
default: () => {}
},
view: {
type: Object,
default: () => {}
}
},
computed: {
selfText() {
return this.properties ? this.properties.text || '' : '';
}
},
methods: {
clickViewHandle(data) {
this.$store.dispatch('activeComponent', {data, fromPlayground: true});
}
}
};
</script>
// import { set, lensPath } from 'ramda';
import properties from '../../../utils/properties';
export default {
component: require('./index.vue'),
properties: {
...properties.node,
...properties.rect
},
props: {
type: 'rect'
},
title: 'rect'
};
<template>
<div class="zero-custom-shape-rect-blue" :style="`background-color: ${properties.fillColor};border-width: ${properties.strokeWidth}px; border-color: ${properties.strokeColor};`"></div>
</template>
<style>
.zero-custom-shape-rect-blue {
height: 100%;
}
</style>
<script>
export default {
name: 'customRect',
props: {
properties: {
type: Object,
default: () => {}
}
// fillColor: {
// type: Object,
// default: () => {
// return {
// title: '填充色',
// type: 'colorPicker',
// value: '#fff'
// };
// }
// },
// strokeColor: {
// type: Object,
// default: () => {
// return {
// title: '边框颜色',
// type: 'colorPicker',
// value: '#000'
// };
// }
// },
// strokeWidth: {
// type: Object,
// default: () => {
// return {
// title: '边框宽度',
// type: 'inputNumber',
// value: 1
// };
// }
// }
}
};
</script>
......@@ -3,6 +3,7 @@
placement="top"
trigger="hover"
width="auto"
:disabled="!legalUrl"
:content="url"
>
<img style="max-width: 200px;" v-if="url" :src="url" alt="" />
......@@ -29,7 +30,6 @@ export default {
methods: {
drop(e) {
if (this.$store.state.project.dragUUID) {
// debugger;
console.log('native drop', this.$store.state);
this.swvalue = `asset://${this.$store.state.project.dragUUID}`
}
......@@ -51,6 +51,9 @@ export default {
} else {
return '';
}
},
legalUrl: function() {
return (this.swvalue + '').indexOf('//') > -1;
}
}
};
......
......@@ -5,7 +5,9 @@
export let API_HOST;
if (process.env.NODE_ENV === 'development') {
//API_HOST = 'http://10.10.95.74:7777';
//API_HOST = 'http://10.10.94.134:7777';
//API_HOST = 'http://192.168.1.16:7777';
//API_HOST = 'http://192.168.0.104:7777';
//API_HOST = 'http://10.10.92.33:7777';
//API_HOST = 'http://localhost:3002';
API_HOST = window.__data.apiHost;
//API_HOST = '';
......@@ -21,6 +23,9 @@ export const SSO_VERIFY_PAGE_URL = '/sso/logout';
export const DOCK_POINT_OFFSET = 4;
export const PROJECT_PAGE_SIZE = 20;
export const HISTORY_PAGE_SIZE = 20;
//文件类型图标 t表示展示缩略图
export const fileTypeIcon = {
'': 'file-empty',
......@@ -28,6 +33,7 @@ export const fileTypeIcon = {
'.json': 'file-text',
'.zip': 'file-zip',
'.fnt': 'file-font',
'.mp3': 'file-music',
'.jpg': 't',
'.jpeg': 't',
......
......@@ -5,28 +5,45 @@
"Close": "Close",
"Still Close": "Still Close",
"Save": "Save",
"Save And Preview": "Save And Preview",
"Save And Close": "Save And Close",
"Reset": "Reset",
"Copy": "Copy",
"Assets config": "Assets config",
"Paste same level":"Paste(same level)",
"Paste child":"Paste(child)",
"Exit": "Exit",
"Props": "Props",
"Behavior": "Behavior",
"Only mine": "Only mine",
"Add": "Add",
"Delete": "Delete",
"Delete all": "Delete all",
"Not delete": "Not delete",
"Import": "Import",
"Export": "Export",
"Importing project": "Importing project",
"Exporting project": "Exporting project",
"Upload": "Upload",
"Uploading": "Uploading",
"Edit": "Edit",
"EditEnv": "EditEnv",
"EditCustomModule": "EditCustomModule",
"Script": "Script",
"CodeSyncServe": "CodeSyncServe",
"ID": "ID",
"Mock Editor": "Mock Editor",
"Enable mock serve": "Enable mock serve",
"Name": "Name",
"Alias": "Alias",
"Output": "Output",
"Code": "Code",
"Desc": "Desc",
"Empty": "Empty",
"Path": "Path",
"Timeout": "Timeout",
"SuccessRatio": "SuccessRatio",
"Data": "Data",
"Key": "Key",
"Default": "Default",
"Event": "Event",
......@@ -53,6 +70,8 @@
"Packing": "Packing",
"Type": "Type",
"Group": "Group",
"Select from history": "Select from history",
"Meta Search": "Meta Search",
"Access denied": "Access denied",
"Invalid router": "Invalid router",
"Jump after": "Jump after {cd}s",
......@@ -75,9 +94,18 @@
"Env editor": "Env editor",
"As inline": "As inline",
"Project": "Project",
"Check template code": "Check template code",
"Missing behavior": "Missing behavior",
"Input event name": "Input event name",
"Invalid event name": "Invalid event name",
"Event name exists": "Event name exists",
"Builtin event should add directly": "Builtin event should add directly",
"Rename event": "Rename event",
"Env constant": "Env constant",
"Custom module": "Custom module",
"Custom module asset mapping": "Custom module asset mapping",
"Copy template to clipboard": "Copy template to clipboard",
"Copied process to clipboard": "Copied process to clipboard",
"Link to parent": "Link to parent",
"Input project name": "Input project name",
"Invalid project name": "Invalid project name",
......@@ -91,6 +119,7 @@
"Fetching projects": "Fetching projects…",
"Failed to fetch env": "Failed to fetch env",
"Failed to fetch projects": "Failed to fetch projects",
"Failed to fetch history": "Failed to fetch history",
"Failed to fetch project": "Failed to fetch project",
"Failed to create project": "Failed to create project",
"Failed to duplicate project": "Failed to duplicate project",
......@@ -98,6 +127,7 @@
"Failed to get project": "Failed to get project",
"Failed to save project": "Failed to save project",
"Save project successfully": "Save project successfully",
"Input version remark": "Input version remark",
"Input view name": "Input view name",
"Invalid view name": "Invalid view name",
"Unsaved version found locally": "Unsaved version found locally, please select version to open",
......@@ -105,6 +135,7 @@
"Remote Version": "Remote Version",
"Confirm to exit the editor": "Confirm to exit the editor?",
"Confirm to publish": "Confirm to publish?",
"Are you sure to delete it's process": "Are you sure to delete it's process",
"Are you sure to delete this asset": "Are you sure to delete this asset",
"Are you sure to delete all assets": "Are you sure to delete all assets",
"Are you sure to delete this item": "Are you sure to delete this item",
......@@ -127,20 +158,40 @@
"Import multi": "Import multi",
"Import view success": "Import view success",
"menu": {
"save": "Save",
"details": "Details",
"preview": "Preview",
"pack": "Pack",
"exit": "Exit",
"undo": "Undo",
"redo": "Redo"
"save": {
"label": "Save"
},
"details": {
"label": "Details"
},
"preview-fast": {
"label": "Preview"
},
"publish": {
"label": "Publish"
},
"mock": {
"label": "Mock"
},
"exit": {
"label": "Exit"
},
"undo": {
"icon": "icon-undo"
},
"redo": {
"icon": "icon-redo"
}
},
"view_node_menu": {
"node": "Node",
"image": "Image",
"label": "Label",
"bitmapText": "BitmapText",
"rect": "Rect",
"scrollView": "ScrollView"
"scrollView": "ScrollView",
"scrollList": "ScrollList",
"svga": "SVGA"
},
"panes": {
"Assets": "Assets",
......@@ -165,11 +216,16 @@
"dynamic": "Dynamic",
"map": "Map"
},
"nodeFilterPresets": {
"trigger": "Behavior Trigger",
"type": "Node Type"
},
"events": {
"init": "Init",
"awake": "Awake",
"sleep": "Sleep",
"data-center": "DataCenter",
"update-data": "UpdateData",
"click": "Click",
"touchstart": "Touchstart",
"touchend": "Touchend",
......
......@@ -5,28 +5,45 @@
"Close": "关闭",
"Still Close": "仍然关闭",
"Save": "保存",
"Save And Preview": "保存并预览",
"Save And Close": "保存并关闭",
"Reset": "重置",
"Copy": "复制",
"Assets config": "素材配置",
"Paste same level":"粘贴(同级)",
"Paste child":"粘贴(子级)",
"Exit": "退出",
"Props": "属性",
"Behavior": "行为",
"Only mine": "仅显示我的",
"Add": "添加",
"Delete": "删除",
"Delete all": "删除全部",
"Not delete": "不删除",
"Import": "导入",
"Export": "导出",
"Importing project": "项目导入中",
"Exporting project": "项目导出中",
"Upload": "上传",
"Uploading": "上传中",
"Edit": "编辑",
"EditEnv": "编辑环境",
"EditCustomModule": "编辑自定义模块",
"Script": "脚本",
"CodeSyncServe": "代码同步服务",
"ID": "ID",
"Mock Editor": "Mock编辑器",
"Enable mock serve": "启用Mock服务",
"Name": "名字",
"Alias": "别名",
"Output": "输出",
"Code": "代码",
"Desc": "描述",
"Empty": "空",
"Path": "路径",
"Timeout": "超时(ms)",
"SuccessRatio": "成功率",
"Data": "数据",
"Key": "属性名",
"Default": "默认值",
"Event": "事件",
......@@ -49,10 +66,13 @@
"Saving": "保存中…",
"Divider": "分流节点",
"Custom": "自定义节点",
"Custom event": "自定义事件",
"Entry": "入口",
"Packing": "打包",
"Type": "类型",
"Group": "分组",
"Select from history": "打开历史版本",
"Meta Search": "过程元查找",
"Access denied": "无权限",
"Invalid router": "无效的页面",
"Jump after": "{cd}秒后跳转",
......@@ -75,9 +95,21 @@
"Env editor": "环境编辑器",
"As inline": "作为内联",
"Project": "项目",
"Check template code": "查看模板代码",
"Missing behavior": "行为丢失",
"Add behavior": "添加行为",
"Edit trigger": "编辑触发",
"Delete trigger": "删除触发",
"Input event name": "输入事件名",
"Rename event": "事件重命名",
"Invalid event name": "无效的事件名",
"Event name exists": "事件名已存在",
"Builtin event should add directly": "内部事件请直接添加",
"Env constant": "自定义常量",
"Custom module": "自定义模块",
"Custom module asset mapping": "自定义模块素材映射",
"Copy template to clipboard": "复制模板到粘贴板",
"Copied process to clipboard": "复制过程到粘贴板",
"Link to parent": "连接到父节点",
"Input project name": "输入项目名",
"Invalid project name": "无效的项目名",
......@@ -91,6 +123,7 @@
"Fetching projects": "获取项目列表…",
"Failed to fetch env": "获取环境失败",
"Failed to fetch projects": "获取项目列表失败",
"Failed to fetch history": "获取历史记录失败",
"Failed to fetch project": "获取项目失败",
"Failed to create project": "创建项目失败",
"Failed to duplicate project": "复制项目失败",
......@@ -98,6 +131,7 @@
"Failed to get project": "获取项目失败",
"Failed to save project": "保存项目失败",
"Save project successfully": "保存项目成功",
"Input version remark": "输入版本备注",
"Input view name": "输入视图名",
"Invalid view name": "无效的视图名",
"Unsaved version found locally": "本地发现了未保存保本,请选择版本打开",
......@@ -105,6 +139,7 @@
"Remote Version": "远程版本",
"Confirm to exit the editor": "确定退出编辑器吗?",
"Confirm to publish": "确定发布吗?",
"Are you sure to delete it's process": "确定删除这对应的过程吗",
"Are you sure to delete this asset": "确定删除这个素材吗",
"Are you sure to delete all assets": "确定删除全部素材吗",
"Are you sure to delete this item": "确定删除这一项吗",
......@@ -127,20 +162,42 @@
"Import multi": "导入多",
"Import view success": "视图导入成功",
"menu": {
"save": "保存",
"details": "详情",
"preview": "预览",
"pack": "发布",
"exit": "退出",
"undo": "撤销",
"redo": "重做"
"save": {
"label": "保存"
},
"details": {
"label": "详情"
},
"preview-fast": {
"label": "预览"
},
"publish": {
"label": "发布"
},
"mock": {
"label": "Mock"
},
"exit": {
"label": "退出"
},
"undo": {
"icon": "icon-undo"
},
"redo": {
"icon": "icon-redo"
}
},
"view_node_menu": {
"node": "节点",
"image": "图片",
"label": "标签",
"label": "文本",
"bitmapText": "位图文本",
"rect": "矩形",
"scrollView": "滚动视图"
"circle": "圆形",
"textinput": "输入框",
"scrollView": "滚动视图",
"scrollList": "滚动列表",
"svga": "SVGA"
},
"panes": {
"Assets": "素材",
......@@ -165,11 +222,16 @@
"dynamic": "动态",
"map": "字典"
},
"nodeFilterPresets": {
"trigger": "行为触发",
"type": "节点类型"
},
"events": {
"init": "初始化",
"awake": "激活",
"sleep": "入眠",
"data-center": "数据中心",
"update-data": "更新数据",
"click": "触摸点击",
"touchstart": "触摸按下",
"touchend": "触摸弹起",
......
......@@ -18,5 +18,10 @@ export default new Router({
name: 'editor',
component: () => import('./views/Editor.vue')
},
{
path: '/preview/:projectID',
name: 'preview',
component: () => import('./views/Preview.vue')
},
]
})
......@@ -29,24 +29,21 @@ export default new Vuex.Store({
plugins: [
SaveToLocalPlugin({
mutationTypes: [
'makeDirty',
'modifyProject',
'addNode',
'deleteNode',
'copyNode',
'addAsset',
'deleteAsset',
'deleteAllAssets',
'modifyAsset',
'importView',
'importAssets',
'addDataMapping',
'deleteDataMapping',
'modifyDataMapping',
'addEnvMapping',
'deleteEnvMapping',
'modifyEnvMapping',
'modifyActiveView',
'behavior_save',
'modifyCustoms',
'modifyProjectDetails',
'modifyMocks',
]
})
]
......
......@@ -8,6 +8,7 @@ import Vue from "vue";
import i18n from "../../i18n";
import generateUUID from "uuid/v4";
import {clonePureObj, metaInUse, updateProcesses} from "../../utils";
import {arrayFind} from "element-ui/src/utils/util";
export const behaviorStore = {
state: {
......@@ -15,8 +16,7 @@ export const behaviorStore = {
currentBehavior: null,
processContext: [],
originData: null,
originBehaviors: null,
behaviors: null,
currentProcess: null,
processStack: [],
editable: false,
......@@ -29,53 +29,20 @@ export const behaviorStore = {
},
},
mutations: {
behavior_startEdit(state, {originData, behaviors, event}) {
behavior_startEdit(state, {originData, behavior}) {
state.originData = originData;
state.originBehaviors = behaviors;
state.behaviors = clonePureObj(behaviors);
state.data = clonePureObj(originData);
if (state.behaviors.length > 0) {
state.currentBehavior = state.behaviors[0];
} else {
let metaUUID = generateUUID();
state.currentBehavior = state.behaviors[0] = {
uuid: generateUUID(),
alias: event,
meta: metaUUID,
};
let subEntryUUID = generateUUID();
state.data.processes.push({
id: metaUUID,
name: event,
props: {},
subEntry: subEntryUUID,
sub: {
[subEntryUUID]: {
uuid: subEntryUUID,
alias: i18n.t('Entry'),
meta: 'entry',
design: {
x: 10,
y: 10,
}
},
},
});
}
state.currentBehavior = clonePureObj(behavior);
},
behavior_save(state) {
state.originData.processes = state.data.processes;
},
addProcessMeta(state, meta) {
state.data.processes.push(meta);
return process;
},
behavior_save(state) {
state.originData.processes = state.data.processes;
state.originBehaviors[0] = state.currentBehavior;
},
updateProcesses(state, {targetMetaID, replaceMetaID}) {
for (let process of state.data.processes) {
updateProcesses(process, targetMetaID, replaceMetaID);
}
updateProcesses(state.data.processes, targetMetaID, replaceMetaID);
},
deleteProcessMeta(state, {meta, process}) {
let container;
......@@ -85,27 +52,23 @@ export const behaviorStore = {
container = state.data.processes;
}
if (container) {
for (let i = 0, li = container.length; i < li; i++) {
const process = container[i];
if (process.id === meta.id) {
container.splice(i, 1);
break;
}
}
deleteProcessMeta(meta.id, container);
}
},
clearProcessStack(state) {
state.processStack.splice(0);
updatePropsEditable(state);
state.currentProcess = null;
},
pushProcessStack(state, process) {
state.processStack.push(process);
updatePropsEditable(state);
state.currentProcess = process;
},
popProcessStack(state, index) {
state.processStack.splice(index);
updatePropsEditable(state);
state.currentProcess = state.processStack[state.processStack.length - 1];
},
setScale(state, value) {
let scale = state.drawState.boardScale;
......@@ -153,6 +116,10 @@ export const behaviorStore = {
}
},
actions: {
behavior_save({commit, state}) {
commit('behavior_save');
return state.currentBehavior;
},
addCustomProcessMeta({commit, state}, {masterProcess, isInline, processId}) {
let meta = {
id: generateUUID(),
......@@ -175,7 +142,7 @@ export const behaviorStore = {
addProcessMeta(commit, isInline, masterProcess, meta);
return meta;
},
addProcessFromPrefab({commit, state}, {masterProcess, isInline, meta: pMeta}){
addProcessFromPrefab({commit, state}, {masterProcess, isInline, meta: pMeta}) {
let meta = clonePureObj(pMeta);
meta.id = generateUUID();
meta.isInline = isInline;
......@@ -183,7 +150,10 @@ export const behaviorStore = {
addProcessMeta(commit, isInline, masterProcess, meta);
return meta;
}
},
searchMeta({state}, {keyword}) {
searchMeta(state.data.processes, keyword);
},
}
};
......@@ -191,7 +161,7 @@ function updatePropsEditable(state) {
state.editable = state.processStack.filter(item => item.meta.type === 'builtin') <= 0;
}
function addProcessMeta(commit, isInline, masterProcess, meta){
function addProcessMeta(commit, isInline, masterProcess, meta) {
if (isInline) {
if (!masterProcess.meta.metas) {
Vue.set(masterProcess.meta, 'metas', []);
......@@ -201,3 +171,53 @@ function addProcessMeta(commit, isInline, masterProcess, meta){
commit('addProcessMeta', meta);
}
}
function searchMeta(processes, keyword, path) {
for (let process of processes) {
/*if(process.id === keyword || process.name === keyword){
}*/
}
}
export function addBehavior(alias, processes) {
let metaUUID = generateUUID();
let behavior = {
uuid: generateUUID(),
meta: metaUUID,
};
let subEntryUUID = generateUUID();
processes.push({
id: metaUUID,
name: alias,
props: {},
subEntry: subEntryUUID,
sub: {
[subEntryUUID]: {
uuid: subEntryUUID,
alias: i18n.t('Entry'),
meta: 'entry',
design: {
x: 10,
y: 10,
}
},
},
});
return behavior;
}
export function deleteProcessMeta(metaId, processes) {
for (let i = 0, li = processes.length; i < li; i++) {
const process = processes[i];
if (process.id === metaId) {
processes.splice(i, 1);
break;
}
}
}
export function findProcess(metaId, processes) {
return arrayFind(processes, process => process.id === metaId);
}
......@@ -7,11 +7,13 @@
import {envApi} from "../../api";
import i18n from "../../i18n";
const storeKey = 'code-sync-serve-config';
export const envStore = {
state: {
initialized: false,
name: 'Zeroing Editor',
version: '1.0.1',
version: '1.0.3',
templates: {
builtin: ['blank'],
custom: [],
......@@ -19,6 +21,10 @@ export const envStore = {
processes: [],
scripts: [],
customs: [],
codeSyncServeConfig: {
ip: 'localhost',
port: 7788,
}
},
mutations: {
updateEnv(state, env) {
......@@ -28,6 +34,15 @@ export const envStore = {
state.customs = parseItem(state.customs);
state.initialized = true;
let configStr = localStorage.getItem(storeKey);
if (configStr) {
state.codeSyncServeConfig = JSON.parse(configStr);
}
},
saveCodeSyncServeConfig(state, config) {
state.codeSyncServeConfig = config;
localStorage.setItem(storeKey, JSON.stringify(state.codeSyncServeConfig));
},
},
getters: {
......@@ -35,8 +50,14 @@ export const envStore = {
return groupProcesses(state.processes, process => process.isPrefab);
},
builtinProcessTree: state => {
const tree = groupProcesses(state.processes, process => !process.isPrefab);
const builtin = groupProcesses(state.processes, process => !process.isPrefab);
let tree = [
{
name: 'builtin',
children: builtin,
}
];
const dividerProcess = {
id: 'divider',
name: i18n.t('Divider'),
......@@ -71,7 +92,7 @@ export const envStore = {
const env = await envApi.fetchEnv();
commit('updateEnv', env);
}
}
},
}
};
......
......@@ -7,10 +7,17 @@ import {projectApi} from "../../api";
import path from "path";
import generateUUID from "uuid/v4";
import {getCmpProps, flattenViews, getCmpByUUID} from '../../utils/common';
import {clonePureObj, saveAs} from "../../utils";
import {clonePureObj, getMockServeEnabled, saveAs} from "../../utils";
import {template} from "../../template";
import {importView, uploadFile} from "../../api/project";
import events from "@/global-events";
import {packImages} from "../../utils/sheet-pack";
import {addBehavior, deleteProcessMeta, findProcess} from "./behavior";
import db from "../../utils/db-storage";
import {preprocess} from "../../views/Preview/preview-preprocess";
const storeName = 'project';
const defaultOptions = {
pageTitle: 'no title',
......@@ -34,6 +41,43 @@ function getDefaultOptions() {
return clonePureObj(defaultOptions);
}
function setUUIDForAllChildren(node) {
if (node.children && node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
node.children[i] = copyBaseRoot(node.children[i])
setUUIDForAllChildren(node.children[i])
}
}
}
let copyNodeCatch = null;
function copyBaseRoot(node) {
let _node = JSON.parse(JSON.stringify(node));
let data;
if (_node.children && _node.children.length > 0) {
data = {
name: _node.name,
type: _node.type,
properties: _node.properties,
events: _node.events,
uuid: generateUUID(),
children: _node.children
};
} else {
data = {
name: _node.name,
type: _node.type,
properties: _node.properties,
events: _node.events,
uuid: generateUUID(),
};
}
return data
}
export const projectStore = {
state: {
id: '',
......@@ -46,7 +90,10 @@ export const projectStore = {
dataMapping: [],
processes: [],
customs: [],
mock: [],
dependencies: {},
},
mockServeEnabled: false,
activeComponent: {},
activeComponentCopy: {}, // 当前选中节点的镜像,用来处理拖拽时数据变化频繁的问题
activeIdList: [],
......@@ -54,11 +101,15 @@ export const projectStore = {
dragUUID: '',
dirty: false,
operateStack: [],
stackIndex: 0
stackIndex: 0,
packages: {},
},
mutations: {
setDirty(state, dirty = true) {
state.dirty = dirty;
},
makeDirty() {
},
updateProject(state, project) {
const {id, name, creator, data} = project;
......@@ -68,7 +119,7 @@ export const projectStore = {
const localData = state.data;
if (data) {
const {views, assets, dataMapping, processes, options, customs,} = JSON.parse(data);
const {views, assets, dataMapping, processes, options, customs, mock, dependencies} = typeof data === 'string' ? JSON.parse(data) : data;
Vue.set(localData, 'options', options || getDefaultOptions());
Vue.set(localData, 'views', views || []);
......@@ -76,6 +127,8 @@ export const projectStore = {
Vue.set(localData, 'dataMapping', dataMapping || []);
Vue.set(localData, 'processes', processes || []);
Vue.set(localData, 'customs', customs || []);
Vue.set(localData, 'mock', mock || []);
Vue.set(localData, 'dependencies', dependencies || {});
} else {
Vue.set(localData, 'options', getDefaultOptions());
Vue.set(localData, 'views', []);
......@@ -83,7 +136,28 @@ export const projectStore = {
Vue.set(localData, 'dataMapping', []);
Vue.set(localData, 'processes', []);
Vue.set(localData, 'customs', []);
Vue.set(localData, 'mock', []);
Vue.set(localData, 'dependencies', {});
}
state.mockServeEnabled = getMockServeEnabled(id);
updateMock(localData.mock);
},
modifyOptions(state, value) {
state.data.options = value;
},
modifyEnv(state, value) {
state.data.options.env = value;
},
modifyDataMapping(state, value) {
state.data.dataMapping = value;
},
modifyCustoms(state, value) {
console.log(value);
state.data.customs = value;
},
modifyProjectDetails(state) {
},
/**
* 激活组件
......@@ -280,6 +354,46 @@ export const projectStore = {
const index = parentChildren.indexOf(node);
parentChildren.splice(index, 1);
},
copyNode(state, {node, parentNode}) {
let _node1 = node
copyNodeCatch = _node1;
//localStorage.copyNodeCatch=_node1;
// let _node=copyBaseRoot(_node1);
// setUUIDForAllChildren(_node)
// if (parentNode) {
// if (!parentNode.children) {
// Vue.set(parentNode, 'children', []);
// }
// parentNode.children.push(_node);
// } else {
// state.data.views.push(_node);
// }
},
pasteNode(state, {node, parentNode, pasteState}) {
let _node1 = copyNodeCatch;
console.log(copyNodeCatch)
if (_node1) {
let _node = copyBaseRoot(_node1);
setUUIDForAllChildren(_node)
if (pasteState == 1) {
parentNode = parentNode;
} else {
parentNode = node;
}
if (parentNode) {
if (!parentNode.children) {
Vue.set(parentNode, 'children', []);
}
parentNode.children.push(_node);
} else {
state.data.views.push(_node);
}
} else {
console.warn("请先选择一个节点进行复制")
}
},
importAssets(state, assets) {
state.data.assets.push(...assets);
},
......@@ -292,6 +406,13 @@ export const projectStore = {
uuid: generateUUID(),
})
},
replaceAsset(state, {uuid, url}) {
for (let asset of state.data.assets) {
if (asset.uuid === uuid) {
asset.url = url;
}
}
},
deleteAsset(state, uuid) {
const {assets} = state.data;
for (let i = 0, li = assets.length; i < li; i++) {
......@@ -310,50 +431,15 @@ export const projectStore = {
},
addDataMapping(state, link) {
if (link) {
state.data.dataMapping.push(link);
} else {
state.data.dataMapping.push({
name: '',
path: '',
});
}
},
deleteDataMapping(state, index) {
state.data.dataMapping.splice(index, 1);
},
modifyDataMapping(state) {
modifyMocks(state, mocks) {
state.data.mock = mocks;
updateMock(mocks);
},
addEnvMapping(state, link) {
if (!state.data.options.env) {
Vue.set(state.data.options, 'env', []);
}
if (link) {
state.data.options.env.push(link);
} else {
state.data.options.env.push({
name: '',
value: '',
});
}
},
deleteEnvMapping(state, index) {
state.data.options.env.splice(index, 1);
},
modifyEnvMapping(state) {
},
modifyCustoms(state, customs) {
state.data.customs = customs;
},
setMockServeEnabled(state, enabled) {
state.mockServeEnabled = enabled;
}
},
getters: {
project(state) {
......@@ -408,7 +494,7 @@ export const projectStore = {
/**
* 返回Array格式的views
*/
views: state => {
views(state) {
// 如果有选中的节点,则展示对应的视图组
// 否则展示第一个视图组
let _view = [];
......@@ -419,29 +505,39 @@ export const projectStore = {
}
return _view;
}
},
assets(state) {
return state.data.assets;
},
getProcess: state => behavior => {
return findProcess(behavior.meta, state.data.processes);
},
},
actions: {
saveToLocal({getters, commit}) {
async saveToLocal({getters, commit}) {
const {project} = getters;
localStorage.setItem('project-' + project.id, JSON.stringify(project));
await db.set(storeName, {id: project.id, data: JSON.stringify(project)});
//localStorage.setItem('project-' + project.id, JSON.stringify(project));
commit('setDirty', true);
},
localVersionExist({commit}, projectID) {
let json = localStorage.getItem('project-' + projectID);
async localVersionExist({commit}, projectID) {
let json = await db.get(storeName, projectID);
//let json = localStorage.getItem('project-' + projectID);
return !!json;
},
loadFromLocal({commit}, projectID) {
let json = localStorage.getItem('project-' + projectID);
async loadFromLocal({commit}, projectID) {
let json = await db.get(storeName, projectID);
//let json = localStorage.getItem('project-' + projectID);
if (json) {
const project = JSON.parse(json);
const project = JSON.parse(json.data);
commit('updateProject', project);
commit('setDirty', true);
}
},
deleteLocalVersion({state, commit}, projectID) {
localStorage.removeItem('project-' + projectID);
async deleteLocalVersion({state, commit}, projectID) {
await db.remove(storeName, projectID);
//localStorage.removeItem('project-' + projectID);
commit('setDirty', false);
},
async loadFromRemote({commit, dispatch}, projectID) {
......@@ -453,14 +549,34 @@ export const projectStore = {
throw new Error('Project does not exist')
}
},
async saveToRemote({state, dispatch, getters}) {
await projectApi.saveOne(getters.project);
async loadFromDataUrl({commit, dispatch}, {project, dataUrl}) {
const projectData = await projectApi.fetchOneFromDataUrl(dataUrl);
if (projectData) {
project.data = projectData;
commit('updateProject', project);
dispatch('saveToLocal');
} else {
throw new Error('Project does not exist')
}
},
async saveToRemote({state, dispatch, getters}, {remark}) {
await projectApi.saveOne(getters.project, remark);
dispatch('deleteLocalVersion', state.id);
},
async updateProject({commit}, projectID) {
const project = await projectApi.getData(projectID);
commit('updateProject', project);
},
async loadPackages({state, commit}) {
let packages = await db.get('packages', state.id);
},
async resolveDependencies({state}) {
for (let key in state.data.dependencies) {
let version = state.data.dependencies[key];
}
},
/**
* 选中节点
* @param {*} context
......@@ -475,10 +591,13 @@ export const projectStore = {
return getTopView(node.parent);
}
};
let _view = getTopView(data.node);
if (_view && _view.data) {
context.state.activeViews = _view.data.uuid;
if (!data.fromPlayground) {
// 点击视图区域选中节点
// 则需要切换当前节点所在的最顶层视图
let _view = getTopView(data.node);
if (_view && _view.data) {
context.state.activeViews = _view.data.uuid;
}
}
context.commit('activeComponent', data.data);
......@@ -495,7 +614,9 @@ export const projectStore = {
});
if (_props.source) {
if (!_props.width || !_props.height) {
// imageWidth imageHeight
// 设置image类型节点的原图高宽度
if (!_props.imageWidth || !_props.imageHeight) {
let _url = _props.source; //_source.value;
if (_url.indexOf('asset://') === 0) {
let uuid = _url.split('//')[1];
......@@ -505,13 +626,16 @@ export const projectStore = {
let _img = new Image();
_img.src = _url;
_img.onload = function () {
_props.width = _img.width;
_props.height = _img.height;
_props.imageWidth = _img.width;
_props.imageHeight = _img.height;
commit('modifyActiveView', {
properties: _props
});
}
}
} else {
delete _props.imageWidth;
delete _props.imageHeight;
}
commit('modifyActiveView', {
......@@ -532,11 +656,10 @@ export const projectStore = {
modifyActiveView({commit}, view) {
commit('modifyActiveView', view)
},
/**
* 新增节点脚本
* @param {*} param0
* @param {*} data
* @param {*} script
*/
addNodeScript({commit, state}, script) {
let _scripts = _.cloneDeep(state.activeComponent.scripts || []);
......@@ -571,37 +694,115 @@ export const projectStore = {
});
},
async uploadFiles({commit}, files) {
const failedList = [];
let ps = [];
for (let file of files) {
events.$emit('upload-indicator', true);
ps.push(
uploadFile(file).catch(e => {
}).finally(() => {
events.$emit('upload-indicator', false);
})
);
}
const result = await Promise.all(ps);
const {failedList, result} = await uploadFiles(files);
for (let item of result) {
const {url, __originFile} = item;
commit('addAsset', {url, file: __originFile});
}
/*for (let file of files) {
try {
const {url} = await uploadFile(file);
commit('addAsset', {url, file});
}catch (e) {
failedList.push(file);
}
events.$emit('upload-indicator', false);
}*/
return failedList;
},
async packProject({state}) {
const result = await projectApi.pack(state.id);
async replaceAsset({commit}, {uuid, file}) {
const {failedList, result} = await uploadFiles([file]);
commit('replaceAsset', {uuid, url: result[0].url,});
return failedList;
},
async packProject({state}, debug) {
let packedAssets;
if (!debug) {
packedAssets = await packAssets(state.data.assets);
}
const result = await projectApi.pack(state.id, debug, packedAssets);
console.log(result);
return result;
},
savePreview({state, rootState, getters}) {
const {project} = getters;
const {processes, scripts, customs} = rootState.env;
/*const data = {
processes, scripts, customs,
data: state.data,
};
localStorage.setItem('preview-project-' + project.id, JSON.stringify(data));
localStorage.setItem('preview-ts', Date.now().toString());*/
preprocess(project, state.data, clonePureObj(processes), clonePureObj(scripts), clonePureObj(customs))
},
addBehaviorDirect({state}, {behaviors, alias}) {
let behavior = addBehavior(alias, state.data.processes);
behaviors.push(behavior);
},
deleteBehaviorDirect({state}, {behaviors, index, deleteMeta}) {
let behavior = behaviors.splice(index, 1)[0];
if (deleteMeta) {
deleteProcessMeta(behavior.meta, state.data.processes);
}
},
setMockServeEnabled({state, commit}, enabled) {
localStorage.setItem('mock-enabled-' + state.id, JSON.stringify(enabled));
commit('setMockServeEnabled', enabled);
return enabled;
}
},
};
async function uploadFiles(files) {
const failedList = [];
let ps = [];
for (let file of files) {
events.$emit('upload-indicator', true);
ps.push(
uploadFile(file).catch(e => {
failedList.push(file);
}).finally(() => {
events.$emit('upload-indicator', false);
})
);
}
const result = await Promise.all(ps);
return {
failedList,
result,
};
}
async function packAssets(assets) {
let failedList = [];
let newAssets = assets.concat();
await packImages(newAssets);
for (let asset of newAssets) {
if (asset.file) {
const {url} = await uploadFile(asset.file, true).catch(e => {
failedList.push(asset);
});
let sheetConfig = {
file: url,
type: 'sheet',
frames: asset.frames,
};
let sheetConfigFile = new File([JSON.stringify(sheetConfig)], 'sheet.json', {type: 'plain/text'});
const {url: sheetConfigUrl} = await uploadFile(sheetConfigFile).catch(e => {
failedList.push(asset);
});
asset.url = sheetConfigUrl;
asset.uuid = generateUUID();
delete asset.file;
delete asset.frames;
}
}
return newAssets;
}
export async function updateMock(mocks) {
await db.clear('mock');
for (let mock of mocks) {
db.set('mock', mock);
}
}
......@@ -4,30 +4,43 @@
import {projectApi} from "../../api";
export const projectsStore = {
state: [],
state: {
projectCount: 0,
projects: [],
},
mutations: {
updateProjects(state, projects) {
state.splice(0);
state.push(...projects);
updateProjects(state, {projects, projectCount}) {
state.projects.splice(0);
state.projects.push(...projects);
state.projectCount = projectCount;
},
addProject(state, project) {
state.unshift(project);
state.projects.unshift(project);
},
deleteProject(state, projectID) {
for (let i = 0, li = state.length; i < li; i++) {
const item = state[i];
const {projects} = state;
for (let i = 0, li = projects.length; i < li; i++) {
const item = projects[i];
if (item.id === projectID) {
state.splice(i, 1);
projects.splice(i, 1);
break;
}
}
},
},
actions: {
async updateProjects({commit}) {
const projects = await projectApi.fetchAll();
commit('updateProjects', projects);
async fetchProjects({commit, state}, {currentPage, pageSize, onlyMine}) {
const data = await projectApi.fetchAll(currentPage, pageSize, onlyMine);
commit('updateProjects', data);
},
async fetchProject({commit}, projectId) {
return await projectApi.fetchOne(projectId);
},
async fetchHistory({commit}, {projectID, currentPage, pageSize}) {
return await projectApi.fetchHistory(projectID, currentPage, pageSize);
},
async createProject({commit}, data) {
const project = await projectApi.createOne(data);
......
......@@ -3,7 +3,7 @@
*/
export const template =
`<!DOCTYPE html>
`<!DOCTYPE html>
<html lang="en">
<head>
......@@ -17,27 +17,28 @@ content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, u
<meta name="x5-fullscreen" content="true"/>
<meta name="360-fullscreen" content="true"/>
<style>
html,
body {
padding: 0;
margin: 0;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
background-color: white;
}
</style>
html,
body {
padding: 0;
margin: 0;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
background-color: white;
}
</style>
</head>
<body>
<div id="$CONTAINER_ID$" style="line-height:0;font-size:0"></div>
<script src="http://10.10.94.134:4002/dist/index.js"></script>
<script src="${process.env.NODE_ENV === 'development' ? 'http://10.10.92.100:4002/debug/engine.js' : 'http://yun.duiba.com.cn/editor/zeroing/libs/engine.269ce0ee2f951a11e004eee2df2fa1d875fa0b62.js'}"></script>
$SCRIPTS$
<script>
engine.launch('//yun.duiba.com.cn/aurora/$VERSION$-data.json');
</script>
engine.launch('//yun.duiba.com.cn/aurora/$VERSION$-data.json');
</script>
</body>
</html>
`;
......@@ -54,108 +54,6 @@
.assets-scrollbar {
flex: 1;
.file-list {
display: flex;
flex: 1;
flex-direction: row;
flex-wrap: wrap;
.file-item {
width: 60px;
padding: 5px;
color: $--color-text-regular;
&:hover {
& > .icon > .operate-bar {
display: block;
}
& > .name > .full-name {
display: block;
}
}
.icon {
width: 100%;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.thumbnail {
max-width: 60px;
max-height: 60px;
}
.file-icon {
font-size: 60px;
}
.operate-bar {
display: none;
position: absolute;
right: 1px;
bottom: 1px;
.el-button {
padding: 2px;
margin-left: 2px;
}
}
}
.name {
text-align: center;
font-size: 12px;
position: relative;
margin-top: 2px;
span {
word-break: break-all;
overflow:hidden;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
}
.full-name {
display: none;
position: absolute;
top: 0;
left: 0;
background-color: $--color-text-secondary;
color: $--color-white;
border-radius: 5px;
padding: 2px;
z-index: 2;
width: 95%;
line-height: 17px;
}
}
}
.file-uploader {
width: 60px;
height: 60px;
margin: 4px;
border: 1px dashed $--color-text-secondary;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.file-uploader-icon {
font-size: 28px;
color: $--color-text-regular;
width: 60px;
height: 60px;
line-height: 60px;
text-align: center;
}
}
}
}
}
......@@ -166,6 +64,114 @@
}
}
.file-list {
display: flex;
flex: 1;
flex-direction: row;
flex-wrap: wrap;
.file-item {
width: 60px;
padding: 5px;
color: $--color-text-regular;
&:hover {
& > .icon > .operate-bar {
visibility: visible;
}
& > .name > .full-name {
visibility: visible;
}
}
.icon {
width: 100%;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.thumbnail {
max-width: 60px;
max-height: 60px;
}
.file-icon {
font-size: 60px;
}
.operate-bar {
visibility: hidden;
position: absolute;
right: 1px;
bottom: 1px;
display: flex;
.el-button + .el-button{
margin-left: 2px;
}
.el-button {
padding: 2px;
}
}
}
.name {
text-align: center;
font-size: 12px;
position: relative;
margin-top: 2px;
span {
word-break: break-all;
overflow:hidden;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
}
.full-name {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
background-color: $--color-text-secondary;
color: $--color-white;
border-radius: 5px;
padding: 2px;
z-index: 2;
width: 95%;
line-height: 17px;
}
}
}
.file-uploader {
width: 60px;
height: 60px;
margin: 4px;
border: 1px dashed $--color-text-secondary;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.file-uploader-icon {
font-size: 28px;
color: $--color-text-regular;
width: 60px;
height: 60px;
line-height: 60px;
text-align: center;
}
}
}
.assets-show {
width: 100%;
height: 100%;
......
......@@ -2,15 +2,7 @@
$dock-pin-width: 9px;
.behavior-editor-dialog {
display: flex;
flex-direction: column;
.el-dialog__body {
flex: 1;
padding: 5px;
}
.process-pane {
......@@ -35,6 +27,13 @@ $dock-pin-width: 9px;
text-overflow: ellipsis;
overflow: hidden;
font-size: 14px;
.current-node {
background-color: $--color-text-secondary;
color: $--color-white;
border-radius: 5px;
padding: 0 2px;
}
}
.edit-button {
......@@ -56,14 +55,28 @@ $dock-pin-width: 9px;
display: flex;
flex-direction: column;
.edit-path {
padding: 5px;
height: 14px;
.el-button {
padding: 3px;
margin-top: 2px;
margin-right: 2px;
}
border-bottom: 1px solid $--border-color-light;
.top-bar {
display: flex;
justify-content: space-between;
align-items: flex-start;
.edit-path {
flex: 1;
padding: 5px;
height: 14px;
border-bottom: 1px solid $--border-color-light;
}
}
.operate-bar{
.operate-bar {
padding: 3px;
border-bottom: 1px solid $--border-color-light;
......@@ -100,6 +113,7 @@ $dock-pin-width: 9px;
visibility: visible;
}
}
/*&:hover {
border-color: $block-border-hover-background-color;
......@@ -126,6 +140,10 @@ $dock-pin-width: 9px;
align-self: flex-end;
visibility: hidden;
.el-link {
padding: 3px 2px 2px 2px;
}
.el-link + .el-link {
margin-left: 5px;
}
......@@ -141,7 +159,7 @@ $dock-pin-width: 9px;
color: white;
display: flex;
i{
i {
display: block;
//color: $--color-warning;
margin-right: 3px;
......@@ -299,8 +317,31 @@ $dock-pin-width: 9px;
.scrollbar-view {
padding-right: 10px;
.input-wrapper {
.label {
p {
margin: 0;
//text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.alias {
line-height: 16px;
font-size: 14px;
}
.property-name {
line-height: 12px;
font-size: 12px;
color: $--color-text-secondary;
}
}
}
}
.el-form-item__content {
display: flex;
justify-content: flex-end;
......@@ -320,8 +361,9 @@ $dock-pin-width: 9px;
}
.meta-editor-wrapper {
height: 40vh;
//height: 40vh;
display: flex;
height: 100%;
padding: 5px;
flex-direction: column;
......@@ -332,6 +374,15 @@ $dock-pin-width: 9px;
.script-editor {
flex: 1;
}
.vue-codemirror {
flex: 1;
height: 0;
}
.CodeMirror {
height: 100%;
}
}
.props-editor-dialog {
......@@ -389,3 +440,23 @@ $dock-pin-width: 9px;
margin-top: 5px;
}
}
.property-name-popover{
p {
margin: 0;
//text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.alias {
line-height: 16px;
font-size: 14px;
}
.property-name {
line-height: 12px;
font-size: 12px;
color: $--color-text-secondary;
}
}
@import "var";
.code-sync-indicator {
padding: 5px;
cursor: pointer;
.indicator {
background-color: red;
width: 10px;
height: 10px;
border-radius: 50%;
}
.indicator.enabled {
background-color: limegreen;
}
}
.bottom-bar{
height: 20px;
display: flex;
align-items: center;
font-size: 12px;
color: $--color-text-secondary;
padding: 0 5px;
.editor-status{
}
.file-upload-indicator {
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@
@import "views";
@import "assets";
@import "behavior";
@import "bottom-bar";
.editor {
display: flex;
......@@ -25,10 +26,6 @@
}
}
.file-upload-indicator{
color: $--color-text-secondary;
}
.right-part {
color: $--color-text-regular;
line-height: 15px;
......@@ -41,6 +38,7 @@
.pane-container {
flex: 1;
height: 0;
}
}
......@@ -56,11 +54,11 @@
align-self: flex-start;
}
.bottom-bar{
.bottom-bar {
align-self: flex-end;
}
.list{
.list {
display: flex;
flex-direction: column;
flex: 1;
......@@ -100,21 +98,46 @@
}
.details-dialog {
.operate-bar{
.operate-bar {
display: flex;
justify-content: flex-end;
padding: 5px;
}
.tabs {
height: 100%;
display: flex;
flex-direction: column;
.el-tabs__content {
flex: 1;
& > div {
height: 100%;
}
}
}
.project-editor {
height: 100%;
display: flex;
flex-direction: column;
.project-scrollbar {
flex: 1;
}
}
.scrollbar {
height: 40vh;
height: 100%;
.view {
padding-right: 10px;
.custom-module-list{
.custom-module-list {
display: flex;
flex-direction: column;
.item + .item {
margin-top: 5px;
}
......@@ -122,3 +145,29 @@
}
}
}
.mock-editor-dialog {
.mock-editor-view {
height: 100%;
display: flex;
flex-direction: column;
.enabled-switch {
align-self: flex-end;
}
.add-button {
align-self: flex-start;
}
.mock-table {
flex: 1;
}
}
.dialog-footer {
display: flex;
justify-content: space-between;
}
}
......@@ -15,6 +15,7 @@
.editor-name {
font-size: 50px;
color: $--color-primary;
margin: 10px 0;
}
.editor-version {
......@@ -25,6 +26,7 @@
.project-list {
flex: 1;
margin-bottom: 10px;
height: 0;
}
.project-info {
......@@ -43,4 +45,32 @@
.project-detail {
}
}
\ No newline at end of file
.bottom-bar {
display: flex;
justify-content: space-between;
}
}
.project-history-table {
height: 50vh;
display: flex;
flex-direction: column;
.popover-remark {
width: 100%;
display: block;
.short-remark {
width: 100%;
display: block;
white-space: nowrap;
overflow: hidden;
}
}
.pagination {
align-self: flex-end;
margin-top: 5px;
}
}
/**
* Created by rockyl on 2019-09-18.
*/
@import "./var.scss";
@import "./base.scss";
@import "./home.scss";
@import "./editor.scss";
@import "./playground.scss";
@import "./inspector.scss";
@import "~element-ui/packages/theme-chalk/src/index.scss";
.alpha-image-background{
background-image: url('data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAiUlEQVQ4jZ1TSxLFIAhLOp7C+19NjyFvlQ7lUUvLRgXzgVGOMQyb6L3/5cwMJAEAxw6sy3FP8tw/EkhJqp7klQMBtKpWcpC1oVp7IoiqviXg4xBFRPL7EM/6WsviYDxwzrkVaBHsz5U4MlWfKxHEySpXIdk6qLRzcXBHVnKQfZRKCy1Ty979XfwApOBe0rB0KiIAAAAASUVORK5CYII=');
}
.flex-dialog {
display: flex;
flex-direction: column;
.el-dialog__body {
flex: 1;
height: 0;
padding: 5px;
}
}
.el-tree {
background: transparent;
}
......@@ -23,15 +36,18 @@
overflow-y: hidden;
}
.el-tree-node__content > .el-tree-node__expand-icon{
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 6px 0;
}
.el-input-number.is-without-controls .el-input__inner{
padding: 0 5px;
}
.el-input__inner {
padding: 0 5px;
}
.el-input--suffix .el-input__inner{
.el-input--suffix .el-input__inner {
padding-right: 5px;
}
......@@ -43,13 +59,13 @@
height: 100%;
}
.el-tabs--border-card > .el-tabs__content{
.el-tabs--border-card > .el-tabs__content {
padding: 5px 0 5px 5px;
}
.el-input-number.is-controls-right .el-input__inner {
padding-left: 5px;
padding-right: 40px;
padding-right: 25px;
}
.el-input-number--mini .el-input-number__increase, .el-input-number--mini .el-input-number__decrease {
......@@ -60,3 +76,12 @@
padding-left: 5px;
padding-right: 5px;
}
.CodeMirror-hints, .CodeMirror-lint-tooltip{
z-index: 3000 !important;
}
.el-button.micro {
padding: 3px;
align-self: center;
}
@import "var";
.inspector-tabs {
flex: 1;
border: 0 !important;
......@@ -6,6 +8,12 @@
&>:last-child{
flex: 1;
height: 0;
}
.el-collapse-item__header{
height: 25px;
line-height: 25px;
}
.el-tabs__item {
......@@ -45,12 +53,93 @@
}
}
.zero-inspector-behavior-form {
.zero-inspector-behavior {
height: 100%;
display: flex;
flex-direction: column;
.add-trigger{
align-self: flex-start;
}
.scrollbar{
margin-top: 5px;
height: 100%;
}
.el-button + .el-button {
margin-left: 5px;
}
.trigger-list{
color: $--color-text-regular;
font-size: 12px;
padding-right: 10px;
.trigger-item + .trigger-item {
margin-top: 5px;
}
.trigger-item {
border: 1px solid lightgray;
border-radius: 3px;
padding: 3px;
.el-button{
padding: 3px;
}
.top-bar{
display: flex;
justify-content: space-between;
align-items: center;
.name{
color: $--color-primary;
font-size: 14px;
font-weight: bold;
flex: 1;
cursor: default;
}
}
.behavior-list {
.behavior-item {
&:hover {
& > .delete-button {
visibility: visible;
}
}
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 2px;
.name-field{
flex: 1;
margin-left: 3px;
}
.missing-behavior{
color: $--color-danger;
}
.name-input {
border: 0;
outline: transparent;
}
.edit-button {
}
.delete-button {
visibility: hidden;
}
}
}
}
}
}
}
......@@ -17,6 +17,10 @@
&::-webkit-scrollbar {
display: none;
}
.active {
border: 2px dashed rgb(20, 100, 206);
}
}
// .zero-playground-draw-panel{
// min-height: 1200px;
......@@ -60,6 +64,3 @@
cursor: default;
user-select: none;
}
.active {
border: 2px dashed rgb(20, 100, 206);
}
/**
* Based on the Public Domain MaxRectanglesBinPack.cpp source by Jukka Jylänki
* https://github.com/juj/RectangleBinPack/
*
* Based on C# port by Sven Magnus
* http://unifycommunity.com/wiki/index.php?title=MaxRectanglesBinPack
*
* Based on ActionScript3 by DUZENGQIANG
* http://www.duzengqiang.com/blog/post/971.html
*
* Ported to javascript by 06wj
* https://github.com/06wj/MaxRectsBinPack
*/
/**
* Rect
* @param {Number} x 矩形坐标x
* @param {Number} y 矩形坐标y
* @param {Number} width 矩形宽
* @param {Number} height 矩形高
*/
function Rect(x, y, width, height) {
this.x = x || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
}
Rect.prototype = {
constructor: Rect,
/**
* clone 复制
* @return {Rect}
*/
clone: function () {
return new Rect(this.x, this.y, this.width, this.height);
}
};
Rect.isContainedIn = function (a, b) {
return a.x >= b.x && a.y >= b.y
&& a.x + a.width <= b.x + b.width
&& a.y + a.height <= b.y + b.height;
};
var BestShortSideFit = 0; ///< -BSSF: Positions the Rectangle against the short side of a free Rectangle into which it fits the best.
var BestLongSideFit = 1; ///< -BLSF: Positions the Rectangle against the long side of a free Rectangle into which it fits the best.
var BestAreaFit = 2; ///< -BAF: Positions the Rectangle into the smallest free Rectangle into which it fits.
var BottomLeftRule = 3; ///< -BL: Does the Tetris placement.
var ContactPointRule = 4; ///< -CP: Choosest the placement where the Rectangle touches other Rectangles as much as possible.
/**
* MaxRectanglesBinPack
* @param {Number} width 容器宽度
* @param {Number} height 容器高度
* @param {Boolean} allowRotate 是否允许旋转
*/
export default function MaxRectsBinPack(width, height, allowRotate) {
this.binWidth = 0;
this.binHeight = 0;
this.allowRotate = false;
this.usedRectangles = [];
this.freeRectangles = [];
this.init(width, height, allowRotate);
}
MaxRectsBinPack.prototype = {
constructor: MaxRectsBinPack,
/**
* 初始化
* @param {Number} width 容器宽度
* @param {Number} height 容器高度
* @param {Boolean} allowRotate 是否允许旋转
*/
init: function (width, height, allowRotate) {
this.binWidth = width;
this.binHeight = height;
this.allowRotate = allowRotate || false;
this.usedRectangles.length = 0;
this.freeRectangles.length = 0;
this.freeRectangles.push(new Rect(0, 0, width, height));
},
/**
* insert a new rect
* @param {Number} width 矩形宽
* @param {Number} height 矩形高
* @param {Number} method 分配方法 0~4
* @return {Rect}
*/
insert: function (width, height, method) {
var newNode = new Rect();
var score1 = {
value: 0
};
var score2 = {
value: 0
};
method = method || 0;
switch (method) {
case BestShortSideFit:
newNode = this._findPositionForNewNodeBestShortSideFit(width, height, score1, score2);
break;
case BottomLeftRule:
newNode = this._findPositionForNewNodeBottomLeft(width, height, score1, score2);
break;
case ContactPointRule:
newNode = this._findPositionForNewNodeContactPoint(width, height, score1);
break;
case BestLongSideFit:
newNode = this._findPositionForNewNodeBestLongSideFit(width, height, score2, score1);
break;
case BestAreaFit:
newNode = this._findPositionForNewNodeBestAreaFit(width, height, score1, score2);
break;
}
if (newNode.height === 0) {
return newNode;
}
this._placeRectangle(newNode);
return newNode;
},
/**
* 插入一组矩形
* @param {Array} rectangles 矩形数组
* @param {Number} method 分配方法 0~4
* @return {Array} 成功插入的数组
*/
insertRects: function (rectangles, method) {
var res = [];
while (rectangles.length > 0) {
var bestScore1 = Infinity;
var bestScore2 = Infinity;
var bestRectangleIndex = -1;
var bestNode = new Rect();
for (var i = 0; i < rectangles.length; i++) {
var score1 = {
value: 0
};
var score2 = {
value: 0
};
var newNode = this._scoreRectangle(rectangles[i].width, rectangles[i].height, method, score1, score2);
if (score1.value < bestScore1 || (score1.value == bestScore1 && score2.value < bestScore2)) {
bestScore1 = score1.value;
bestScore2 = score2.value;
bestNode = newNode;
bestRectangleIndex = i;
}
}
if (bestRectangleIndex == -1) {
return res;
}
this._placeRectangle(bestNode);
var rect = rectangles.splice(bestRectangleIndex, 1)[0];
rect.x = bestNode.x;
rect.y = bestNode.y;
res.push(rect);
}
return res;
},
_placeRectangle: function (node) {
var numRectanglesToProcess = this.freeRectangles.length;
for (var i = 0; i < numRectanglesToProcess; i++) {
if (this._splitFreeNode(this.freeRectangles[i], node)) {
this.freeRectangles.splice(i, 1);
i--;
numRectanglesToProcess--;
}
}
this._pruneFreeList();
this.usedRectangles.push(node);
},
_scoreRectangle: function (width, height, method, score1, score2) {
var newNode = new Rect();
score1.value = Infinity;
score2.value = Infinity;
switch (method) {
case BestShortSideFit:
newNode = this._findPositionForNewNodeBestShortSideFit(width, height, score1, score2);
break;
case BottomLeftRule:
newNode = this._findPositionForNewNodeBottomLeft(width, height, score1, score2);
break;
case ContactPointRule:
newNode = this._findPositionForNewNodeContactPoint(width, height, score1);
// todo: reverse
score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better.
break;
case BestLongSideFit:
newNode = this._findPositionForNewNodeBestLongSideFit(width, height, score2, score1);
break;
case BestAreaFit:
newNode = this._findPositionForNewNodeBestAreaFit(width, height, score1, score2);
break;
}
// Cannot fit the current Rectangle.
if (newNode.height === 0) {
score1.value = Infinity;
score2.value = Infinity;
}
return newNode;
},
_occupancy: function () {
var usedRectangles = this.usedRectangles;
var usedSurfaceArea = 0;
for (var i = 0; i < usedRectangles.length; i++) {
usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
}
return usedSurfaceArea / (this.binWidth * this.binHeight);
},
_findPositionForNewNodeBottomLeft: function (width, height, bestY, bestX) {
var freeRectangles = this.freeRectangles;
var bestNode = new Rect();
//memset(bestNode, 0, sizeof(Rectangle));
bestY.value = Infinity;
var rect;
var topSideY;
for (var i = 0; i < freeRectangles.length; i++) {
rect = freeRectangles[i];
// Try to place the Rectangle in upright (non-flipped) orientation.
if (rect.width >= width && rect.height >= height) {
topSideY = rect.y + height;
if (topSideY < bestY.value || (topSideY == bestY.value && rect.x < bestX.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = width;
bestNode.height = height;
bestY.value = topSideY;
bestX.value = rect.x;
}
}
if (this.allowRotate && rect.width >= height && rect.height >= width) {
topSideY = rect.y + width;
if (topSideY < bestY.value || (topSideY == bestY.value && rect.x < bestX.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = height;
bestNode.height = width;
bestY.value = topSideY;
bestX.value = rect.x;
}
}
}
return bestNode;
},
_findPositionForNewNodeBestShortSideFit: function (width, height, bestShortSideFit, bestLongSideFit) {
var freeRectangles = this.freeRectangles;
var bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rectangle));
bestShortSideFit.value = Infinity;
var rect;
var leftoverHoriz;
var leftoverVert;
var shortSideFit;
var longSideFit;
for (var i = 0; i < freeRectangles.length; i++) {
rect = freeRectangles[i];
// Try to place the Rectangle in upright (non-flipped) orientation.
if (rect.width >= width && rect.height >= height) {
leftoverHoriz = Math.abs(rect.width - width);
leftoverVert = Math.abs(rect.height - height);
shortSideFit = Math.min(leftoverHoriz, leftoverVert);
longSideFit = Math.max(leftoverHoriz, leftoverVert);
if (shortSideFit < bestShortSideFit.value || (shortSideFit == bestShortSideFit.value && longSideFit < bestLongSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit.value = shortSideFit;
bestLongSideFit.value = longSideFit;
}
}
var flippedLeftoverHoriz;
var flippedLeftoverVert;
var flippedShortSideFit;
var flippedLongSideFit;
if (this.allowRotate && rect.width >= height && rect.height >= width) {
flippedLeftoverHoriz = Math.abs(rect.width - height);
flippedLeftoverVert = Math.abs(rect.height - width);
flippedShortSideFit = Math.min(flippedLeftoverHoriz, flippedLeftoverVert);
flippedLongSideFit = Math.max(flippedLeftoverHoriz, flippedLeftoverVert);
if (flippedShortSideFit < bestShortSideFit.value || (flippedShortSideFit == bestShortSideFit.value && flippedLongSideFit < bestLongSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit.value = flippedShortSideFit;
bestLongSideFit.value = flippedLongSideFit;
}
}
}
return bestNode;
},
_findPositionForNewNodeBestLongSideFit: function (width, height, bestShortSideFit, bestLongSideFit) {
var freeRectangles = this.freeRectangles;
var bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rectangle));
bestLongSideFit.value = Infinity;
var rect;
var leftoverHoriz;
var leftoverVert;
var shortSideFit;
var longSideFit;
for (var i = 0; i < freeRectangles.length; i++) {
rect = freeRectangles[i];
// Try to place the Rectangle in upright (non-flipped) orientation.
if (rect.width >= width && rect.height >= height) {
leftoverHoriz = Math.abs(rect.width - width);
leftoverVert = Math.abs(rect.height - height);
shortSideFit = Math.min(leftoverHoriz, leftoverVert);
longSideFit = Math.max(leftoverHoriz, leftoverVert);
if (longSideFit < bestLongSideFit.value || (longSideFit == bestLongSideFit.value && shortSideFit < bestShortSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit.value = shortSideFit;
bestLongSideFit.value = longSideFit;
}
}
if (this.allowRotate && rect.width >= height && rect.height >= width) {
leftoverHoriz = Math.abs(rect.width - height);
leftoverVert = Math.abs(rect.height - width);
shortSideFit = Math.min(leftoverHoriz, leftoverVert);
longSideFit = Math.max(leftoverHoriz, leftoverVert);
if (longSideFit < bestLongSideFit.value || (longSideFit == bestLongSideFit.value && shortSideFit < bestShortSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit.value = shortSideFit;
bestLongSideFit.value = longSideFit;
}
}
}
return bestNode;
},
_findPositionForNewNodeBestAreaFit: function (width, height, bestAreaFit, bestShortSideFit) {
var freeRectangles = this.freeRectangles;
var bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rectangle));
bestAreaFit.value = Infinity;
var rect;
var leftoverHoriz;
var leftoverVert;
var shortSideFit;
var areaFit;
for (var i = 0; i < freeRectangles.length; i++) {
rect = freeRectangles[i];
areaFit = rect.width * rect.height - width * height;
// Try to place the Rectangle in upright (non-flipped) orientation.
if (rect.width >= width && rect.height >= height) {
leftoverHoriz = Math.abs(rect.width - width);
leftoverVert = Math.abs(rect.height - height);
shortSideFit = Math.min(leftoverHoriz, leftoverVert);
if (areaFit < bestAreaFit.value || (areaFit == bestAreaFit.value && shortSideFit < bestShortSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit.value = shortSideFit;
bestAreaFit = areaFit;
}
}
if (this.allowRotate && rect.width >= height && rect.height >= width) {
leftoverHoriz = Math.abs(rect.width - height);
leftoverVert = Math.abs(rect.height - width);
shortSideFit = Math.min(leftoverHoriz, leftoverVert);
if (areaFit < bestAreaFit.value || (areaFit == bestAreaFit.value && shortSideFit < bestShortSideFit.value)) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit.value = shortSideFit;
bestAreaFit.value = areaFit;
}
}
}
return bestNode;
},
/// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
_commonIntervalLength: function (i1start, i1end, i2start, i2end) {
if (i1end < i2start || i2end < i1start) {
return 0;
}
return Math.min(i1end, i2end) - Math.max(i1start, i2start);
},
_contactPointScoreNode: function (x, y, width, height) {
var usedRectangles = this.usedRectangles;
var score = 0;
if (x == 0 || x + width === this.binWidth)
score += height;
if (y == 0 || y + height === this.binHeight)
score += width;
var rect;
for (var i = 0; i < usedRectangles.length; i++) {
rect = usedRectangles[i];
if (rect.x == x + width || rect.x + rect.width == x)
score += this._commonIntervalLength(rect.y, rect.y + rect.height, y, y + height);
if (rect.y == y + height || rect.y + rect.height == y)
score += this._commonIntervalLength(rect.x, rect.x + rect.width, x, x + width);
}
return score;
},
_findPositionForNewNodeContactPoint: function (width, height, bestContactScore) {
var freeRectangles = this.freeRectangles;
var bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rectangle));
bestContactScore.value = -1;
var rect;
var score;
for (var i = 0; i < freeRectangles.length; i++) {
rect = freeRectangles[i];
// Try to place the Rectangle in upright (non-flipped) orientation.
if (rect.width >= width && rect.height >= height) {
score = this._contactPointScoreNode(rect.x, rect.y, width, height);
if (score > bestContactScore.value) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = width;
bestNode.height = height;
bestContactScore = score;
}
}
if (this.allowRotate && rect.width >= height && rect.height >= width) {
score = this._contactPointScoreNode(rect.x, rect.y, height, width);
if (score > bestContactScore.value) {
bestNode.x = rect.x;
bestNode.y = rect.y;
bestNode.width = height;
bestNode.height = width;
bestContactScore.value = score;
}
}
}
return bestNode;
},
_splitFreeNode: function (freeNode, usedNode) {
var freeRectangles = this.freeRectangles;
// Test with SAT if the Rectangles even intersect.
if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
return false;
var newNode;
if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
// New node at the top side of the used node.
if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
newNode = freeNode.clone();
newNode.height = usedNode.y - newNode.y;
freeRectangles.push(newNode);
}
// New node at the bottom side of the used node.
if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
newNode = freeNode.clone();
newNode.y = usedNode.y + usedNode.height;
newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
freeRectangles.push(newNode);
}
}
if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
// New node at the left side of the used node.
if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
newNode = freeNode.clone();
newNode.width = usedNode.x - newNode.x;
freeRectangles.push(newNode);
}
// New node at the right side of the used node.
if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
newNode = freeNode.clone();
newNode.x = usedNode.x + usedNode.width;
newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
freeRectangles.push(newNode);
}
}
return true;
},
_pruneFreeList: function () {
var freeRectangles = this.freeRectangles;
for (var i = 0; i < freeRectangles.length; i++)
for (var j = i + 1; j < freeRectangles.length; j++) {
if (Rect.isContainedIn(freeRectangles[i], freeRectangles[j])) {
freeRectangles.splice(i, 1);
break;
}
if (Rect.isContainedIn(freeRectangles[j], freeRectangles[i])) {
freeRectangles.splice(j, 1);
}
}
}
};
import _ from 'lodash';
import properties from './properties';
export const componentsMap = [
{
label: '文本',
value: 'label'
},
{
label: '图片',
value: 'image'
},
{
label: '视图',
value: 'node'
},
{
label: '矩形',
value: 'rect'
},
{
label: '圆形',
value: 'circle'
},
{
label: '滚动视图',
value: 'scrollView'
},
];
// 属性的计算方法
const propsComputeRules = {
x: 'add',
......@@ -66,7 +39,7 @@ const attrShortMapper = {
// 编辑时想拖拽组件需要生成的css属性
// 只需要位置,不需要来源透明度等等
const operatProps = ['x', 'y', 'left', 'top', 'right', 'bottom', 'width', 'height', 'rotation'];
const operatProps = ['x', 'y', 'left', 'top', 'right', 'bottom', 'width', 'height', 'rotation', 'scaleX', 'scaleY'];
const ignoreProps = ['left', 'right', 'top', 'bottom'];
// 属性单位 对照表, 如果是数值的时候需要添加单位
......@@ -107,7 +80,7 @@ function getParentCmps(uuid, list) {
return _self.parent ? parentLoop(_self.parent, list) : [];
}
export { getParentCmps, completeSelfProps };
export {getParentCmps, completeSelfProps};
export const getCmpByUUID = function (uuid, views) {
if (!uuid) {
......@@ -207,10 +180,10 @@ export const styles = {
return ['transform', [`rotate(${value}deg)`]]; //`transform: rotate(${value}deg);`;
case 'background-image':
return ['background-image', [`url(${value})`]]; //`background-image: url(${value});background-position:center;background-size:contain;`;
// case 'scale-x':
// return ['transform', [`scaleX(${value})`]]; //`transform: scaleX(${value});`;
// case 'scale-y':
// return ['transform', [`scaleY(${value})`]]; //`transform: scaleY(${value});`;
case 'scale-x':
return ['transform', [`scaleX(${value})`]]; //`transform: scaleX(${value});`;
case 'scale-y':
return ['transform', [`scaleY(${value})`]]; //`transform: scaleY(${value});`;
case 'visible':
return ['display', [value ? 'block' : 'none']]; // `display: ${value ? 'block' : 'none'};`;
default:
......@@ -265,6 +238,11 @@ export const styles = {
});
}
if (component.type === 'image') {
cmpSelfProps.width = cmpSelfProps.width || cmpSelfProps.imageWidth;
cmpSelfProps.height = cmpSelfProps.height || cmpSelfProps.imageHeight;
}
// 获取拖拽组件的样式,只生成operatProps中的属性
if (onlyOpera) {
_.forIn(cmpSelfProps, (val, key) => {
......@@ -276,14 +254,16 @@ export const styles = {
// 如果节点的width/height未定义,则设置拖拽组件的width/height为0
cmpSelfProps.width = cmpSelfProps.width || 0;
cmpSelfProps.height = cmpSelfProps.height || 0;
}
if (cmpSelfProps.scaleX && cmpSelfProps.width) {
cmpSelfProps.width *= cmpSelfProps.scaleX;
}
if (cmpSelfProps.scaleX && cmpSelfProps.width) {
cmpSelfProps.width *= cmpSelfProps.scaleX;
delete cmpSelfProps.scaleX;
}
if (cmpSelfProps.scaleY && cmpSelfProps.height) {
cmpSelfProps.height *= cmpSelfProps.scaleY;
if (cmpSelfProps.scaleY && cmpSelfProps.height) {
cmpSelfProps.height *= cmpSelfProps.scaleY;
delete cmpSelfProps.scaleY;
}
}
// 把不需要参与样式计算的属性干掉
......@@ -291,10 +271,20 @@ export const styles = {
delete cmpSelfProps[prop];
});
if (component.type === 'label') {
if (component.type === 'label' || component.type === 'textinput') {
// 如果是label类型,把fillColor转换为color
cmpSelfProps.color = cmpSelfProps.fillColor;
delete cmpSelfProps.fillColor;
if (!onlyOpera) {
if (cmpSelfProps.lineType === 'single') {
// word-break: keep-all;white-space: nowrap;
cmpSelfProps.wordBreak = 'keep-all';
cmpSelfProps.whiteSpace = 'nowrap';
} else {
cmpSelfProps.wordBreak = 'break-all';
cmpSelfProps.whiteSpace = 'normal';
}
}
}
// console.log('cmpSelfProps after inherit ', cmpSelfProps);
......@@ -305,38 +295,47 @@ export const styles = {
result += 'border-style: solid;'
}
});
result += `background-position: center;background-size: 100% 100%;`
result += `background-position: center;background-size: 100% 100%;transform-origin: left top;`
if (component.type === 'circle' && !onlyOpera) {
// 如果是circle类型,加圆角
result += 'border-radius: 50%;'
}
if (component.type === 'label' && !onlyOpera) {
// 如果是label类型,禁止自动换行
result += 'word-break: keep-all;white-space: nowrap;'
}
// if (component.type === 'label' && !onlyOpera) {
// 如果是label类型,禁止自动换行
// console.log('cmpSelfProps', cmpSelfProps);
// result += 'word-break: keep-all;white-space: nowrap;'
// }
// console.log('getComponentStyle',component.name, result);
return result;
}
}
export const getCmpProps = function (type) {
const cmpPropsCache = {};
export function getCmpProps(type) {
if (!type) {
return {}
} else {
let _nodeProps = _.cloneDeep(properties.node);
let _typeProps = {};
if (type !== 'node') {
_typeProps = _.cloneDeep(properties[type]);
let cmpProps = cmpPropsCache[type];
if (cmpProps) {
cmpProps = _.cloneDeep(cmpProps);
} else {
let typeProps = properties[type];
let inherits = [_.cloneDeep(typeProps)];
while (typeProps.base) {
typeProps = cmpPropsCache.hasOwnProperty(typeProps.base) ? cmpPropsCache[typeProps.base] : properties[typeProps.base];
inherits.unshift(_.cloneDeep(typeProps));
}
let result = Object.assign({}, ...inherits);
cmpPropsCache[type] = result;
delete result.base;
delete result.groupName;
}
let result = {
..._nodeProps,
..._typeProps
};
delete result.groupName;
return result
return cmpProps;
}
}
\ No newline at end of file
}
/**
* Created by rockyl on 2019-12-18.
*/
const version = 4;
const storeConfigs = [
{name: 'project', key: 'id'},
{name: 'mock', key: 'path'},
{name: 'preview', key: 'id'},
{name: 'packages', key: 'id'},
];
export default {
open(databaseName = 'store') {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open(databaseName, version);
request.onerror = function (event) {
reject(event.target.error);
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(request.result);
};
request.onupgradeneeded = (event) => {
this.db = event.target.result;
this.createStore(storeConfigs);
}
})
},
createStore(stores) {
for (let {name, key} of stores) {
if (!this.db.objectStoreNames.contains(name)) {
this.db.createObjectStore(name, {keyPath: key || 'id'});
}
}
},
get(storeName, id) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName])
.objectStore(storeName)
.get(id);
request.onerror = function (event) {
reject(event.target.error);
};
request.onsuccess = function (event) {
resolve(request.result);
};
})
},
getAll(storeName) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName])
.objectStore(storeName)
.getAll();
request.onerror = function (event) {
reject(event.target.error);
};
request.onsuccess = function (event) {
resolve(request.result);
};
})
},
add(storeName, data) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.add(data);
request.onsuccess = function (event) {
resolve();
};
request.onerror = function (event) {
reject(event.target.error);
}
});
},
put(storeName, data) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.put(data);
request.onsuccess = function (event) {
resolve();
};
request.onerror = function (event) {
reject(event.target.error);
}
});
},
async set(storeName, data) {
try {
await this.add(storeName, data);
} catch (e) {
await this.put(storeName, data);
}
},
remove(storeName, id) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.delete(id);
request.onsuccess = function (event) {
resolve();
};
request.onerror = function (event) {
reject(event.target.error);
}
});
},
clear(storeName){
return new Promise((resolve, reject) => {
let request = this.db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.clear();
request.onsuccess = function (event) {
resolve();
};
request.onerror = function (event) {
reject(event.target.error);
}
});
},
}
\ No newline at end of file
......@@ -14,7 +14,7 @@ export function messageError(e) {
})
}
export function playWaiting(promise, text) {
export function playWaiting(promise, text, closeLoading = true) {
const loading = Loading.service({
lock: true,
text: text || i18n.t('In processing'),
......@@ -24,7 +24,9 @@ export function playWaiting(promise, text) {
messageError(e);
throw e;
}).finally(() => {
loading.close();
if (closeLoading) {
loading.close();
}
})
}
......@@ -45,35 +47,20 @@ export function strEllipsis(str, maxLength = 0, rightOffset = 0) {
return result;
}
export function saveAs(blob, fileName) {
if ('msSaveOrOpenBlob' in navigator) {
navigator.msSaveOrOpenBlob(blob, fileName);
} else {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 500);
}
}
export function getInputDefaultValue(property) {
return property ? property.hasOwnProperty('default') ? property.default + '' : 'unset' : 'unset';
}
export function updateProcesses(process, targetMetaID, replaceMetaID) {
for (let key in process.sub) {
let subProcess = process.sub[key];
if (subProcess.meta === targetMetaID) {
subProcess.meta = replaceMetaID;
export function updateProcesses(processes, targetMetaID, replaceMetaID) {
for (let process of processes) {
for (let key in process.sub) {
let subProcess = process.sub[key];
if (subProcess.meta === targetMetaID) {
subProcess.meta = replaceMetaID;
}
}
if (process.metas) {
updateProcesses(process.metas, targetMetaID, replaceMetaID)
}
}
}
......@@ -94,6 +81,26 @@ export function clonePureObj(obj) {
return JSON.parse(JSON.stringify(obj));
}
export function saveAs(blob, fileName) {
if ('msSaveOrOpenBlob' in navigator) {
navigator.msSaveOrOpenBlob(blob, fileName);
} else {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 500);
}
}
export function readTextFile(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
......@@ -109,12 +116,39 @@ export function readTextFile(file) {
})
}
export function selectFile(callback, {accept, multiple} = {}) {
function scanEntries(item){
return new Promise(resolve => {
if (item.isDirectory) {
let directoryReader = item.createReader();
directoryReader.readEntries(function(es){
resolve(es);
});
}else{
resolve();
}
})
}
export async function scanFiles(item, container) {
const entries = await scanEntries(item);
if(entries){
for(let entry of entries){
container.push(entry);
await scanFiles(entry, container);
}
}
}
export function selectFile(callback, {accept, multiple, acceptDirectory} = {}) {
let input = document.createElement('input');
input.type = 'file';
input.onchange = function (e) {
callback(input.files);
};
if(acceptDirectory){
input.webkitdirectory = true;
}
if (accept) {
input.accept = accept;
}
......@@ -123,3 +157,27 @@ export function selectFile(callback, {accept, multiple} = {}) {
}
input.click();
}
export function openPreview(packResult) {
setTimeout(() => {
let url;
if (location.host.startsWith('localhost')) {
url = packResult.tplUrl;
} else {
url = '/preview?url=http:' + packResult.tplUrl;
}
window.open(url, 'blank');
}, 500);
}
export function newScriptEl(url) {
return `<script src="${url}"></script>`
}
export function getMockServeEnabled(projectID) {
let enabled = localStorage.getItem('mock-enabled-' + projectID);
if (enabled) {
enabled = JSON.parse(enabled);
}
return !!enabled;
}
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
import CodeMirror from 'codemirror';
var Pos = CodeMirror.Pos;
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
function arrayContains(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
if (arr[i] === item) {
return true;
}
}
return false;
}
return arr.indexOf(item) != -1;
}
function scriptHint(editor, keywords, getToken, options) {
// Find the token at the cursor
var cur = editor.getCursor(), token = getToken(editor, cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
if (innerMode.mode.helperType === "json") return;
token.state = innerMode.state;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
type: token.string == "." ? "property" : null};
} else if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
var tprop = token;
// If it is a property, find out what it is a property of.
while (tprop.type == "property") {
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string != ".") return;
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (!context) var context = [];
context.push(tprop);
}
return {list: getCompletions(token, context, keywords, options),
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)};
}
function javascriptHint(editor, options) {
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {
// This getToken, it is for coffeescript, imitates the behavior of
// getTokenAt method in javascript.js, that is, returning "property"
// type and treat "." as indepenent token.
var token = editor.getTokenAt(cur);
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
token.end = token.start;
token.string = '.';
token.type = "property";
}
else if (/^\.[\w$_]*$/.test(token.string)) {
token.type = "property";
token.start++;
token.string = token.string.replace(/\./, '');
}
return token;
}
function coffeescriptHint(editor, options) {
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
}
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
"toUpperCase toLowerCase split concat match replace search").split(" ");
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
var funcProps = "prototype apply call bind".split(" ");
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function forAllProps(obj, callback) {
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
for (var name in obj) callback(name)
} else {
for (var o = obj; o; o = Object.getPrototypeOf(o))
Object.getOwnPropertyNames(o).forEach(callback)
}
}
function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) {
var lstart=start.toLowerCase();
var lstr=str.toLowerCase();
if (lstr.lastIndexOf(lstart, 0) == 0 && !arrayContains(found, lstr)) found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
forAllProps(obj, maybeAdd)
}
if (context && context.length) {
// If this is a property, see if it belongs to some object we can
// find in the current environment.
var obj = context.pop(), base;
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
if (!options || options.useGlobalScope !== false)
base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
(typeof global.jQuery == 'function'))
base = global.jQuery();
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
base = global._();
}
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
// If not, just look in the global object and any local scope
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var c = token.state.context; c; c = c.prev)
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
if (!options || options.useGlobalScope !== false)
gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
}
\ No newline at end of file
......@@ -3,6 +3,14 @@
export default {
node: {
groupName: '基础',
visible: {
title: '是否可见',
type: 'switch',
props: {
width: 40
},
value: true
},
x: {
title: 'x坐标',
type: 'inputNumber',
......@@ -47,13 +55,13 @@ export default {
title: '水平偏移',
type: 'inputNumber',
value: undefined,
desc: '相对于父元素中心点的水平偏移,0为正中心'
//desc: '相对于父元素中心点的水平偏移,0为正中心'
},
verticalCenter: {
title: '垂直偏移',
type: 'inputNumber',
value: undefined,
desc: '相对于父元素中心点的垂直偏移,0为正中心'
//desc: '相对于父元素中心点的垂直偏移,0为正中心'
},
rotation: {
title: '旋转',
......@@ -117,36 +125,57 @@ export default {
},
value: true
},
visible: {
title: '是否可见',
type: 'switch',
props: {
width: 40
},
value: true
}
},
label: {
base: 'node',
groupName: '文本',
text: {
title: '文本内容',
type: 'textArea',
value: ''
},
htmlText: {
title: 'HTML内容',
type: 'input',
value: ''
},
bold: {
title: '粗体',
type: 'switch',
props: {
width: 40
},
value: false,
},
fillColor: {
title: '颜色',
type: 'colorPicker',
value: '#000'
},
lineType: {
title: '自动换行',
type: 'select',
options: [
{ label: '单行', value: 'single' },
{ label: '多行', value: 'multi' }
],
value: 'single'
},
size: {
title: '字体大小',
type: 'swSelect',
type: 'inputNumber',
/*type: 'swSelect',
props: {
optionType: 'fontSize'
},
},*/
value: 12
},
lineHeight: {
title: '行高',
type: 'inputNumber',
value: 1
},
textAlign: {
title: '文本对齐',
type: 'select',
......@@ -161,23 +190,129 @@ export default {
title: '纵向对齐',
type: 'select',
options: [
{ label: '靠上', value: 'top' },
{ label: '靠上', value: 'up' },
{ label: '居中', value: 'middle' },
{ label: '靠下', value: 'bottom' }
{ label: '靠下', value: 'down' }
],
value: 'top'
},
},
bitmapText: {
base: 'node',
groupName: '图片文本',
text: {
title: '文本内容',
type: 'input',
value: ''
},
font: {
title: '字体名',
type: 'input',
value: ''
},
letterSpacing: {
title: '字间距',
type: 'inputNumber',
value: 0
},
verticalAlign: {
title: '纵向对齐',
type: 'select',
options: [
{ label: '靠上', value: 'up' },
{ label: '居中', value: 'middle' },
{ label: '靠下', value: 'down' }
],
value: 'down'
},
},
textinput: {
base: 'label',
groupName: '输入框',
type: {
title: '输入类型',
type: 'select',
options: [
{ label: '文本', value: 'text' },
{ label: '密码', value: 'password' },
],
value: 'text'
},
pattern: {
title: '输入模式',
type: 'input',
value: ''
},
maxLength: {
title: '最大长度',
type: 'inputNumber',
value: undefined,
},
placeholder: {
title: '提示文字',
type: 'input',
value: ''
},
placeholderColor: {
title: '提示颜色',
type: 'colorPicker',
value: '#666666'
},
},
image: {
groupName: '来源',
base: 'node',
groupName: '图片',
source: {
title: '来源',
type: 'source',
value: ''
},
imageWidth: {
title: '原图宽度',
type: 'input',
value: '',
desc: '原图片的宽度',
props: {
disabled: true
}
},
imageHeight: {
title: '原图高度',
type: 'input',
value: '',
desc: '原图片的高度',
props: {
disabled: true
}
}
},
rect: {
groupName: '矩形',
svga: {
base: 'node',
groupName: 'SVGA',
source: {
title: '来源',
type: 'source',
value: ''
},
lockStep: {
title: '锁步',
type: 'switch',
props: {
width: 40
},
value: false
},
autoPlay: {
title: '自动播放',
type: 'switch',
props: {
width: 40
},
value: false
},
},
shape: {
base: 'node',
fillColor: {
title: '填充色',
type: 'colorPicker',
......@@ -195,7 +330,11 @@ export default {
props: {
min: 0
}
},
}
},
rect: {
base: 'shape',
groupName: '矩形',
borderRadius: {
title: '圆角',
type: 'inputNumber',
......@@ -206,35 +345,20 @@ export default {
}
},
circle: {
base: 'shape',
groupName: '圆形',
fillColor: {
title: '填充色',
type: 'colorPicker',
value: '#fff'
},
strokeColor: {
title: '边框颜色',
type: 'colorPicker',
value: '#000'
},
strokeWidth: {
title: '边框宽度',
type: 'inputNumber',
value: 0,
props: {
min: 0
}
}
},
scrollView: {
base: 'node',
groupName: '滚动视图',
isVertical: {
title: '纵向锁定',
type: 'switch',
props: {
width: 40
},
value: true,
direction: {
title: '滚动方向',
type: 'select',
options: [
{ label: '纵向', value: 'vertical' },
{ label: '横向', value: 'horizontal' }
],
value: 'vertical',
},
isSpringBack: {
title: '回弹效果',
......@@ -244,11 +368,6 @@ export default {
},
value: true,
},
/*maxDistance: {
title: '最大距离',
type: 'inputNumber',
value: 1040,
},*/
maxSpeed: {
title: '最大速度',
type: 'inputNumber',
......@@ -259,5 +378,47 @@ export default {
type: 'inputNumber',
value: 20,
},
}
},
scrollList: {
base: 'scrollView',
groupName: '滚动列表',
itemWidth: {
title: '单项宽度',
type: 'inputNumber',
value: 0,
},
itemHeight: {
title: '单项高度',
type: 'inputNumber',
value: 0,
},
itemCol: {
title: '列数',
type: 'inputNumber',
value: 1,
},
cloneEvents: {
title: '克隆事件',
type: 'switch',
props:{
width: 40,
},
value: true,
},
cloneScripts: {
title: '克隆脚本',
type: 'switch',
props:{
width: 40,
},
value: true,
},
},
/*htmlView: {
groupName: 'HTML视图',
htmlElement: {
title: 'HTML内容',
type: 'input',
},
}*/
}
/**
* Created by rockyl on 2019-12-08.
*/
import MaxRectsBinPack from "./MaxRectsBinPack";
import {clonePureObj} from "./index";
const packExts = ['.png']; //, '.jpg', '.jpeg', '.bmp'
let canvas = document.createElement('canvas');
export async function packImages(asssts, options = {}) {
const padding = options.padding || 1;
const maxSize = options.maxSize || 2048;
const mode = options.mode || 0;
const images = await preProcessing(asssts);
let rects = [], singles = [];
for (let item of images) {
const {image, assets, image: {width, height}} = item;
if (width < maxSize && height < maxSize) {
const rectWidth = width + padding * 2;
const rectHeight = height + padding * 2;
rects.push({
image,
assets,
width: rectWidth,
height: rectHeight,
area: rectWidth * rectHeight,
sourceW: width,
sourceH: height,
offX: 0,
offY: 0,
})
} else {
asssts.push({
name: assets[0].name,
ext: assets[0].ext,
url: assets[0].url,
uuids: assets.map(asset => asset.uuid),
})
}
}
rects.sort((a, b) => {
return b.area - a.area;
});
let remainRects = rects.concat();
let index = 0;
while (remainRects.length > 0) {
let name = 'sheet' + index;
let pack = new MaxRectsBinPack(maxSize, maxSize, false);
let packedRects = pack.insertRects(remainRects, mode);
//document.body.append(canvas);
canvas.width = canvas.height = maxSize;
let context = canvas.getContext('2d');
//context.fillStyle = `rgba(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, 1)`;
//context.strokeRect(0, 0, maxSize, maxSize);
for (let rect of packedRects) {
context.drawImage(rect.image, rect.x + padding, rect.y + padding);
}
let blob = await new Promise(resolve => {
canvas.toBlob(function (blob) {
resolve(blob);
}, 'image/png');
});
let frames = {};
let i = 0;
for (let rect of packedRects) {
let sprite = {
x: rect.x + padding,
y: rect.y + padding,
w: rect.width - padding * 2,
h: rect.height - padding * 2,
ox: rect.offX,
oy: rect.offY,
sw: rect.sourceW,
sh: rect.sourceH,
};
for (let asset of rect.assets) {
frames[asset.uuid] = Object.assign({}, sprite, {name: asset.name});
i++;
}
}
asssts.push({
ext: '.sht',
file: new File([blob], name + '.png'),
frames,
});
index++;
}
}
function loadImage(url, assets) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function () {
resolve({
image: img,
assets,
})
};
img.onerror = reject;
img.src = url;
})
}
async function preProcessing(assets) {
let targetAssets = [];
for (let i = 0, li = assets.length; i < li; i++) {
const asset = assets[i];
if (packExts.includes(asset.ext)) {
targetAssets.push(assets.splice(i, 1)[0]);
i--;
li--;
}
}
let groups = {};
for (let asset of targetAssets) {
let group = groups[asset.url];
if (!group) {
group = groups[asset.url] = [];
}
group.push(asset);
}
let ps = [];
for (let url in groups) {
ps.push(loadImage(url, groups[url]))
}
return await Promise.all(ps);
}
......@@ -7,17 +7,20 @@
<views splitpanes-min="20" :splitpanes-size="getSize(2, 0)"></views>
<playground splitpanes-min="20" :splitpanes-size="getSize(2, 1)"></playground>
</split-panes>
<assets splitpanes-min="20" :splitpanes-size="getSize(1, 1)"></assets>
<assets splitpanes-min="25" :splitpanes-size="getSize(1, 1)"></assets>
</split-panes>
<inspector splitpanes-min="20" :splitpanes-size="getSize(0, 1)"></inspector>
</split-panes>
<bottom-bar/>
<details-dialog ref="dialogsDialog"/>
<pack-result-dialog ref="packResultDialog"/>
<mock-editor-dialog ref="mockEditorDialog"/>
<code-sync-serve-dialog ref="codeSyncServeDialog"/>
</div>
</template>
<script>
import {mapGetters, mapActions} from 'vuex'
import {mapState, mapActions} from 'vuex'
import SplitPanes from 'splitpanes'
import ToolBar from "./Editor/ToolBar";
import Inspector from "./Editor/Inspector";
......@@ -25,13 +28,23 @@
import Playground from "./Editor/Playground";
import Assets from "./Editor/Assets";
import DetailsDialog from "./Editor/dialogs/DetailsDialog";
import {playWaiting} from "../utils";
import {openPreview, playWaiting} from "../utils";
import i18n from "../i18n";
import PackResultDialog from "./Editor/dialogs/PackResultDialog";
import events from "@/global-events.js"
import db from "../utils/db-storage";
import MockEditorDialog from "./Editor/dialogs/MockEditorDialog";
import {startCodeSyncServe} from "../code-sync-serve";
import BottomBar from "./Editor/BottomBar";
import CodeSyncServeDialog from "./Editor/dialogs/CodeSyncServeDialog";
export default {
name: 'Editor',
components: {
CodeSyncServeDialog,
BottomBar,
MockEditorDialog,
PackResultDialog,
DetailsDialog,
Assets,
......@@ -50,7 +63,7 @@
},
watch: {
$route: {
handler: function(val, oldVal){
handler: function (val, oldVal) {
console.log('router changed');
this.loadProject();
},
......@@ -58,26 +71,43 @@
}
},
computed: {
...mapGetters([]),
...mapState({
codeSyncServeConfig(state) {
return state.env.codeSyncServeConfig;
},
}),
},
async mounted() {
document.addEventListener('keydown', this.onKeyPress);
events.$on('save-and-preview', () => {
this.clickMenu("preview");
});
events.$on('show-code-sync-serve-dialog', () => {
this.$refs.codeSyncServeDialog.show();
});
await playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {});
await playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {
});
setTimeout(() => {
startCodeSyncServe(this.codeSyncServeConfig);
}, 100);
this.loadProject();
},
destroyed(){
destroyed() {
document.removeEventListener('keydown', this.onKeyPress)
},
methods: {
prepare(){
prepare() {
return Promise.all([
this.updateEnv(),
db.open('store'),
])
},
onKeyPress(e){
if(e.key === 's' && (e.ctrlKey||e.metaKey)){
onKeyPress(e) {
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.saveProject();
......@@ -85,8 +115,8 @@
return false;
}
},
async loadProject(){
const {projectID} = this.$route.params;
async loadProject() {
const {projectID, project, dataUrl} = this.$route.params;
if (await this.localVersionExist(projectID)) {
this.$confirm(this.$t('Unsaved version found locally'), this.$t('Alert'), {
showClose: false,
......@@ -94,34 +124,35 @@
closeOnPressEscape: false,
confirmButtonText: this.$t('Local Version'),
cancelButtonText: this.$t('Remote Version'),
type: 'warning'
type: 'warning',
}).then(() => {
this.loadLocalVersion(projectID);
}).catch((e) => {
if (e === 'cancel') {
this.loadRemoteVersion(projectID);
this.loadRemoteVersion(projectID, project, dataUrl);
} else {
console.log(e);
}
});
} else {
this.loadRemoteVersion(projectID);
this.loadRemoteVersion(projectID, project, dataUrl);
}
},
loadLocalVersion(projectID) {
this.ready = false;
this.loadFromLocal(projectID);
this.$nextTick(()=>{
this.$nextTick(() => {
this.ready = true;
});
},
async loadRemoteVersion(projectID) {
async loadRemoteVersion(projectID, project, dataUrl) {
if (projectID) {
this.ready = false;
await playWaiting(this.loadFromRemote(projectID), this.$t('Preparing')).catch(e => {
let p = dataUrl ? this.loadFromDataUrl({project, dataUrl}) : this.loadFromRemote(projectID);
await playWaiting(p, this.$t('Preparing')).catch(e => {
this.$alert(this.$t('Project does not exist'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
callback: action=>{
callback: action => {
this.$router.replace({name: 'home'});
}
});
......@@ -139,12 +170,28 @@
this.panesConfig[id] = configs[0].width / 100;
localStorage.panesConfig = JSON.stringify(this.panesConfig);
},
async saveProject() {
await playWaiting(this.saveToRemote(), this.$t('Saving'));
this.$message({
message: i18n.t('Save project successfully'), //因为message是异步出现,但是当路由回退的时候,this.i18n的实例已经置空,所以要用全局的i18n实例
type: 'success'
async saveProject(closeLoading) {
let remark, cancel;
await this.$prompt(this.$t('Input version remark'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
showClose: false,
inputPattern: /^.{0,256}$/,
}).then(
({value}) => {
remark = value;
}
).catch((action) => {
cancel = true;
});
if (!cancel) {
await playWaiting(this.saveToRemote({remark}), this.$t('Saving'), closeLoading);
this.$message({
message: i18n.t('Save project successfully'), //因为message是异步出现,但是当路由回退的时候,this.i18n的实例已经置空,所以要用全局的i18n实例
type: 'success'
});
}
return cancel;
},
async clickMenu(menuItem) {
switch (menuItem) {
......@@ -157,8 +204,18 @@
case 'details':
this.$refs.dialogsDialog.show();
break;
case 'pack':
await this.pack();
case 'preview':
this.preview();
await this.pack(true);
break;
case 'preview-fast':
this.preview();
break;
case 'publish':
await this.publish();
break;
case 'mock':
this.$refs.mockEditorDialog.show();
break;
case 'undo':
this.$store.commit('undoRedo', 1);
......@@ -173,9 +230,9 @@
confirmButtonText: this.$t('Save'),
cancelButtonText: this.$t('Exit'),
type: 'warning'
}).then(() => {
}).then(async () => {
try {
this.saveProject();
await this.saveProject();
this.backToHome();
} catch (e) {
}
......@@ -188,20 +245,33 @@
break;
}
},
async pack(){
async publish() {
this.pack();
},
async pack(debug = false) {
let cancel;
if (!debug) {
cancel = await this.saveProject(false);
}
if(cancel){
return;
}
const loading = this.$loading({
lock: true,
text: this.$t('Packing'),
});
try{
await this.saveProject();
const packResult = await this.packProject();
try {
const packResult = await this.packProject(debug);
this.$message({
message: this.$t('Pack project successfully'),
type: 'success',
duration: 1000,
duration: 500,
});
this.$refs.packResultDialog.show(packResult);
if (debug) {
openPreview(packResult);
} else {
this.$refs.packResultDialog.show(packResult);
}
/*this.$confirm(this.$t('Pack project successfully'), this.$t('Alert'), {
confirmButtonText: this.$t('Open in new tab'),
cancelButtonText: this.$t('Close'),
......@@ -213,7 +283,8 @@
}).catch(() => {
});*/
}catch (e) {
} catch (e) {
console.log(e);
this.$message({
message: this.$t('Pack project failed'),
type: 'error',
......@@ -225,14 +296,29 @@
backToHome() {
this.$router.replace({name: 'home'});
},
preview() {
this.savePreview();
const {projectID} = this.$route.params;
let previewUrl = new URL(location.href);
previewUrl.hash = '#/preview/' + projectID;
setTimeout(() => {
window.open(previewUrl.href, 'blank');
}, 300);
},
...mapActions([
'localVersionExist',
'loadFromLocal',
'loadFromRemote',
'loadFromDataUrl',
"saveToLocal",
"saveToRemote",
'updateEnv',
'packProject',
'savePreview',
'packImages',
])
}
}
......
......@@ -22,13 +22,11 @@
</div>
<el-scrollbar class="assets-scrollbar" wrap-class="wrap-x-hidden"
view-class="scrollbar-view">
<div class="file-list">
<div class="file-uploader" @click="toUploadFile">
<asset-list editable @show-file-details="showFileDetails" @click-item="onItemClick">
<div slot="first" class="file-uploader" @click="toUploadFile" @dragover.prevent @drop="onDropFile">
<i class="el-icon-plus file-uploader-icon"></i>
</div>
<file-item v-for="(asset, index) in assets" :data="asset" :key="index" @show-file-details="showFileDetails"
@click="onItemClick(asset)"/>
</div>
</asset-list>
</el-scrollbar>
</div>
</div>
......@@ -37,16 +35,17 @@
</template>
<script>
import {mapState, mapMutations, mapActions} from 'vuex'
import {mapGetters, mapMutations, mapActions} from 'vuex'
import Pane from "../../components/Pane";
import FileItem from "./Assets/FileItem";
import AssetsShow from "./Assets/AssetsShow";
import SplitPanes from 'splitpanes'
import {selectFile} from "../../utils";
import {scanFiles, selectFile} from "../../utils";
import AssetList from "./Assets/AssetList";
export default {
name: "Assets",
components: {AssetsShow, FileItem, Pane, SplitPanes},
components: {AssetList, AssetsShow, FileItem, Pane, SplitPanes},
data() {
return {
showFields: ['url', 'uuid'],
......@@ -57,9 +56,7 @@
}
},
computed: {
...mapState({
assets: state => state.project.data.assets
}),
...mapGetters(['assets']),
},
mounted() {
this.currentItem = null;
......@@ -70,8 +67,25 @@
this.uploadFiles(files);
}, {multiple: true})
},
showFileDetails(file) {
this.$refs.assetsShow.show(file);
async onDropFile(e) {
const items = e.dataTransfer.items;
let allFiles = [];
let allEntries = [];
for (let item of items) {
await scanFiles(item.webkitGetAsEntry(), allEntries);
}
for (let entry of allEntries) {
await new Promise(resolve => {
entry.file(function (file) {
allFiles.push(file);
resolve();
});
})
}
this.uploadFiles(allFiles);
},
showFileDetails(asset) {
this.$refs.assetsShow.show(asset);
},
onItemClick(asset) {
this.currentItem = asset;
......
<template>
<div class="file-list">
<slot name="first"></slot>
<file-item v-for="(asset, index) in assets" :editable="editable" :data="asset" :key="index" @show-file-details="showFileDetails(asset)"
@click="onClickItem(asset)"/>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
import FileItem from "./FileItem";
export default {
name: "AssetList",
components: {FileItem},
props: {
editable:{
type: Boolean,
default: false,
}
},
computed: {
...mapGetters([
'assets',
])
},
methods: {
showFileDetails(asset) {
this.$emit('show-file-details', asset);
},
onClickItem(asset) {
this.$emit('click-item', asset);
},
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="file-item" @click="$emit('click', $event)">
<div class="icon">
<i v-if="!showThumbnail" draggable="true" class="file-icon" :class="fileIcon"></i>
<img @dragstart="assetDragStart(data)" v-if="showThumbnail" draggable="true" class="thumbnail" :src="thumbnailUrl" alt="thumb" @dblclick="onDbclick()">
<div class="operate-bar">
<i @dragstart="assetDragStart(data)" v-if="!showThumbnail" draggable="true" class="file-icon"
:class="fileIcon"></i>
<img @dragstart="assetDragStart(data)" v-if="showThumbnail" draggable="true"
class="thumbnail alpha-image-background" :src="thumbnailUrl"
alt="thumb" @dblclick="onDbclick()">
<div v-if="editable" class="operate-bar">
<el-button circle size="mini" type="success" icon="icon-upload" @dblclick.native.stop
@click="onClickReplace"/>
<el-button circle size="mini" type="success" icon="el-icon-edit" @dblclick.native.stop @click="onClickEdit"/>
<el-button circle size="mini" type="danger" icon="el-icon-delete" @dblclick.native.stop
@click="onClickDelete"/>
......@@ -17,21 +22,20 @@
</template>
<script>
import {mapMutations} from "vuex";
import {mapMutations, mapActions} from "vuex";
import {fileTypeIcon} from "../../../config";
import {strEllipsis} from "../../../utils";
import {selectFile, strEllipsis} from "../../../utils";
export default {
name: "FileItem",
props: ['data'],
data() {
return {
}
props: {
data: {type: Object},
editable: {type: Boolean, default: false},
},
watch: {
data() {
return {}
},
watch: {},
computed: {
fileIcon() {
let icon = fileTypeIcon[this.data.ext];
......@@ -43,7 +47,7 @@
thumbnailUrl() {
return this.data.url;
},
ellipsisName(){
ellipsisName() {
return strEllipsis(this.data.name, 14, 5);
},
},
......@@ -78,11 +82,23 @@
}).catch(() => {
});
},
onClickReplace() {
selectFile((files) => {
let file = files[0];
this.replaceAsset({
file: file,
uuid: this.data.uuid,
});
})
},
...mapMutations([
'deleteAsset',
'modifyAsset',
'assetDragStart'
'assetDragStart',
]),
...mapActions([
'replaceAsset'
])
}
}
</script>
......
<template>
<div class="bottom-bar">
<div class="editor-status">Ready</div>
<code-sync-indicator/>
<upload-indicator/>
</div>
</template>
<script>
import UploadIndicator from "./BottomBar/UploadIndicator";
import CodeSyncIndicator from "./BottomBar/CodeSyncIndicator";
export default {
name: "BottomBar",
components: {CodeSyncIndicator, UploadIndicator},
}
</script>
<style scoped>
</style>
<template>
<div class="code-sync-indicator">
<div class="indicator" :class="{enabled: enabled}" @click="onClick"></div>
</div>
</template>
<script>
import events from "../../../global-events";
import {codeSyncServeEnabled} from "../../../code-sync-serve";
export default {
name: "CodeSyncIndicator",
data() {
return {
enabled: codeSyncServeEnabled,
}
},
mounted() {
events.$on('code-sync-start', this.onCodeSyncStart);
events.$on('code-sync-stop', this.onCodeSyncStop);
},
destroyed() {
events.$off('code-sync-start', this.onCodeSyncStart);
events.$off('code-sync-stop', this.onCodeSyncStop);
},
methods: {
onCodeSyncStart() {
this.enabled = true;
},
onCodeSyncStop() {
this.enabled = false;
},
onClick() {
events.$emit('show-code-sync-serve-dialog');
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -4,6 +4,9 @@
<el-tab-pane :label="$t('Props')" name="properties">
<props-tab/>
</el-tab-pane>
<el-tab-pane :label="$t('Script')" name="script">
<scripts-tab/>
</el-tab-pane>
<el-tab-pane :label="$t('Behavior')" name="behavior">
<behavior-tab/>
</el-tab-pane>
......@@ -15,10 +18,11 @@
import Pane from '../../components/Pane';
import BehaviorTab from "./Inspector/BehaviorTab";
import PropsTab from "./Inspector/PropsTab";
import ScriptsTab from "./Inspector/ScriptsTab";
export default {
name: 'Inspector',
components: {BehaviorTab, PropsTab, Pane},
components: {ScriptsTab, BehaviorTab, PropsTab, Pane},
data() {
return {
tab: 'properties'
......
<template>
<div class="zero-inspector-behavior-form" v-if="activeComponent.uuid">
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden">
<el-form ref="form" size="mini" label-width="60px">
<div v-for="(evn, key) in eventsObj" :key="key">
<el-form-item :label="$t('Event') + ':'">
<div>{{events[key]}}</div>
<div >
<!--<el-tooltip :content="$t('Trigger once')" placement="top">
<el-switch v-model="evn.once" @change="v => handleOnceChange(key, v)"></el-switch>
</el-tooltip>-->
<el-button size="mini" @click="showBehaviorEditor(evn, key)" style="margin-left: 20px;" icon="el-icon-edit">
<!--<i v-if="evn.behaviors && evn.behaviors.length" class="el-icon-check el-icon&#45;&#45;right"></i>-->
</el-button>
<el-button icon="el-icon-delete" size="mini" v-if="evn.behaviors && evn.behaviors.length" @click="deleteBehavior(key)">
</el-button>
<div class="zero-inspector-behavior" v-if="activeComponent.uuid">
<el-dropdown trigger="click" size="mini" @command="addTrigger" class="add-trigger">
<el-link icon="el-icon-plus">添加触发</el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="!!activeComponent.events[key]" v-for="(item, key) in builtinEvents" :command="key"
:key="key">{{item}}
</el-dropdown-item>
<el-dropdown-item divided command="custom">{{$t('Custom event')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="trigger-list">
<div class="trigger-item" v-for="(trigger, name, index) in activeComponent.events" :key="index">
<div class="top-bar">
<span class="el-icon-caret-right el-tree-node__expand-icon expanded-arrow"
:class="{expanded: !trigger.collapse}"></span>
<span class="name" @click="toggleCollapse(trigger)">
{{$t('events')[name] || name}}({{trigger.behaviors.length}})
</span>
<el-dropdown trigger="click" size="mini" @command="command=>clickTriggerMore(command, trigger, name, index)">
<el-link icon="el-icon-more" :underline="false"></el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-plus" command="addBehavior">{{$t('Add behavior')}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-edit" command="editTrigger" :disabled="!trigger.custom" divided>{{$t('Edit trigger')}}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-minus" command="deleteTrigger" :disabled="trigger.behaviors.length > 0">
{{$t('Delete trigger')}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<el-collapse-transition>
<div class="behavior-list" v-show="!trigger.collapse">
<div v-for="(behavior, index) in trigger.behaviors" class="behavior-item">
<enabled-setter :target="behavior"/>
<span v-if="!getBehavior(behavior)" class="name-field missing-behavior">{{$t('Missing behavior')}}</span>
<input v-else class="name-field name-input" type="text" v-model="getBehavior(behavior).name" @change="onBehaviorNameChange"></input>
<el-button icon="el-icon-minus" class="delete-button" type="danger" plain circle size="mini"
@click="deleteBehavior(index, trigger.behaviors)"/>
<el-button icon="el-icon-edit" class="edit-button" type="primary" plain circle size="mini"
@click="editBehavior(behavior, trigger.behaviors)"/>
</div>
</div>
</el-form-item>
</el-collapse-transition>
</div>
</el-form>
</el-scrollbar>
<behavior-editor-dialog :behaviors="behaviors" @change="v => handleBehaviorsChange(v)" ref="behaviorEditorDialog"></behavior-editor-dialog>
<behavior-editor-dialog @change="handleBehaviorsChange" ref="behaviorEditorDialog"></behavior-editor-dialog>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import _ from 'lodash';
import BehaviorEditorDialog from '../dialogs/BehaviorEditorDialog';
import {mapGetters, mapActions, mapMutations} from 'vuex';
import BehaviorEditorDialog from "../dialogs/BehaviorEditorDialog";
import events from "@/global-events.js"
import EnabledSetter from "../components/EnabledSetter";
export default {
name: 'BehaviorTab',
data() {
let eventsObj = {};
const events = this.$t('events');
Object.keys(events).forEach(event => {
eventsObj[event] = {
once: false,
behaviors: []
};
});
return {
events,
eventsObj,
behaviors: [],
currentEvent: ''
};
},
components: { BehaviorEditorDialog },
computed: {
...mapGetters(['activeComponent', 'componentList'])
},
methods: {
showBehaviorEditor(evn, key) {
this.currentEvent = key;
this.behaviors = evn.behaviors || [];
this.$refs.behaviorEditorDialog.show(this.behaviors, this.activeComponent.name + '_' + key);
},
/**
* 当前选中组件发生变化时,更新eventsObj的数据
*/
updateEventsObj() {
let _events = this.activeComponent.events || {};
export default {
name: "BehaviorTab",
components: {EnabledSetter, BehaviorEditorDialog},
data() {
const builtinEvents = this.$t('events');
return {
builtinEvents,
}
},
watch: {
'activeComponent.uuid': function () {
this.updateEvents();
}
},
computed: {
...mapGetters(['activeComponent', 'componentList'])
},
methods: {
getBehavior(behavior) {
let process = this.$store.getters.getProcess(behavior);
if (!process) {
console.warn('missing process:', behavior.meta);
}
return process;
},
updateEvents() {
if (!this.activeComponent.events) {
this.$set(this.activeComponent, 'events', {});
}
},
addTrigger(command) {
let trigger = {behaviors: []};
if (command === 'custom') {
trigger.custom = true;
}
this.addBehavior(command, trigger.behaviors);
this.$set(this.activeComponent.events, command, trigger);
},
toggleCollapse(trigger) {
this.$set(trigger, 'collapse', !trigger.collapse);
},
clickTriggerMore(command, trigger, name, index) {
switch (command) {
case 'addBehavior':
this.addBehavior(name, trigger.behaviors);
this.$set(trigger, 'collapse', false);
break;
case 'editTrigger':
this.editTriggerName(name);
break;
case 'deleteTrigger':
this.deleteTrigger(name);
break;
}
},
addBehavior(eventName, behaviors) {
const alias = `${this.activeComponent.name}_${eventName}` + (behaviors.length > 0 ? '_' + (behaviors.length + 1) : '');
_.forIn(this.eventsObj, (value, key) => {
if (_events[key]) {
this.$set(this.eventsObj, key, _.cloneDeep(_events[key]));
} else {
value.once = false;
value.behaviors = [];
}
});
},
/**
* 是否只执行一次
*/
handleOnceChange(key, v) {
let _event = this.eventsObj[key];
// 如果没有behavior,once没有用处,则不执行
if (_event.behaviors && _event.behaviors.length) {
let event = {};
event[key] = {
once: v,
behaviors: _event.behaviors
};
let events = this.activeComponent.events;
this.addBehaviorDirect({
alias, behaviors,
});
this.$store.dispatch('modifyActiveView', {
events: _.merge({}, events, event)
});
}
},
/**
* 行为发生变化,同步数据
*/
handleBehaviorsChange(v) {
if (this.currentEvent /* && v && v.length*/) {
let event = {};
let currentEvent = this.eventsObj[this.currentEvent];
event[this.currentEvent] = Object.assign(currentEvent /*, { behaviors: v }*/);
let events = this.activeComponent.events;
this.$store.dispatch('modifyActiveView', {
events: _.merge({}, events, event)
});
this.updateEventsObj();
}
},
/**
* 删除行为
*/
deleteBehavior(key) {
let _events = this.activeComponent.events || {};
delete _events[key];
this.makeDirty();
},
editTriggerName(name) {
this.$prompt(this.$t('Input event name'), this.$t('Rename event'), {
inputValue: name,
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
closeOnClickModal: false,
inputPattern: /^.{1,256}$/,
inputErrorMessage: this.$t('Invalid event name'),
}).then(({value}) => {
if (name !== value) {
const events = this.activeComponent.events;
if (events.hasOwnProperty(value)) {
this.$alert(this.$t('Event name exists'))
} else if (this.builtinEvents.hasOwnProperty(value)) {
this.$alert(this.$t('Builtin event should add directly'))
} else {
this.$set(events, value, events[name]);
this.$delete(events, name);
this.$store.dispatch('modifyActiveView', {
events: _events
});
this.updateEventsObj();
}
},
watch: {
'activeComponent.uuid': function() {
this.updateEventsObj();
console.log('activeComponent.uuid change');
}
}
};
this.makeDirty();
}
}
}).catch(() => {
});
},
deleteTrigger(name) {
this.$delete(this.activeComponent.events, name);
this.makeDirty();
},
editBehavior(behavior, behaviors) {
this.$refs.behaviorEditorDialog.show(behavior, behaviors);
},
handleBehaviorsChange(isPreview) {
if (isPreview) {
events.$emit('save-and-preview')
}
this.makeDirty();
},
onBehaviorNameChange(){
this.makeDirty();
},
async deleteBehavior(index, behaviors) {
let deleteMeta = false;
let close;
await this.$confirm(this.$t('Are you sure to delete it\'s process'), this.$t('Alert'), {
confirmButtonText: this.$t('Delete'),
cancelButtonText: this.$t('Not delete'),
distinguishCancelAndClose: true,
type: 'warning'
}).then(() => {
deleteMeta = true;
this.makeDirty();
}).catch(action => {
if(action === 'close'){
close = true;
}
});
if(!close){
this.deleteBehaviorDirect({
behaviors,
index,
deleteMeta,
})
}
},
...mapActions([
'modifyActiveView',
'getProcess',
'addBehaviorDirect',
'deleteBehaviorDirect',
]),
...mapMutations([
'makeDirty',
]),
}
}
</script>
<style scoped>
<style scoped lang="scss">
</style>
\ No newline at end of file
<template>
<div class="zero-inspector-behavior-form" v-if="activeComponent.uuid">
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden">
<el-form ref="form" size="mini" label-width="60px">
<div v-for="(evn, key) in eventsObj" :key="key">
<el-form-item :label="$t('Event') + ':'">
<div>{{events[key]}}</div>
<div >
<!--<el-tooltip :content="$t('Trigger once')" placement="top">
<el-switch v-model="evn.once" @change="v => handleOnceChange(key, v)"></el-switch>
</el-tooltip>-->
<el-button size="mini" @click="showBehaviorEditor(evn, key)" style="margin-left: 20px;" icon="el-icon-edit">
<!--<i v-if="evn.behaviors && evn.behaviors.length" class="el-icon-check el-icon&#45;&#45;right"></i>-->
</el-button>
<el-button icon="el-icon-delete" size="mini" v-if="evn.behaviors && evn.behaviors.length" @click="deleteBehavior(key)">
</el-button>
</div>
</el-form-item>
</div>
</el-form>
</el-scrollbar>
<behavior-editor-dialog :behaviors="behaviors" @change="isPreview => handleBehaviorsChange(isPreview)" ref="behaviorEditorDialog"></behavior-editor-dialog>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import _ from 'lodash';
import BehaviorEditorDialog from '../dialogs/BehaviorEditorDialog';
import events from "@/global-events.js"
export default {
name: 'BehaviorTab2',
data() {
let eventsObj = {};
const events = this.$t('events');
Object.keys(events).forEach(event => {
eventsObj[event] = {
once: false,
behaviors: []
};
});
return {
events,
eventsObj,
behaviors: [],
currentEvent: ''
};
},
components: { BehaviorEditorDialog },
computed: {
...mapGetters(['activeComponent', 'componentList'])
},
methods: {
showBehaviorEditor(evn, key) {
this.currentEvent = key;
this.behaviors = evn.behaviors || [];
this.$refs.behaviorEditorDialog.show(this.behaviors, this.activeComponent.name + '_' + key);
},
/**
* 当前选中组件发生变化时,更新eventsObj的数据
*/
updateEventsObj() {
let _events = this.activeComponent.events || {};
_.forIn(this.eventsObj, (value, key) => {
if (_events[key]) {
this.$set(this.eventsObj, key, _.cloneDeep(_events[key]));
} else {
value.once = false;
value.behaviors = [];
}
});
},
/**
* 是否只执行一次
*/
handleOnceChange(key, v) {
let _event = this.eventsObj[key];
// 如果没有behavior,once没有用处,则不执行
if (_event.behaviors && _event.behaviors.length) {
let event = {};
event[key] = {
once: v,
behaviors: _event.behaviors
};
let events = this.activeComponent.events;
this.$store.dispatch('modifyActiveView', {
events: _.merge({}, events, event)
});
}
},
/**
* 行为发生变化,同步数据
*/
handleBehaviorsChange(isPreview) {
if (this.currentEvent /* && v && v.length*/) {
let event = {};
let currentEvent = this.eventsObj[this.currentEvent];
event[this.currentEvent] = Object.assign(currentEvent /*, { behaviors: v }*/);
let events = this.activeComponent.events;
this.$store.dispatch('modifyActiveView', {
events: _.merge({}, events, event)
});
this.updateEventsObj();
}
if(isPreview){
events.$emit('saveAndPreview')
}
},
/**
* 删除行为
*/
deleteBehavior(key) {
let _events = this.activeComponent.events || {};
delete _events[key];
this.$store.dispatch('modifyActiveView', {
events: _events
});
this.updateEventsObj();
}
},
watch: {
'activeComponent.uuid': function() {
this.updateEventsObj();
console.log('activeComponent.uuid change');
}
}
};
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -2,68 +2,34 @@
<div class="zero-inspector-props-form" v-if="activeComponent.uuid">
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden">
<el-form ref="form" size="mini" :model="form" label-width="80px" @submit.native.prevent>
<el-collapse v-model="configColl">
<el-collapse-item title="配置" name="properties">
<div class="zero-inspector-props-group">
<el-form-item label="名称">
<el-input v-model="form.name" @input="v => handleChange('name', v)"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.type" @change="v => handleChange('type', v)" placeholder="请选择类型">
<el-option v-for="cmp in componentsMap" :key="cmp.value" :label="cmp.label" :value="cmp.value"></el-option>
</el-select>
</el-form-item>
<template v-for="(p, key) in cmpProps">
<el-form-item v-if="key !== 'groupName'" :id="activeComponent.uuid + '-' + key" :key="activeComponent.uuid + key" :label="p.title">
<!-- {{key}} -->
<el-tooltip :disabled="!p.desc" placement="top-start">
<div slot="content">{{p.desc}}</div>
<div>
<dynamic-component :component-value="getPropValue(p, key)" :component-props="getPropProps(p)" :component-type="getPropCmpType(p)" @onChange="v => handlePropertiesChange(key, v)"></dynamic-component>
</div>
</el-tooltip>
</el-form-item>
</template>
</div>
</el-collapse-item>
<el-collapse-item title="脚本" name="scripts">
<el-collapse accordion v-if="activeComponent.scripts && activeComponent.scripts.length">
<template v-for="(script, index) in activeComponent.scripts">
<el-collapse-item :title="getScriptName(script.script)" :key="script + index">
<template v-for="(p, key) in getScriptOptions(script.script)">
<el-form-item :key="activeComponent.uuid + index + key" :label="p.alias|| key">
<dynamic-component :component-value="getScriptValue(p, key, index)" :component-props="getScriptProps(p, index)" :component-type="getScriptType(p, index)" @onChange="v => handleScriptChange(index, key, v)"></dynamic-component>
</el-form-item>
</template>
<el-form-item label="">
<el-button @click="deleteNodeScript(index)">删除</el-button>
</el-form-item>
</el-collapse-item>
</template>
</el-collapse>
<div style="padding-top: 15px;text-align: center;">
<el-popover
placement="top"
width="300"
v-model="scriptDialog"
trigger="manual">
<div class="script-config-dialog">
<el-tree :data="scripts" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
<el-button slot="reference" @click="scriptDialog = !scriptDialog" size="mini">add script</el-button>
</el-popover>
</div>
</el-collapse-item>
</el-collapse>
<el-form-item label="名称">
<el-input v-model="form.name" @input="v => handleChange('name', v)"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.type" @change="v => handleChange('type', v)" placeholder="请选择类型">
<el-option v-for="(cmp, key) in componentsMap" :key="key" :label="cmp" :value="key"></el-option>
</el-select>
</el-form-item>
<template v-for="(p, key) in cmpProps">
<el-form-item v-if="key !== 'groupName'" :id="activeComponent.uuid + '-' + key" :key="activeComponent.uuid + key" :label="p.title">
<!-- {{key}} -->
<el-tooltip :disabled="!p.desc" placement="top-start">
<div slot="content">{{p.desc}}</div>
<div>
<dynamic-component :component-value="getPropValue(p, key)" :component-props="getPropProps(p)" :component-type="getPropCmpType(p)" @onChange="v => handlePropertiesChange(key, v)"></dynamic-component>
</div>
</el-tooltip>
</el-form-item>
</template>
</el-form>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { mapGetters, mapState } from 'vuex';
import _ from 'lodash';
import { componentsMap, getCmpProps } from '../../../utils/common';
import { getCmpProps } from '../../../utils/common';
import dynamicComponent from '../components/dynamicComponent';
const componentMapper = {
......@@ -95,44 +61,24 @@ const componentMapper = {
'controls-position': 'right'
}
},
input: {
component: 'el-input'
},
colorPicker: {
component: 'el-color-picker',
textArea: {
component: 'el-input',
props: {
'show-alpha': true
}
}
};
const scriptTypeMap = {
number: {
component: 'el-input-number',
props: {
size: 'mini'
type: 'textarea',
autosize: {
minRows: 3,
maxRows: 10
}
}
},
string: {
input: {
component: 'el-input'
},
boolean: {
component: 'el-switch',
props: {
width: 40
}
},
color: {
colorPicker: {
component: 'el-color-picker',
props: {
'show-alpha': true
}
},
select: {
component: 'el-select',
props: {
slotComponent: 'el-option'
}
}
};
......@@ -140,20 +86,14 @@ export default {
name: 'PropsTab',
components: { 'dynamic-component': dynamicComponent },
data() {
const componentsMap = this.$t('view_node_menu');
return {
componentsMap,
scriptDialog: false,
form: {
name: '',
type: '',
properties: {},
scripts: []
},
defaultProps: {
children: 'children',
label: 'name'
},
configColl: ['properties']
};
},
computed: {
......@@ -162,13 +102,6 @@ export default {
// 获取properties.js中的默认配置
return getCmpProps(this.activeComponent.type);
},
scripts: function() {
console.log('scripts', this.$store.state.env.scripts);
return _.filter(this.$store.state.env.scripts, s => {
return s !== null && s !== undefined;
});
}
},
watch: {
activeComponent: {
......@@ -186,14 +119,6 @@ export default {
_view[label] = v;
this.$store.dispatch('modifyActiveView', _view);
},
/**
* 脚本预设对象选中
*/
handleNodeClick(script) {
console.log('handleNodeClick', script);
this.$store.dispatch('addNodeScript', script.id);
this.scriptDialog = false;
},
/**
* 基础属性发生改变
*/
......@@ -204,20 +129,6 @@ export default {
_prop[key] = v;
this.$store.dispatch('modifyProperties', _prop);
},
/**
* 事件属性发生改变
*/
handleScriptChange(index, key, v) {
let _props = {};
_props[key] = v;
let _scripts = _.cloneDeep(this.activeComponent.scripts);
let _script = _scripts[index];
_script.props = _.assign(_script.props, _props);
_scripts[index] = _script;
this.$store.dispatch('modifyActiveView', {
scripts: _scripts
});
},
/**
* 获取动态组件的类型
*/
......@@ -243,61 +154,6 @@ export default {
let _properties = this.activeComponentCopy.properties;
return _properties[key] === undefined || _properties[key] === null ? item.value : _properties[key];
},
getScriptValue(item, key, index) {
let _script = this.activeComponent.scripts[index];
// let result =
return _script.props[key] || item.default;
},
getScriptProps(item, index) {
let _type = item.type;
let _options = [];
if (_type === 'enum') {
// 如果脚本选项对应的类型是数组,说明是枚举类型
_options = item.enum.map(i => {
return {
label: i,
value: i
};
});
_type = 'select';
}
let _cmp = scriptTypeMap[_type];
return {
size: 'mini',
...(_cmp.props || {}),
options: _options
};
},
getScriptType(item, index) {
// 如果脚本选项对应的类型是数组,说明是枚举类型
let _type = item.type;
if (_type === 'enum') {
_type = 'select';
}
return scriptTypeMap[_type].component;
},
getScriptName(id) {
let _script = this.scripts.find(script => script.id === id);
return _script ? _script.name : '';
},
getScriptOptions(id) {
let _script = this.scripts.find(script => script.id === id);
return _script ? _script.props : {};
},
/**
* 删除节点脚本
*/
deleteNodeScript(index) {
let _scripts = _.cloneDeep(this.activeComponent.scripts);
_.remove(_scripts, (s, sindex) => {
return sindex === index;
});
this.$store.dispatch('modifyActiveView', {
scripts: _scripts
});
}
}
};
</script>
......@@ -306,17 +162,11 @@ export default {
.zero-inspector-props-form {
width: 100%;
padding-right: 10px;
.el-form-item--mini.el-form-item {
margin-bottom: 10px;
}
.el-divider__text {
background-color: #e9e9e9;
}
.zero-inspector-props-group {
width: 100%;
max-height: 600px;
overflow-x: hidden;
overflow-y: auto;
max-height: 500px;
}
}
.script-config-dialog {
......
<template>
<div class="zero-inspector-script-form" v-if="activeComponent.uuid">
<el-collapse v-model="configColl">
<el-collapse-item title="配置" name="properties">
<el-form ref="form" size="mini" :model="form" label-width="80px" @submit.native.prevent>
<el-form-item label="名称">
<el-input v-model="form.name" @input="v => handleChange('name', v)"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.type" @change="v => handleChange('type', v)" placeholder="请选择类型">
<el-option v-for="cmp in componentsMap" :key="cmp.value" :label="cmp.label" :value="cmp.value"></el-option>
</el-select>
</el-form-item>
<template v-for="(p, key) in cmpProps">
<el-form-item v-if="key !== 'groupName'" :id="activeComponent.uuid + '-' + key" :key="activeComponent.uuid + key" :label="p.title">
<!-- {{key}} -->
<dynamic-component :label="key" :item="p" :properties="activeComponent.properties"></dynamic-component>
</el-form-item>
<div class="zero-inspector-props-form" v-if="activeComponent.uuid">
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden">
<el-form ref="form" size="mini" :model="form" label-width="80px" @submit.native.prevent>
<el-collapse accordion v-if="activeComponent.scripts && activeComponent.scripts.length">
<template v-for="(script, index) in activeComponent.scripts">
<el-collapse-item :title="getScriptName(script.script)" :key="script + index">
<template v-for="(p, key) in getScriptOptions(script.script)">
<el-form-item :key="activeComponent.uuid + index + key" :label="p.alias|| key">
<dynamic-component :component-value="getScriptValue(p, key, index)" :component-props="getScriptProps(p, index)" :component-type="getScriptType(p, index)" @onChange="v => handleScriptChange(index, key, v)"></dynamic-component>
</el-form-item>
</template>
<el-form-item label="">
<el-button @click="deleteNodeScript(index)">删除</el-button>
</el-form-item>
</el-collapse-item>
</template>
</el-form>
</el-collapse-item>
<el-collapse-item title="脚本" name="scripts">
</el-collapse-item>
</el-collapse>
</el-collapse>
<div style="padding-top: 15px;text-align: center;">
<el-dropdown trigger="click" @command="handleAddScript" placement="top" size="small">
<el-button size="mini">{{$t('Add')}}</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(script, key) of scripts" :command="script.id" :key="key">{{script.name}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-form>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { componentsMap, getCmpProps } from '../../../utils/common';
import { mapGetters, mapState } from 'vuex';
import _ from 'lodash';
import dynamicComponent from '../components/dynamicComponent';
const scriptTypeMap = {
number: {
component: 'el-input-number',
props: {
size: 'mini'
}
},
string: {
component: 'el-input'
},
boolean: {
component: 'el-switch',
props: {
width: 40
}
},
color: {
component: 'el-color-picker',
props: {
'show-alpha': true
}
},
select: {
component: 'el-select',
props: {
slotComponent: 'el-option'
}
}
};
export default {
name: 'PropsTab',
name: 'ScriptsTab',
components: { 'dynamic-component': dynamicComponent },
data() {
return {
componentsMap,
form: {
name: '',
type: '',
properties: {}
scripts: []
},
configColl: ['properties']
};
},
computed: {
...mapGetters(['activeComponent', 'componentList']),
cmpProps: function() {
return getCmpProps(this.activeComponent.type);
}
},
watch: {
activeComponent: {
deep: true,
handler: function(val) {
this.form.name = val.name || '';
this.form.type = val.type || '';
this.form.properties = val.properties || {};
...mapGetters(['activeComponent', 'activeComponentCopy', 'componentList']),
...mapState({
scripts(state){
return state.env.scripts;
}
}
}),
},
methods: {
handleChange(label, v) {
this.$store.dispatch('modifyComponent', {
label: label,
value: v
/**
* 脚本预设对象选中
*/
handleAddScript(command) {
console.log('handleAddScript', command);
this.$store.dispatch('addNodeScript', command);
},
/**
* 事件属性发生改变
*/
handleScriptChange(index, key, v) {
let _props = {};
_props[key] = v;
let _scripts = _.cloneDeep(this.activeComponent.scripts);
let _script = _scripts[index];
_script.props = _.assign(_script.props, _props);
_scripts[index] = _script;
this.$store.dispatch('modifyActiveView', {
scripts: _scripts
});
},
getScriptValue(item, key, index) {
let _script = this.activeComponent.scripts[index];
let v = _script.props[key];
return v === undefined ? item.default : v;
},
getScriptProps(item, index) {
let _type = item.type;
let _options = [];
if (_type === 'enum') {
// 如果脚本选项对应的类型是数组,说明是枚举类型
_options = item.enum.map(i => {
return {
label: i,
value: i
};
});
_type = 'select';
}
let _cmp = scriptTypeMap[_type];
return {
size: 'mini',
...(_cmp.props || {}),
options: _options
};
},
getScriptType(item, index) {
// 如果脚本选项对应的类型是数组,说明是枚举类型
let _type = item.type;
if (_type === 'enum') {
_type = 'select';
}
return scriptTypeMap[_type].component;
},
getScriptName(id) {
let _script = this.scripts.find(script => script.id === id);
return _script ? _script.name : '';
},
getScriptOptions(id) {
let _script = this.scripts.find(script => script.id === id);
return _script ? _script.props : {};
},
/**
* 删除节点脚本
*/
deleteNodeScript(index) {
let _scripts = _.cloneDeep(this.activeComponent.scripts);
_.remove(_scripts, (s, sindex) => {
return sindex === index;
});
this.$store.dispatch('modifyActiveView', {
scripts: _scripts
});
}
}
......@@ -72,14 +165,17 @@ export default {
</script>
<style lang="scss">
.zero-inspector-script-form {
.zero-inspector-props-form {
width: 100%;
padding-right: 10px;
.el-form-item--mini.el-form-item {
margin-bottom: 10px;
}
.el-divider__text {
background-color: #e9e9e9;
}
.zero-inspector-props-group {
max-height: 500px;
}
}
.script-config-dialog {
height: 350px;
}
</style>
\ No newline at end of file
<template>
<div class="tool-bar">
<sample-menu :data="menu" @click-menu="clickMenu"/>
<upload-indicator/>
<div style="flex: 1"></div>
<div class="right-part">
<span>
......@@ -15,11 +14,10 @@
<script>
import {mapState, mapActions, mapMutations} from 'vuex'
import SampleMenu from "../../components/SampleMenu";
import UploadIndicator from "./ToolBar/UploadIndicator";
export default {
name: "ToolBar",
components: {UploadIndicator, SampleMenu},
components: {SampleMenu},
data() {
return {}
},
......@@ -30,8 +28,9 @@
menu: function () {
const menuConfig = this.$t('menu');
let menu = {};
for (let item of Object.keys(menuConfig)) {
menu[item] = {text: menuConfig[item], disabled: false}
for (let key of Object.keys(menuConfig)) {
let item = menuConfig[key];
menu[key] = {label: item.label, icon: item.icon, disabled: false}
}
if (menu['undo']) {
menu['undo']['disabled'] = !this.project.operateStack.length || this.project.operateStack.length === this.project.stackIndex + 1
......@@ -42,6 +41,11 @@
return menu;
}
},
async mounted() {
//监听键盘事件
//document.addEventListener('keydown', this.onKeyPress);
},
methods: {
clickMenu(menuItem) {
this.$emit('click-menu', menuItem);
......@@ -60,6 +64,25 @@
}).catch(() => {
});
},
onKeyPress(e) {
if (e.key === 'z' && (e.ctrlKey || e.metaKey) && (!e.shiftKey)) {
//快捷键ctrl+z执行撤销
e.preventDefault();
//是否可执行撤销
if(!(!this.project.operateStack.length || this.project.operateStack.length === this.project.stackIndex + 1)){
this.clickMenu("undo");
}
return false;
}else if (e.key === 'z' && (e.ctrlKey || e.metaKey) && (e.shiftKey)) {
//快捷键ctrl+shift+z执行重做
e.preventDefault();
//是否可以执行重做
if(!(this.project.stackIndex === 0)){
this.clickMenu("redo");
}
return false;
}
},
...mapMutations([
'modifyProject'
]),
......
......@@ -12,18 +12,33 @@
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="filter-bar">
<el-input class="filter-input" prefix-icon="el-icon-search" v-model="filterText" size="mini" clearable/>
<el-dropdown class="more-button" trigger="click" size="mini" @command="onFilterCommand">
<el-link icon="el-icon-more" :underline="false"></el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, key) in nodeFilterPresets" :command="key" :key="key">{{item}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!--<el-button size="mini" class="micro" icon="el-icon-magic-stick" circle @click="filterByBehavior"></el-button>-->
</div>
<el-scrollbar class="tree-scrollbar" wrap-class="wrap-x-hidden">
<el-tree
ref="tree"
:data="views"
:props="defaultProps"
:expand-on-click-node="false"
:default-expanded-keys="expandedKeys"
draggable
node-key="uuid"
highlight-current
:default-expand-all="false"
@node-click="handleNodeClick"
empty-text=""
:allow-drag="allowDrag"
:allow-drop="allowDrop"
:filter-node-method="filterNodeMethod"
@node-click="handleNodeClick"
>
<div slot-scope="{ node, data }" class="tree-node">
<div class="node-name">
......@@ -35,6 +50,9 @@
<el-link icon="el-icon-more" :underline="false" @click.stop/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="delete">{{$t('Delete')}}</el-dropdown-item>
<el-dropdown-item command="copy">{{$t('Copy')}}</el-dropdown-item>
<el-dropdown-item command="paste">{{$t('Paste same level')}}</el-dropdown-item>
<el-dropdown-item command="pasteChild">{{$t('Paste child')}}</el-dropdown-item>
<el-dropdown-item command="export" divided v-if="!node.parent.parent">{{$t('Export')}}
</el-dropdown-item>
<el-dropdown-item v-for="(type, key, index) in $t('view_node_menu')"
......@@ -53,7 +71,7 @@
</template>
<script>
import {mapState, mapMutations, mapActions} from 'vuex';
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
import Pane from '../../components/Pane';
import {selectFile} from "../../utils";
import events from "../../global-events";
......@@ -63,6 +81,7 @@
components: {Pane},
data() {
return {
filterText: '',
defaultProps: {
children: 'children',
label: 'name'
......@@ -70,14 +89,85 @@
uploadHeaders: {
authorization: 'Bearer ' + window['zeroing_token'],
},
expandedKeys: [],
nodeFilterPresets: this.$t('nodeFilterPresets'),
};
},
computed: {
...mapGetters(['activeComponentId']),
...mapState({
views: state => state.project.data.views
})
},
watch: {
activeComponentId: function (val) {
this.$refs.tree.setCheckedKeys([val]); // 设置选中节点
this.$refs.tree.setCurrentKey(val); // 设置高亮节点
this.expandedKeys = [val]; // 展开对应的节点
},
filterText(val) {
this.updateFilter();
},
},
methods: {
updateFilter() {
if (this.$refs.tree) {
this.$refs.tree.filter(this.filterText);
}
},
filterNodeMethod(filterText, data) {
let visible = false;
if (filterText.startsWith(':')) {
let remain = filterText.substr(1);
let arr = remain.split('|');
let cmd = arr[0];
let param = arr[1];
switch (cmd) {
case 'trigger':
if (data.events && Object.keys(data.events).length > 0) {
visible = true;
if (param && param.length > 0) {
let events = param.split(',');
let exists = false;
for (let event of events) {
if (data.events[event]) {
exists = true;
break;
}
}
visible = exists;
}
}
break;
case 'type':
if (param && param.length > 0) {
let types = param.split(',');
visible = types.includes(data.type);
}else{
visible = true;
}
break;
default:
visible = true;
}
} else {
visible = data.name.toLowerCase().includes(filterText.toLowerCase());
}
return visible;
},
onFilterCommand(command) {
let filterText;
switch (command) {
case 'trigger':
filterText = ':trigger';
break;
case 'type':
filterText = ':type|node';
break;
}
this.filterText = filterText;
},
allowDrag(draggingNode) {
return draggingNode.parent.parent;
},
......@@ -140,6 +230,26 @@
parentNode: node.parent.data
});
break;
case 'copy':
this.copyNode({
node: data,
parentNode: node.parent.data
});
break;
case 'paste':
this.pasteNode({
node: data,
parentNode: node.parent.data,
pasteState: 1
});
break;
case 'pasteChild':
this.pasteNode({
node: data,
parentNode: node.parent.data,
pasteState: 2
});
break;
case 'export':
this.exportView(data);
break;
......@@ -155,11 +265,23 @@
break;
}
},
...mapMutations(['deleteNode', 'addNode']),
...mapMutations(['copyNode', 'pasteNode', 'deleteNode', 'addNode']),
...mapActions(['exportView', 'importView'])
}
};
</script>
<style scoped>
<style scoped lang="scss">
.filter-bar {
display: flex;
align-items: center;
}
.filter-input {
flex: 1;
}
.more-button {
margin: 0 3px;
}
</style>
\ No newline at end of file
......@@ -4,20 +4,23 @@
<split-panes splitpanes-min="10" :splitpanes-size="10" horizontal>
<process-list @edit-meta="onEditMeta" @delete-meta="onDeleteMeta" :data="prefabProcessTree"
class="background full-size" splitpanes-min="20"
:splitpanes-size="30"/>
:splitpanes-size="20"/>
<process-list @edit-meta="onEditMeta" @delete-meta="onDeleteMeta" :data="normalProcessTree"
class="background full-size" splitpanes-min="20"
:splitpanes-size="70"/>
:splitpanes-size="80"/>
</split-panes>
<div class="center full-size background" splitpanes-min="20" :splitpanes-size="70">
<edit-path :processStack="processStack" @pop="onPop"/>
<div class="operate-bar">
<!--<el-button-group>
<div class="top-bar">
<edit-path :processStack="processStack" @pop="onPop"/>
<el-button icon="el-icon-search" circle plain size="mini" @click="onClickSearch"></el-button>
</div>
<!--<div class="operate-bar">
<el-button-group>
<el-button size="mini" icon="el-icon-zoom-out" @click="setScale(-0.1)"/>
<el-button size="mini" @click="setScale(0)">1:1</el-button>
<el-button size="mini" icon="el-icon-zoom-in" @click="setScale(0.1)"/>
</el-button-group>-->
</div>
</el-button-group>
</div>-->
<board ref="board" @select-process-node="onSelectProcessNode" @edit-process="editProcess" @edit-meta="onEditMeta"/>
</div>
<div class="properties background full-size" splitpanes-min="20" :splitpanes-size="20">
......@@ -25,6 +28,7 @@
</div>
</split-panes>
<meta-editor-dialog ref="metaEditorDialog" @input="onSaveMeta"/>
<meta-search-dialog ref="metaSearchDialog"/>
</div>
</template>
......@@ -37,10 +41,13 @@
import EditPath from "./Board/EditPath";
import Process from "./Board/Process";
import MetaEditorDialog from "./MetaEditorDialog";
import events from "@/global-events.js"
import MetaSearchDialog from "./MetaSearchDialog";
export default {
name: "BehaviorEditor",
components: {MetaEditorDialog, PropertiesEditor, EditPath, ProcessList, Board, SplitPanes,},
components: {MetaSearchDialog, MetaEditorDialog, PropertiesEditor, EditPath, ProcessList, Board, SplitPanes,},
props: [],
data() {
return {
......@@ -97,6 +104,9 @@
this.$refs.board.edit(process, this.resolveProcess);
this.$refs.properties.edit();
},
onClickSearch(){
this.$refs.metaSearchDialog.show();
},
onEditMeta(meta) {
this.metaInEditing = meta;
this.$refs.metaEditorDialog.edit(meta);
......@@ -118,7 +128,7 @@
});
}
},
onSaveMeta(meta) {
onSaveMeta(meta,isPreview) {
let oldMetaID = this.metaInEditing.id;
for (let key in meta) {
this.metaInEditing[key] = meta[key];
......@@ -131,6 +141,10 @@
});
}
this.$refs.board.updateProcessNode(meta.id);
//是否预览
if(isPreview){
events.$emit('behaviorSave',isPreview)
}
},
...mapMutations([
'updateProcesses',
......
<template>
<div class="board" @dragover="onDragOver" @drop="onDrop" v-resize="onResize">
<div class="board" @dragover="onDragOver" @drop="onDrop" @paste="onPaste" v-resize="onResize">
<div style="height: 1px;"></div>
<!--<el-scrollbar class="full-size">-->
<svg ref="board" class="svg-board full-size" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" @mousedown="onBoardMouseDown">
......@@ -14,8 +15,9 @@
@click="onClickProcessNode(process, key)"
@hover-pin="onPinHover"
@leave-pin="onPinLeave"
@down-pin="onPintDown"
@down-pin="onPinDown"
@delete="onProcessNodeDelete"
@copy="onProcessNodeCopy"
@edit-meta="onEditMeta"
@dblclick="editSubProcess(process)"
@meta-modified="onProcessMetaModified"
......@@ -25,7 +27,6 @@
</svg>
<!--</el-scrollbar>-->
<tool-tip ref="toolTip"/>
<inline-choose-dialog ref="inlineChooseDialog"/>
</div>
</template>
......@@ -39,13 +40,14 @@
import {DOCK_POINT_OFFSET} from "../../../config";
import events from "../../../global-events";
import generateUUID from "uuid/v4";
import InlineChooseDialog from "./InlineChooseDialog";
import copy from 'copy-to-clipboard'
import {clonePureObj} from "../../../utils";
const customs = ['custom', 'divider'];
export default {
name: "Board",
components: {InlineChooseDialog, ToolTip, LinkLine, ProcessNode,},
components: {ToolTip, LinkLine, ProcessNode,},
props: [],
data() {
return {
......@@ -113,30 +115,39 @@
},
addSubProcess(uuid, data) {
const process = new Process(this.process, data, this.resolveProcess);
this.$set(this.subProcessMap, uuid, process);
if (process.meta) {
this.$set(this.subProcessMap, uuid, process);
} else {
console.log('节点丢失:', uuid);
}
},
showInlineChoose() {
return this.$confirm(this.$t('As inline'));
},
async addSubProcessData(processId, pos) {
let meta, isInline;
if (customs.includes(processId)) {
try {
const result = await this.$refs.inlineChooseDialog.show();
isInline = result.isInline;
await this.showInlineChoose();
isInline = true;
} catch (e) {
return;
}
meta = await this.addCustomProcessMeta({masterProcess: this.process, isInline, processId});
}else{
} else {
meta = this.process.resolveMeta(processId);
}
if(meta.isPrefab){
if (meta.isPrefab) {
try {
const result = await this.$refs.inlineChooseDialog.show();
isInline = result.isInline;
await this.showInlineChoose();
isInline = true;
} catch (e) {
return;
}
meta = await this.addProcessFromPrefab({masterProcess: this.process, isInline, meta})
}
......@@ -170,7 +181,7 @@
const data = await this.addSubProcessData(processId, {
x: e.offsetX - this.boardOffset.x - 9,
y: e.offsetY - this.boardOffset.y,
y: e.offsetY - this.boardOffset.y - 20,
});
if (!data) {
return;
......@@ -181,6 +192,30 @@
events.$emit('update-dock-pin-pos');
});
},
onPaste(e) {
let dataStr = e.clipboardData.getData("process-data");
if (dataStr) {
this.addProcessFromCopy(dataStr);
}
},
addProcessFromCopy(avatar) {
if (typeof avatar === 'string') {
avatar = JSON.parse(avatar);
}
avatar.uuid = generateUUID();
avatar.design.x += 20;
avatar.design.y += 20;
avatar.output = {};
delete avatar.design['output'];
delete avatar.design['input'];
this.$set(this.process.meta.sub, avatar.uuid, avatar);
this.addSubProcess(avatar.uuid, avatar);
this.$nextTick(() => {
events.$emit('update-dock-pin-pos');
});
},
onResize() {
const {x, y} = this.$el.getBoundingClientRect();
this.drawState.boardOffset.x = x + this.boardOffset.x;
......@@ -220,6 +255,17 @@
outputIndex,
});
this.drawState.lineID++;
} else {
for (let uuid in this.subProcessMap) {
const process = this.subProcessMap[uuid];
for (let key in process.data.output) {
let item = process.data.output[key];
let index = item.indexOf(outputID);
if(index >= 0){
item.splice(index, 1);
}
}
}
}
},
onPinHover(x, y, pin) {
......@@ -228,7 +274,7 @@
onPinLeave(x, y, pin) {
this.$refs.toolTip.hide();
},
onPintDown(e, process, pin) {
onPinDown(e, process, pin) {
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
......@@ -270,6 +316,14 @@
this.$delete(this.subProcessMap, process.uuid);
this.$delete(this.process.meta.sub, process.uuid);
let remain = 0;
for(let key in this.process.meta.sub){
let p = this.process.meta.sub[key];
if (p.meta === meta.id) {
remain ++;
}
}
for (let id in this.lines) {
const line = this.lines[id];
const {prev, next} = line;
......@@ -280,9 +334,25 @@
}
if (meta.isDivider || meta.isInline) { //如果是分流节点或者内联节点还要删除对应的meta
this.deleteProcessMeta({
meta,
process: this.process,
if(remain === 0){
this.deleteProcessMeta({
meta,
process: this.process,
});
}
}
},
onProcessNodeCopy(data, meta) {
if (meta.isInline) { //内联直接复制
let avatar = clonePureObj(data);
this.addProcessFromCopy(avatar);
} else {
copy(JSON.stringify(data), {format: 'process-data'});
this.$message({
message: this.$t('Copied process to clipboard'),
type: 'success',
duration: 700,
});
}
},
......
......@@ -18,7 +18,7 @@
},
methods: {
parseName(process){
return process.data.alias || process.meta.name;
return process.meta.name;
},
onClickItem(process, index){
this.$emit('pop', index);
......
......@@ -5,6 +5,7 @@
<div class="top-bar" v-if="meta.id !== 'entry' && editable">
<el-link icon="el-icon-delete" :underline="false" @mousedown.stop.prevent @click.stop="onClickDelete"/>
<el-link icon="el-icon-edit" :underline="false" v-if="meta.type !== 'builtin' && !meta.isDivider" @mousedown.stop.prevent @click.stop="onClickEdit"/>
<el-link icon="el-icon-document-copy" :underline="false" @mousedown.stop.prevent @click.stop="onClickCopy"/>
</div>
<div class="header">
<i v-if="meta.isInline">i</i>
......@@ -231,6 +232,9 @@
onClickEdit() {
this.$emit('edit-meta', this.data, this.meta);
},
onClickCopy() {
this.$emit('copy', this.data, this.meta);
},
outputPointModify(action) {
let output = this.meta.output;
let count = output.length;
......
<template>
<codemirror ref="codeEditor"
:value="value"
:options="cmOptions"
@input="onChange"
@inputRead="inputRead"/>
</template>
<script>
import {codemirror} from "vue-codemirror";
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/ayu-mirage.css'
import 'codemirror/addon/edit/closebrackets.js'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/lint/lint.js'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/addon/lint/javascript-lint.js'
import 'codemirror/addon/search/searchcursor.js'
import 'codemirror/addon/search/search.js'
import 'codemirror/addon/search/jump-to-line.js'
import 'codemirror/addon/dialog/dialog.js'
import 'codemirror/addon/dialog/dialog.css'
import '../../../utils/javascript-hint.js'
export default {
name: "CodeEditor",
components: {codemirror},
data(){
return {
cmOptions: {
tabSize: 2,
styleActiveLine: true,
theme: 'ayu-mirage',
line: true,
matchBrackets: true,
autoCloseBrackets: true,
extraKeys: {
"Alt-Space": "autocomplete",
},
lineNumbers: true,
mode: {name: 'javascript', globalVars: true},
gutters: ["CodeMirror-lint-markers"],
lint: {
esversion: 6
},
keyword: {
caseInsensitive: true,
}
}
}
},
props: {
value: {
type: String,
default: '',
}
},
methods:{
inputRead(codemirror) {
if (codemirror.state.completionActive) {
return;
}
let cur = codemirror.getCursor();
let token = codemirror.getTokenAt(cur);
let string = '';
if (token.string.match(/^[.`\w@]\w*$/)) {
string = token.string;
}
if (string.length > 0) {
codemirror.showHint({ completeSingle: false });
}
},
onChange(v){
this.$emit('input', v);
},
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<el-dialog
:title="$t('Alert')"
:visible.sync="dialogVisible"
append-to-body
@close="onClose"
>
<el-checkbox v-model="asInline">{{$t('As inline')}}</el-checkbox>
<span slot="footer" class="dialog-footer">
<el-button size="mini" @click="dialogVisible = false">{{$t('Cancel')}}</el-button>
<el-button size="mini" type="primary" @click="onConfirm">{{$t('Confirm')}}</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
name: "InlineChooseDialog",
data() {
return {
dialogVisible: false,
asInline: false,
}
},
methods: {
show() {
this.dialogVisible = true;
return new Promise((resolve, reject) => {
this.p = {resolve, reject};
});
},
onClose() {
if (this.p) {
this.p.reject();
this.p = null;
}
this.dialogVisible = false;
},
onConfirm() {
if (this.p) {
this.p.resolve({
isInline: this.asInline,
});
this.p = null;
}
this.dialogVisible = false;
},
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<el-dialog :title="$t('Meta Editor')" width="80%" :visible.sync="visible"
:close-on-click-modal="false"
:append-to-body="true">
<div class="meta-editor-wrapper">
<el-form ref="form" v-if="meta" :model="meta" :rules="rules" :show-message="false" class="info-editor" size="mini" label-position="right" label-width="70px" @submit.native.prevent>
<template>
<el-form-item prop="id" :label="$t('ID')">
<el-input v-model="meta.id" :placeholder="$t('ID')" :readonly="!editable"/>
</el-form-item>
<el-form-item prop="name" :label="$t('Name')">
<el-input v-model="meta.name" :placeholder="$t('Name')" :readonly="!editable"/>
</el-form-item>
<el-form-item prop="desc" :label="$t('Desc')">
<el-input v-model="meta.desc" :placeholder="$t('Description')" :readonly="!editable"/>
</el-form-item>
<!--<el-form-item prop="type" label="Type">
<el-dialog
:title="$t('Meta Editor')"
width="80%"
:visible.sync="visible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
fullscreen
:append-to-body="true"
custom-class="flex-dialog behavior-editor-dialog"
>
<div class="meta-editor-wrapper">
<el-form
ref="form"
v-if="meta"
:model="meta"
:rules="rules"
:show-message="false"
class="info-editor"
size="mini"
label-position="right"
label-width="70px"
@submit.native.prevent
>
<template>
<el-form-item prop="id" :label="$t('ID')">
<el-input
v-model="meta.id"
:placeholder="$t('ID')"
:readonly="!editable"
/>
</el-form-item>
<el-form-item prop="name" :label="$t('Name')">
<el-input
v-model="meta.name"
:placeholder="$t('Name')"
:readonly="!editable"
/>
</el-form-item>
<el-form-item prop="desc" :label="$t('Desc')">
<el-input
v-model="meta.desc"
:placeholder="$t('Description')"
:readonly="!editable"
/>
</el-form-item>
<!--<el-form-item prop="type" label="Type">
<el-input v-model="meta.type" :placeholder="$t('Type')" :readonly="!editable"/>
</el-form-item>
<el-form-item prop="group" label="Group">
<el-input v-model="meta.group" :placeholder="$t('Group')" :readonly="!editable"/>
</el-form-item>-->
<el-form-item :label="$t('Props')">
<el-link :underline="false" @click="onClickEditProps" :disabled="!editable">
<template v-if="Object.keys(meta.props).length">
<el-tag type="success" size="mini" v-for="(option, key) in meta.props" :key="key">{{key}}</el-tag>
</template>
<template v-else>{{$t('Empty')}}</template>
</el-link>
</el-form-item>
<el-form-item :label="$t('Output')">
<div style="display: flex;flex: 1;">
<el-select style="flex: 1;" v-model="meta.output" :disabled="!editable" allow-create filterable multiple
:placeholder="$t('Output')"/>
</div>
</el-form-item>
</template>
</el-form>
<div style="margin-top: 5px;">
<el-tag v-for="item in exposeVariables" size="mini">{{item}}</el-tag>
</div>
<el-input v-if="meta" class="script-editor" :readonly=" !editable" type="textarea" :placeholder="$t('Code')"
v-model="meta.script"></el-input>
</div>
<div slot="footer" class="dialog-footer">
<div class="button-bar">
<el-button-group style="margin-right: 20px;">
<el-button size="mini" plain @click="copyMeta">CopyMeta</el-button>
<el-popover>
<div ref="pasteBoard" @paste="onPaste">Click and Ctrl+V</div>
<el-button size="mini" slot="reference" plain @click="pasteMeta">PasteMeta</el-button>
</el-popover>
</el-button-group>
<el-button size="mini" plain @click="cancel">{{$t('Cancel')}}</el-button>
<el-button size="mini" plain @click="save">{{$t('Save')}}</el-button>
</div>
</div>
<props-editor-dialog ref="propsEditorDialog"/>
</el-dialog>
</el-form-item>-->
<el-form-item :label="$t('Props')">
<el-link
:underline="false"
@click="onClickEditProps"
:disabled="!editable"
>
<template v-if="Object.keys(meta.props).length">
<el-tag
type="success"
size="mini"
v-for="(option, key) in meta.props"
:key="key"
>{{ key }}</el-tag
>
</template>
<template v-else>{{ $t("Empty") }}</template>
</el-link>
</el-form-item>
<el-form-item :label="$t('Output')">
<div style="display: flex;flex: 1;">
<el-select
style="flex: 1;"
v-model="meta.output"
:disabled="!editable"
allow-create
filterable
multiple
:placeholder="$t('Output')"
/>
</div>
</el-form-item>
</template>
</el-form>
<div style="margin-top: 5px;">
<code-sync-indicator />
<el-tag v-for="(item, key) in exposeVariables" :key="key" size="mini">{{
item
}}</el-tag>
</div>
<!--<el-input v-if="meta" class="script-editor" :readonly=" !editable" type="textarea" :placeholder="$t('Code')"
v-model="meta.script"></el-input>-->
<!-- <codemirror
ref="codeEditor"
v-if="meta"
v-model="meta.script"
:options="cmOptions"
@cursorActivity="onCodeChange"
/>-->
<monaco-editor
ref="codeEditor"
v-if="meta"
:code="meta.script"
:options="monacoConfig"
/>
</div>
<div slot="footer" class="dialog-footer">
<div class="button-bar">
<el-button-group style="margin-right: 20px;">
<el-button size="mini" plain @click="copyMeta">CopyMeta</el-button>
<el-popover>
<div ref="pasteBoard" @paste="onPaste">Click and Ctrl+V</div>
<el-button size="mini" slot="reference" plain @click="pasteMeta"
>PasteMeta</el-button
>
</el-popover>
</el-button-group>
<el-button size="mini" plain @click="cancel">{{
$t("Cancel")
}}</el-button>
<!--<el-button size="mini" plain @click="save(true)">{{$t('Save And Preview')}}</el-button>-->
<el-button size="mini" type="primary" plain @click="save(false)">{{
$t("Save")
}}</el-button>
</div>
</div>
<props-editor-dialog ref="propsEditorDialog" />
</el-dialog>
</template>
<script>
import ElFormItem from "./inputs/form-item";
import PropsEditorDialog from "./PropsEditorDialog";
import copy from 'copy-to-clipboard'
import {clonePureObj} from "../../../utils";
import ElFormItem from "./inputs/form-item";
import PropsEditorDialog from "./PropsEditorDialog";
import * as monaco from "monaco-editor";
import MonacoEditor from "../components/MonacoEditor";
import copy from "copy-to-clipboard";
import { clonePureObj } from "../../../utils";
import events from "../../../global-events";
import CodeSyncIndicator from "../BottomBar/CodeSyncIndicator";
const exposeVariables = ['args', 'props', 'target', 'global', 'vm', 'engine'];
const exposeVariables = ["args", "props", "target", "global", "vm", "engine"];
export default {
name: "MetaEditorDialog",
components: {PropsEditorDialog, ElFormItem},
data() {
return {
visible: false,
meta: null,
propsEditorVisible: false,
exposeVariables,
export default {
name: "MetaEditorDialog",
components: {
CodeSyncIndicator,
PropsEditorDialog,
ElFormItem,
monaco,
"monaco-editor": MonacoEditor
},
data() {
return {
visible: false,
meta: null,
propsEditorVisible: false,
exposeVariables,
rules: {
id: [
{ required: true, trigger: 'blur' },
],
name: [
{ required: true, trigger: 'blur' }
],
},
}
},
computed: {
editable() {
return this.meta;// && this.meta.type !== 'builtin';
},
props() {
return Object.keys(this.meta.props).join(',')
},
},
methods: {
edit(meta) {
this.visible = true;
this.meta = clonePureObj(meta);
this.oldMetaID = this.meta.id;
},
onClickEditProps() {
this.$refs.propsEditorDialog.edit(this.meta.props);
},
save() {
this.$refs.form.validate((valid) => {
if (valid) {
if (this.oldMetaID !== this.meta.id && this.$store.getters.metaIDExists(this.meta.id)) {
this.$alert(this.$t('This Meta ID is in use, can not save'), this.$t('Alert'))
.catch((e) => {
});
} else {
this.$emit('input', this.meta);
this.visible = false;
}
} else {
return false;
}
});
},
cancel() {
this.visible = false;
},
copyMeta() {
copy(JSON.stringify(this.meta));
},
pasteMeta() {
//setTimeout(this.focusPasteBoard, 1000, this);
},
onPaste(e) {
let metaStr = e.clipboardData.getData("Text");
if(metaStr){
try {
let meta = JSON.parse(metaStr);
this.meta = meta;
}catch (e) {
rules: {
id: [{ required: true, trigger: "blur" }],
name: [{ required: true, trigger: "blur" }]
},
cmOptions: {
tabSize: 2,
styleActiveLine: true,
theme: "default",
line: true,
matchBrackets: true,
autoCloseBrackets: true,
extraKeys: {
"Alt-Space": "autocomplete"
},
}
}
},
focusPasteBoard(){
this.$refs.pasteBoard.focus();
},
}
}
</script>
lineNumbers: true,
mode: { name: "javascript", globalVars: true },
gutters: ["CodeMirror-lint-markers"],
lint: true
},
monacoConfig: {
language: "javascript"
}
};
},
mounted() {
events.$on("edit-save", this.onEditSave);
},
destroyed() {
events.$off("edit-save", this.onEditSave);
},
computed: {
editable() {
return this.meta; // && this.meta.type !== 'builtin';
},
props() {
return Object.keys(this.meta.props).join(",");
}
},
methods: {
edit(meta) {
this.visible = true;
this.meta = clonePureObj(meta);
this.oldMetaID = this.meta.id;
<style scoped>
events.$emit("edit-open", this.meta.script);
},
onEditSave(code) {
this.$set(this.meta, "script", code);
},
onClickEditProps() {
this.$refs.propsEditorDialog.edit(this.meta.props);
},
save(isPreview) {
this.$refs.form.validate(valid => {
if (valid) {
if (
this.oldMetaID !== this.meta.id &&
this.$store.getters.metaIDExists(this.meta.id)
) {
this.$alert(
this.$t("This Meta ID is in use, can not save"),
this.$t("Alert")
).catch(e => {});
} else {
this.meta.script = this.$refs.codeEditor.editor.getValue();
this.$emit("input", this.meta, isPreview);
this.visible = false;
}
} else {
return false;
}
});
},
cancel() {
this.visible = false;
},
copyMeta() {
copy(JSON.stringify(this.meta));
},
pasteMeta() {
//setTimeout(this.focusPasteBoard, 1000, this);
},
onPaste(e) {
let metaStr = e.clipboardData.getData("Text");
if (metaStr) {
try {
let meta = JSON.parse(metaStr);
this.meta = meta;
} catch (e) {}
}
},
focusPasteBoard() {
this.$refs.pasteBoard.focus();
},
onCodeChange(codemirror) {
//codemirror.showHint();
//console.log(code);
}
}
};
</script>
</style>
\ No newline at end of file
<style scoped></style>
<template>
<el-dialog :title="$t('Meta Search')" width="80%" :visible.sync="visible"
:append-to-body="true"
custom-class="behavior-editor-dialog"
>
<div style="padding: 10px;">
<el-input size="mini" v-model="keyword">
<el-button slot="append" icon="el-icon-search" @click="search"></el-button>
</el-input>
<div style="height: 50vh;">
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="mini" plain @click="close">{{$t('Close')}}</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: "MetaSearchDialog",
data() {
return {
visible: false,
keyword: '',
}
},
methods:{
show(){
this.visible = true;
},
close(){
this.visible = false;
},
search(){
},
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="process-tree">
<el-input v-model="filterText" size="mini"/>
<el-input v-model="filterText" prefix-icon="el-icon-search" size="mini" clearable/>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden">
<el-tree
ref="tree"
......@@ -11,7 +11,7 @@
>
<div slot-scope="{ node, data }" class="process-tree-node">
<div class="node-name">
<span :draggable="draggable(data)" @dragstart.stop="dragProcessStart(data, $event)">{{data.name}}</span>
<span :class="{'current-node': isCurrentProcess(data)}" :draggable="draggable(data)" @dragstart.stop="dragProcessStart(data, $event)">{{data.name}}</span>
</div>
<el-dropdown v-if="nodeMenu(data)" class="more-button" size="mini" trigger="click"
......@@ -31,6 +31,8 @@
</template>
<script>
import {mapState} from 'vuex'
const editableIds = ['custom', 'divider'];
export default {
......@@ -59,6 +61,13 @@
deep: true
}
},
computed: {
...mapState({
currentProcess(state){
return state.behavior.currentProcess;
}
}),
},
methods: {
updateFilter(){
if(this.$refs.tree){
......@@ -99,6 +108,13 @@
let filterDivider = data.isDivider !== true;
if (!value) return filterDivider;
return filterDivider && !data.hasOwnProperty('children') && data.name.toUpperCase().indexOf(value.toUpperCase()) >= 0;
},
isCurrentProcess(data){
let yes= this.currentProcess && this.currentProcess.meta && data.id && data.id === this.currentProcess.meta.id;
if(yes){
console.log();
}
return yes;
}
},
......
......@@ -5,7 +5,7 @@
</el-input>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden"
view-class="scrollbar-view">
<el-form v-model="process" size="mini" label-width="80px" label-position="left" @submit.native.prevent>
<el-form v-model="process" size="mini" label-width="100px" label-position="left" @submit.native.prevent>
<component v-for="(property, key) in process.meta.props"
:is="getInput(property)"
v-model="process.data.props[key]"
......
<template>
<el-form-item class="input-wrapper" :label="property.alias || propertyName" content-float="right"
<el-form-item class="input-wrapper" :label="propertyName" content-float="right"
:content-width="contentWidth" :labelOffsetTop="labelOffsetTop">
<el-tooltip placement="top" class="label" slot="label" trigger="hover" :open-delay="500" :enterable="false">
<div slot="content" class="property-name-popover">
<p>{{$t('Alias')}}{{property.alias}}</p>
<p>{{$t('Name')}}{{propertyName}}</p>
</div>
<div>
<p class="alias">{{property.alias}}</p>
<p class="property-name">{{propertyName}}</p>
</div>
</el-tooltip>
<template v-if="linked">
<span class="linked">Linked to parent</span>
</template>
......@@ -48,13 +58,11 @@
default: 0,
},
},
data(){
return {
}
data() {
return {}
},
watch: {
value(v){
value(v) {
}
},
......
......@@ -19,7 +19,7 @@
<dynamic-selector style="flex: 1;" v-model="item.value"
:editable="editable"/>
<el-button class="delete-button" icon="el-icon-minus" size="mini" plain circle type="danger"
@click="addMapItem"/>
@click="deleteMapItem(index)"/>
</div>
</div>
<div class="bottom-bar">
......@@ -85,6 +85,9 @@
addMapItem() {
this.editValue.push({});
},
deleteMapItem(index) {
this.editValue.splice(index, 1);
},
save() {
let v = {type: 'map', value: {}};
for (let item of this.editValue) {
......
<template>
<el-checkbox v-model="editValue"/>
</template>
<script>
export default {
name: "EnabledSetter",
props: ['target'],
computed: {
editValue: {
get() {
return !this.target.disabled;
},
set(v) {
this.$set(this.target, 'disabled', !v);
}
}
},
methods: {}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div id="container"></div>
</template>
<script>
import * as monaco from "monaco-editor";
export default {
name: "MonacoEditor",
props: {
options: Object,
code: String
},
mounted() {
const { options, code } = this;
const defaultConfig = {
language: "javascript",
value: "",
minimap: {
enabled: false
}
};
// compiler options
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES6,
allowNonTsExtensions: true
});
// extra libraries
monaco.languages.typescript.javascriptDefaults.addExtraLib(
[window.__data.types].join("\n")
);
const config = Object.assign({}, defaultConfig, options, { value: code });
console.log(config);
this.editor = monaco.editor.create(
document.getElementById("container"),
config
);
}
};
</script>
<style lang="scss" scoped>
#container {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
<template>
<div class="views-tree">
<custom-node v-for="view in views" :key="view.uuid" :custom-style="styleObject(view)" :properties="view.properties">
<custom-node v-for="view in views" :key="view.uuid" :view="view" :custom-style="styleObject(view)" :properties="view.properties">
<template v-if="view.children">
<views-tree :views="view.children"></views-tree>
</template>
......
<template>
<el-dialog :title="$t('Behavior Editor')" :visible.sync="visible" :before-close="beforeClose" @opened="onOpened"
:fullscreen="true" :close-on-click-modal="false" :close-on-press-escape="false"
:append-to-body="true" custom-class="behavior-editor-dialog">
:append-to-body="true" custom-class="flex-dialog behavior-editor-dialog">
<behavior-editor v-if="editorReady" ref="behaviorEditor" class="full-size"></behavior-editor>
<div slot="footer" class="dialog-footer">
<el-button size="mini" type="primary" @click="onSave">{{$t('Save')}}</el-button>
<el-button size="mini" @click="onSave(true)">{{$t('Save And Preview')}}</el-button>
<el-button size="mini" type="primary" @click="onSave(false)">{{$t('Save And Close')}}</el-button>
</div>
</el-dialog>
</template>
<script>
import {mapState, mapMutations} from 'vuex'
import {mapState, mapMutations, mapActions} from 'vuex'
import BehaviorEditor from "../behavior-editor/BehaviorEditor";
import events from "@/global-events.js"
export default {
name: "BehaviorEditorDialog",
......@@ -28,20 +30,34 @@
}),
},
created() {
events.$on('behaviorSave', (isPreview) => {
this.onSave(isPreview)
});
},
methods: {
show(behaviors, event) {
show(behavior, behaviors) {
this.behaviors = behaviors;
this.behavior_startEdit({
originData: this.data,
behaviors,
event,
behavior,
});
this.editorReady = false;
this.visible = true;
},
onSave() {
this.behavior_save();
this.visible = false;
this.$emit('change');
async onSave(isPreview) {
const behavior = await this.behavior_save();
for (let i = 0, li = this.behaviors.length; i < li; i++) {
const b = this.behaviors[i];
if (b.uuid === behavior.uuid) {
this.$set(this.behaviors, i, behavior);
break;
}
}
this.$emit('change', isPreview);
if (!isPreview) {
this.visible = false;
}
},
beforeClose(done) {
this.$confirm(this.$t('Save this behavior before'), this.$t('Alert'), {
......@@ -68,9 +84,11 @@
},
...mapMutations([
'behavior_startEdit',
'behavior_save',
'behavior_cancel',
]),
...mapActions([
'behavior_save',
])
}
}
</script>
......
<template>
<el-dialog :title="$t('CodeSyncServe')" width="70%" :visible.sync="visible"
:append-to-body="true"
custom-class=""
>
<div>
<el-form label-position="right" label-width="50px" size="mini">
<el-form-item label="IP">
<el-input v-model="config.ip"/>
</el-form-item>
<el-form-item label="Port">
<el-input-number v-model="config.port" controls-position="right" :max="65535"/>
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<div>
</div>
<div>
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
<el-button size="mini" @click="onSave" type="primary">{{$t('Save')}}</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import {mapMutations, mapState} from 'vuex'
import EnabledSetter from "../components/EnabledSetter";
import ElFormItem from "../behavior-editor/inputs/form-item";
import {clonePureObj} from "../../../utils";
import {startCodeSyncServe} from "../../../code-sync-serve";
export default {
name: "CodeSyncServeDialog",
components: {ElFormItem, EnabledSetter,},
data() {
return {
visible: false,
config: {},
}
},
mounted() {
},
computed: {
...mapState({
codeSyncServeConfig(state) {
return state.env.codeSyncServeConfig;
}
})
},
methods: {
show() {
this.config = clonePureObj(this.codeSyncServeConfig);
this.visible = true;
},
onSave() {
this.saveCodeSyncServeConfig(this.config);
startCodeSyncServe(this.config);
this.visible = false;
},
onClose() {
this.visible = false;
},
...mapMutations([
'saveCodeSyncServeConfig',
]),
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<el-dialog :title="$t('Project details')" width="70%" :visible.sync="visible" @open="onOpen"
<el-dialog :title="$t('Project details')" width="70%" :visible.sync="visible" @opened="onOpen"
:close-on-click-modal="false"
:append-to-body="true"
:show-close="false"
custom-class="details-dialog"
fullscreen
custom-class="flex-dialog details-dialog"
>
<el-tabs v-model="activeName">
<el-tabs v-model="activeName" class="tabs">
<el-tab-pane :label="$t('Project')" name="project">
<project-editor/>
<project-editor ref="projectEditor"/>
</el-tab-pane>
<el-tab-pane :label="$t('Env constant')" name="env">
<env-editor/>
<env-editor ref="envEditor"/>
</el-tab-pane>
<el-tab-pane :label="$t('Data mapping')" name="data-mapping">
<data-mapping-editor/>
<data-mapping-editor ref="dataMappingEditor"/>
</el-tab-pane>
<el-tab-pane :label="$t('Custom module')" name="custom">
<custom-editor/>
<custom-module-editor ref="customModuleEditor"/>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
<el-button size="mini" @click="onSave" type="primary">{{$t('Save')}}</el-button>
</div>
</el-dialog>
</template>
<script>
import {mapMutations} from 'vuex';
import ProjectEditor from "./editors/ProjectEditor";
import EnvEditor from "./editors/EnvEditor";
import CustomEditor from "./editors/CustomEditor";
import CustomModuleEditor from "./editors/CustomModuleEditor";
import DataMappingEditor from "./editors/DataMappingEditor";
const refs = [
'projectEditor',
'envEditor',
'dataMappingEditor',
'customModuleEditor',
];
export default {
name: "DetailsDialog",
components: {DataMappingEditor, CustomEditor, EnvEditor, ProjectEditor},
components: {DataMappingEditor, CustomModuleEditor, EnvEditor, ProjectEditor},
data() {
return {
visible: false,
......@@ -44,12 +54,24 @@
show() {
this.visible = true;
},
onSave() {
for (let ref of refs) {
this.$refs[ref].save();
}
this.visible = false;
this.modifyProjectDetails();
},
onClose() {
this.visible = false;
},
onOpen() {
for (let ref of refs) {
this.$refs[ref].edit();
}
},
...mapMutations([
'modifyProjectDetails',
]),
}
}
</script>
......
<template>
<el-dialog :title="$t('Mock Editor')" width="70%" :visible.sync="visible"
:close-on-click-modal="false"
:append-to-body="true"
:show-close="false"
fullscreen
custom-class="flex-dialog mock-editor-dialog"
>
<div class="mock-editor-view">
<label class="enabled-switch">
<el-switch :value="mockServeEnabled" @input="onMockServeEnabledChange"/>
{{$t('Enable mock serve')}}
</label>
<el-table class="mock-table" :data="mocks" height="100%" stripe size="mini">
<el-table-column type="expand">
<template slot-scope="props">
<codemirror ref="codeEditor"
v-model="props.row.data"
:options="cmOptions"/>
</template>
</el-table-column>
<el-table-column
prop="disabled"
width="40">
<template slot-scope="scope">
<enabled-setter :target="scope.row"/>
</template>
</el-table-column>
<el-table-column
prop="path"
:label="$t('Path')"
width="200">
<template slot-scope="scope">
<el-input v-model="scope.row.path" size="mini"/>
</template>
</el-table-column>
<el-table-column
prop="timeout"
:label="$t('Timeout')"
width="100">
<template slot-scope="scope">
<el-input-number style="width:100%;" v-model="scope.row.timeout" size="mini" controls-position="right"/>
</template>
</el-table-column>
<el-table-column
prop="successRatio"
:label="$t('SuccessRatio')"
width="150">
<template slot-scope="scope">
<el-slider v-model="scope.row.successRatio" size="mini" :max="1" :min="0" :step="0.1"/>
</template>
</el-table-column>
<el-table-column
prop="data"
:label="$t('Data')">
<template slot-scope="scope">
<span style="white-space: nowrap;overflow: hidden;">{{scope.row.data}}</span>
</template>
</el-table-column>
<el-table-column
width="50">
<template slot-scope="scope">
<el-button circle size="mini" type="danger" icon="el-icon-delete" plain
@click="toDeleteItem(scope.$index)"/>
</template>
</el-table-column>
</el-table>
</div>
<div slot="footer" class="dialog-footer">
<div>
<el-button size="mini" @click="onAdd">{{$t('Add')}}</el-button>
</div>
<div>
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
<el-button size="mini" @click="onSave" type="primary">{{$t('Save')}}</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import {mapState, mapMutations, mapActions} from 'vuex'
import EnabledSetter from "../components/EnabledSetter";
import {clonePureObj} from "../../../utils";
import {codemirror} from "vue-codemirror";
import 'codemirror/addon/lint/lint.js'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/addon/lint/json-lint.js'
export default {
name: "MockEditorDialog",
components: {EnabledSetter, codemirror},
data() {
return {
visible: false,
mocks: [],
cmOptions: {
tabSize: 2,
styleActiveLine: true,
theme: 'default',
line: true,
matchBrackets: true,
autoCloseBrackets: true,
lineNumbers: true,
mode: "application/json",
gutters: ["CodeMirror-lint-markers"],
lint: true
}
}
},
mounted() {
},
computed: {
...mapState({
mocksOrigin: state => state.project.data.mock,
mockServeEnabled: state => state.project.mockServeEnabled,
}),
},
watch: {
mockServeEnabled(v) {
}
},
methods: {
show() {
this.mocks = clonePureObj(this.mocksOrigin);
this.visible = true;
},
onSave() {
this.modifyMocks(this.mocks);
this.visible = false;
},
onClose() {
this.visible = false;
},
onAdd() {
this.mocks.push({
path: '',
data: '',
timeout: 0,
successRatio: 1,
});
},
onDelete(index) {
this.mocks.splice(index, 1);
},
toDeleteItem(index) {
this.$confirm(this.$t('Are you sure to delete this item'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this.onDelete(index);
}).catch((e) => {
});
},
onMockServeEnabledChange(v){
this.setMockServeEnabled(v);
},
...mapMutations([
'modifyMocks',
]),
...mapActions([
'setMockServeEnabled',
]),
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -7,6 +7,11 @@
>
<div>
<p>{{$t('Pack project successfully')}}</p>
<el-collapse>
<el-collapse-item :title="$t('Check template code')" name="1">
<el-input v-if="packResult" type="textarea" readonly v-model="packResult.tpl" :rows="10"/>
</el-collapse-item>
</el-collapse>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
......@@ -18,6 +23,7 @@
<script>
import copy from 'copy-to-clipboard'
import {openPreview} from "../../../utils";
export default {
name: "PackResultDialog",
......@@ -40,9 +46,7 @@
openInNewTab() {
this.visible = false;
setTimeout(()=>{
window.open(this.packResult.tplUrl, 'blank');
}, 500);
openPreview(this.packResult);
},
copy() {
this.visible = false;
......
<template>
<el-dialog :title="$t('Project details')" width="70%" :visible.sync="visible" @opened="onOpen"
:close-on-click-modal="false"
:append-to-body="true"
custom-class="select-asset-dialog"
>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<asset-list @click-item="onClickItem"/>
</el-scrollbar>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
</div>
</el-dialog>
</template>
<script>
import AssetList from "../Assets/AssetList";
export default {
name: "SelectAssetDialog",
components: {AssetList},
data() {
return {
visible: false,
activeName: 'project',
}
},
methods: {
show() {
this.$emit('cancel');
this.visible = true;
},
onClose() {
this.$emit('cancel');
this.visible = false;
},
onOpen() {
},
onClickItem(asset) {
this.$emit('select', asset);
this.visible = false;
},
}
}
</script>
<style lang="scss">
.select-asset-dialog {
.scrollbar {
height: 50vh;
}
}
</style>
\ No newline at end of file
<template>
<el-dialog :title="$t('Custom module asset mapping')" width="70%" :visible.sync="visible" @opened="onOpen"
:close-on-click-modal="false"
:append-to-body="true"
:show-close="false"
fullscreen
custom-class="flex-dialog asset-mapping-editor-dialog"
>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<div class="mapping-list">
<div class="list" v-if="customMeta">
<div class="item" v-for="(item, index) in customMeta.assets" :key="index">
<span class="name">{{item.name}}</span>
<div class="diff">
<div class="side left">
<span class="extname">
{{item.ext}}
</span>
<div class="thumbnail">
<img class="alpha-image-background" v-if="item.ext === '.png'" :src="item.url">
</div>
<el-input size="mini" v-model="item.url" readonly/>
</div>
<el-icon class="el-icon-connection"/>
<div class="side right">
<!--<span class="extname">
{{item.ext}}
</span>-->
<div class="thumbnail">
<img class="alpha-image-background" v-if="item.replace && showThumbnail(item.ext)"
:src="resolveImg(item)">
</div>
<el-input size="mini" v-model="item.replace" clearable>
<el-button slot="append" icon="el-icon-brush" @click="onClickReplace(item)"/>
</el-input>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
<el-button size="mini" @click="onSave" type="primary">{{$t('Save')}}</el-button>
</div>
<select-asset-dialog ref="selectAssetDialog" @select="onSelectReplace"/>
</el-dialog>
</template>
<script>
import {mapMutations, mapState, mapGetters} from 'vuex';
import {fileTypeIcon} from "../../../../config";
import SelectAssetDialog from "../SelectAssetDialog";
import {clonePureObj} from "../../../../utils";
const linkScheme = 'link://';
export default {
name: "AssetMappingEditorDialog",
components: {SelectAssetDialog},
data() {
return {
visible: false,
mid: '',
customMeta: null,
}
},
computed: {
...mapState({
customs(state) {
console.log(state.env.customs);
return state.env.customs;
},
}),
...mapGetters([
'assets',
])
},
methods: {
showThumbnail(ext) {
return fileTypeIcon[ext] === 't';
},
resolveImg(item) {
let url;
const replace = item.replace;
if (replace.startsWith(linkScheme)) {
let uuid = replace.replace(linkScheme, '');
let asset = this.assets.find(asset => asset.uuid === uuid);
if (asset) {
url = asset.url;
}
} else {
url = replace;
}
return url;
},
onClickReplace(item) {
this.toReplaceItem = item;
this.$refs.selectAssetDialog.show();
},
onSelectReplace(asset) {
this.$delete(this.toReplaceItem, 'replace');
this.$set(this.toReplaceItem, 'replace', linkScheme + asset.uuid);
},
show(data) {
this.mid = data.id;
this.customMeta = clonePureObj(this.customs.find(item => item.id === this.mid));
for (let uuid in data.assetMapping) {
let asset = this.customMeta.assets.find(item => item.uuid === uuid);
if (asset) {
this.$set(asset, 'replace', data.assetMapping[uuid]);
}
}
this.visible = true;
},
onSave() {
let assetMapping = {};
for (let asset of this.customMeta.assets) {
if (asset.replace) {
assetMapping[asset.uuid] = asset.replace;
}
}
this.$emit('save', this.mid, assetMapping);
this.visible = false;
},
onClose() {
this.visible = false;
},
onOpen() {
},
...mapMutations([]),
}
}
</script>
<style lang="scss">
.asset-mapping-editor-dialog {
.view {
padding-right: 10px;
}
.scrollbar {
height: 100%;
.item {
display: flex;
flex-direction: column;
align-items: stretch;
.name{
text-align: center;
}
.diff{
position: relative;
border: 1px solid lightgray;
border-radius: 5px;
min-height: 70px;
align-items: stretch;
display: flex;
.el-icon-connection {
margin-bottom: 6px;
align-self: flex-end;
}
.side {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
.extname {
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
color: white;
padding: 2px;
}
.thumbnail {
margin: 5px;
font-size: 0;
width: 110px;
height: 110px;
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100px;
max-height: 100px;
}
}
&.left {
.extname {
left: 0;
border-top-left-radius: 4px;
border-bottom-right-radius: 4px;
}
}
&.right {
.extname {
right: 0;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
}
}
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<el-checkbox-group :value="customs" class="custom-module-list" @input="onChange">
<el-checkbox class="item" v-for="customMeta in customMetas" :key="customMeta.id" :label="customMeta.id">
{{customMeta.alias || customMeta.name}}
</el-checkbox>
</el-checkbox-group>
</el-scrollbar>
</template>
<script>
import {mapState, mapMutations} from 'vuex'
export default {
name: "CustomEditor",
data() {
return {
visible: false,
}
},
computed: {
...mapState({
customMetas: state => state.env.customs,
customs: state => state.project.data.customs,
}),
},
methods: {
...mapMutations([
'modifyCustoms',
]),
onChange(v){
this.modifyCustoms(v);
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<el-table v-if="editData" :data="customMetas" stripe height="100%">
<el-table-column
width="40"
>
<template slot-scope="scope">
<el-checkbox v-model="scope.row.selected"/>
</template>
</el-table-column>
<el-table-column
prop="id"
label="ID"
width="200">
</el-table-column>
<el-table-column
prop="name"
:label="$t('Name')"
width="200">
</el-table-column>
<el-table-column
prop="assets"
:label="$t('Assets config')"
width="100">
<template slot-scope="scope">
<el-button size="mini" icon="el-icon-edit" circle type="primary" plain @click="editAssetMapping(scope.row)"/>
</template>
</el-table-column>
<!--<el-table-column
label="Version"
width="100"
>
<template slot-scope="scope">
<el-select size="mini"/>
</template>
</el-table-column>-->
<el-table-column>
</el-table-column>
<asset-mapping-editor-dialog ref="assetMappingEditorDialog" @save="onSaveAssetMapping"/>
</el-table>
</template>
<script>
import {mapState, mapMutations} from 'vuex'
import {clonePureObj} from "../../../../utils";
import AssetMappingEditorDialog from "./AssetMappingEditorDialog";
export default {
name: "CustomModuleEditor",
components: {AssetMappingEditorDialog},
data() {
return {
visible: false,
customMetas: [],
editData: null,
}
},
computed: {
...mapState({
customs: state => state.project.data.customs,
}),
},
methods: {
edit() {
this.editData = clonePureObj(this.customs);
this.customMetas.splice(0);
for (let meta of this.$store.state.env.customs) {
let data = this.editData.find(item => item.id === meta.id);
this.customMetas.push({
id: meta.id,
name: meta.name,
assetMapping: data && (data.assetMapping || {}),
selected: !!data,
})
}
},
save() {
const editData = this.editData;
editData.splice(0);
for (let meta of this.customMetas) {
if (meta.selected) {
editData.push({
id: meta.id,
assetMapping: meta.assetMapping || {},
});
}
}
this.modifyCustoms(editData);
},
editAssetMapping(data) {
this.$refs.assetMappingEditorDialog.show(data);
},
onSaveAssetMapping(mid, assetMapping) {
this.customMetas.find(item=>item.id === mid).assetMapping = assetMapping;
},
...mapMutations([
'modifyCustoms',
]),
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<el-scrollbar v-if="editData" class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<div class="mapping-list">
<el-button class="add-button" icon="el-icon-plus" size="mini" circle @click="onAdd"/>
<div class="list">
<div class="item" v-for="(link, index) in mapping">
<el-input size="mini" style="width: 30%" v-model="link.name" @change="onChange"/>
<div class="item" v-for="(link, index) in editData">
<el-input size="mini" style="width: 30%" v-model="link.name"/>
<el-icon class="el-icon-connection"/>
<el-input size="mini" v-model="link.path" @change="onChange"/>
<el-input size="mini" v-model="link.path"/>
<el-button class="delete-button" icon="el-icon-minus" circle size="mini" plain type="danger" @click="toDeleteItem(link, index)"/>
</div>
</div>
......@@ -16,12 +16,14 @@
<script>
import {mapState, mapMutations} from 'vuex'
import {clonePureObj} from "../../../../utils";
export default {
name: "DataMappingEditor",
data() {
return {
visible: false,
editData: null,
}
},
computed: {
......@@ -30,11 +32,17 @@
}),
},
methods: {
onAdd() {
this.addDataMapping();
edit() {
this.editData = clonePureObj(this.mapping);
},
save() {
this.modifyDataMapping(this.editData);
},
onChange() {
this.modifyDataMapping();
onAdd() {
this.editData.push({
name: '',
path: '',
});
},
toDeleteItem(link, index) {
this.$confirm(this.$t('Are you sure to delete this item'), this.$t('Alert'), {
......@@ -42,13 +50,11 @@
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this.deleteDataMapping(index);
this.editData.splice(index, 1);
}).catch((e) => {
});
},
...mapMutations([
'addDataMapping',
'deleteDataMapping',
'modifyDataMapping',
]),
}
......
<template>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<el-scrollbar v-if="editData" class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<div class="mapping-list">
<el-button class="add-button" icon="el-icon-plus" size="mini" circle @click="onAdd"/>
<div class="list">
<div class="item" v-for="(link, index) in env">
<el-input size="mini" style="width: 30%" v-model="link.name" @change="onChange"/>
<div class="item" v-for="(link, index) in editData">
<el-input size="mini" style="width: 30%" v-model="link.name"/>
<el-icon class="el-icon-connection"/>
<el-input size="mini" v-model="link.value" @change="onChange"/>
<el-button class="delete-button" icon="el-icon-minus" circle size="mini" plain type="danger" @click="toDeleteItem(link, index)"/>
<el-input size="mini" v-model="link.value"/>
<el-button class="delete-button" icon="el-icon-minus" circle size="mini" plain type="danger"
@click="toDeleteItem(link, index)"/>
</div>
</div>
</div>
......@@ -16,12 +17,14 @@
<script>
import {mapState, mapMutations} from 'vuex'
import {clonePureObj} from "../../../../utils";
export default {
name: "EnvEditor",
data() {
return {
visible: false,
editData: null,
}
},
computed: {
......@@ -30,11 +33,17 @@
}),
},
methods: {
onAdd() {
this.addEnvMapping();
edit() {
this.editData = clonePureObj(this.env);
},
save() {
this.modifyEnv(this.editData);
},
onChange() {
this.modifyEnvMapping();
onAdd() {
this.editData.push({
name: '',
value: '',
});
},
toDeleteItem(link, index) {
this.$confirm(this.$t('Are you sure to delete this item'), this.$t('Alert'), {
......@@ -42,14 +51,12 @@
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this.deleteEnvMapping(index);
this.editData.splice(index, 1);
}).catch((e) => {
});
},
...mapMutations([
'addEnvMapping',
'deleteEnvMapping',
'modifyEnvMapping',
'modifyEnv',
]),
}
}
......
<template>
<div>
<el-scrollbar class="scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<el-form @submit.native.prevent ref="form" :model="options" size="mini" label-position="right"
<div class="project-editor">
<el-scrollbar v-if="editData" class="project-scrollbar" wrap-class="wrap-x-hidden" view-class="view">
<el-form @submit.native.prevent ref="form" :model="editData" size="mini" label-position="right"
label-width="150px">
<el-form-item prop="pageTitle" :label="$t('Page title')">
<el-input v-model="options.pageTitle"/>
<el-input v-model="editData.pageTitle"/>
</el-form-item>
<el-form-item prop="entrySceneView" :label="$t('Entry scene view')">
<el-select v-model="options.entrySceneView">
<el-select v-model="editData.entrySceneView">
<el-option v-for="(view, index) in project.data.views"
:key="index"
:value="view.name"
......@@ -16,19 +16,19 @@
</el-select>
</el-form-item>
<el-form-item prop="containerId" :label="$t('Container ID')">
<el-input v-model="options.containerId"/>
<el-input v-model="editData.containerId"/>
</el-form-item>
<el-form-item prop="designWidth" :label="$t('Design width')">
<el-input-number v-model="options.designWidth" controls-position="right"/>
<el-input-number v-model="editData.designWidth" controls-position="right"/>
</el-form-item>
<el-form-item prop="designHeight" :label="$t('Design height')">
<el-input-number v-model="options.designHeight" controls-position="right"/>
<el-input-number v-model="editData.designHeight" controls-position="right"/>
</el-form-item>
<el-form-item prop="frameRate" :label="$t('Frame Rate')">
<el-input-number v-model="options.frameRate" :max="60" :min="0" controls-position="right"/>
<el-input-number v-model="editData.frameRate" :max="60" :min="0" controls-position="right"/>
</el-form-item>
<el-form-item prop="scaleMode" :label="$t('Scale Mode')">
<el-select v-model="options.scaleMode">
<el-select v-model="editData.scaleMode">
<el-option v-for="(label, key) in scaleMode"
:key="key"
:value="key"
......@@ -37,7 +37,7 @@
</el-select>
</el-form-item>
<el-form-item prop="rendererType" :label="$t('Renderer Type')">
<el-select v-model="options.rendererType">
<el-select v-model="editData.rendererType">
<el-option v-for="(label, key) in rendererType"
:key="key"
:value="key"
......@@ -46,30 +46,55 @@
</el-select>
</el-form-item>
<el-form-item prop="tpl" :label="$t('Template')">
<el-input type="textarea" v-model="options.tpl" :rows="10"/>
<!-- <el-input type="textarea" v-model="options.tpl" :rows="10"/> -->
<codemirror ref="codeEditor"
v-model="editData.tpl"
:options="cmOptions"
@cursorActivity="onCodeChange"
>
</codemirror>
</el-form-item>
</el-form>
</el-scrollbar>
<div class="operate-bar">
<!--<div class="operate-bar">
<el-button size="mini" @click="onReset">{{$t('Reset')}}</el-button>
<el-button size="mini" type="primary" @click="onSave">{{$t('Save')}}</el-button>
</div>
</div>-->
</div>
</template>
<script>
import {mapState, mapGetters, mapMutations} from 'vuex';
import {codemirror} from "vue-codemirror";
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css'
import 'codemirror/addon/edit/closebrackets.js'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/show-hint.css'
import {clonePureObj} from "../../../../utils";
export default {
name: "ProjectEditor",
components: {},
components: {codemirror},
data() {
const scaleMode = this.$t('scaleMode');
const rendererType = this.$t('rendererType');
return {
visible: false,
editData: null,
scaleMode,
rendererType,
cmOptions: {
tabSize: 2,
mode: 'text/javascript',
styleActiveLine: true,
theme: 'default',
lineNumbers: true,
line: true,
matchBrackets: true,
autoCloseBrackets: true,
}
}
},
computed: {
......@@ -82,14 +107,24 @@
},
methods: {
...mapMutations([
'modifyProject',
'modifyOptions',
]),
onReset() {
edit() {
this.editData = clonePureObj(this.options);
},
save() {
this.modifyOptions(this.editData);
},
/*onReset() {
this.$refs.form.resetFields();
},
onSave() {
this.modifyProject();
},
},*/
onCodeChange(codemirror) {
//codemirror.showHint();
//console.log(code);
}
}
}
</script>
......
<template>
<div class="home">
<p class="editor-name" style="font-weight: bold">{{env.name}}<span
class="editor-version">{{env.version}}</span></p>
<div class="editor-name" style="font-weight: bold">{{env.name}}
<span class="editor-version">{{env.version}}</span>
</div>
<main>
<div class="project-list">
<el-table
:data="projects"
:data="projects.projects"
:empty-text="$t('No projects')"
height="100%"
>
......@@ -21,32 +22,60 @@
</el-table-column>
<el-table-column
fixed="right"
width="140">
width="180">
<template slot="header" slot-scope="scope">
<el-checkbox
v-model="onlyMine"
size="mini">{{$t('Only mine')}}
</el-checkbox>
</template>
<template slot-scope="scope">
<el-button
@click.native.prevent="selectProject(scope.row)"
type="primary" icon="el-icon-edit"
size="small" circle plain>
</el-button>
<el-button
@click.native.prevent="selectFromHistory(scope.row)"
type="primary" icon="el-icon-coin"
size="small" circle plain>
</el-button>
<el-button
@click.native.prevent="showDuplicateProjectDialog(scope.row)"
type="success" icon="el-icon-document-copy"
size="small" circle plain>
</el-button>
<!--<el-button
<el-button
@click.native.prevent="exportProject(scope.row)"
type="warning" icon="icon-download"
size="small" circle plain>
</el-button>
<el-button
v-if="showDeleteButton"
@click.native.prevent="onDeleteProject(scope.row)"
type="danger" icon="el-icon-delete"
size="small" circle plain>
</el-button>-->
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div>
<el-button @click="showCreateProjectDialog()">{{$t('Create')}}</el-button>
<div class="bottom-bar">
<div>
<el-button type="primary" @click="showCreateProjectDialog()">{{$t('Create')}}</el-button>
<el-button @click="importProject()">{{$t('Import')}}</el-button>
</div>
<el-pagination
@current-change="handleCurrentChange"
:current-page.sync="currentPage"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="projects.projectCount">
</el-pagination>
</div>
<create-project-dialog ref="createProjectDialog" @success="onCreateProject"/>
<duplicate-project-dialog ref="duplicateProjectDialog" @success="onDuplicateProject"/>
<project-history-dialog ref="projectHistoryDialog" @select-history-project="onSelectProject"/>
</main>
</div>
</template>
......@@ -54,32 +83,46 @@
<script>
import {mapState, mapActions} from 'vuex'
import CreateProjectDialog from "./Home/CreateProjectDialog";
import {playWaiting} from "../utils";
import {playWaiting, readTextFile, saveAs, selectFile} from "../utils";
import moment from "moment";
import DuplicateProjectDialog from "./Home/DuplicateProjectDialog";
import {PROJECT_PAGE_SIZE} from "../config";
import ProjectHistoryDialog from "./Home/ProjectHistoryDialog";
export default {
name: "Home",
components: {DuplicateProjectDialog, CreateProjectDialog},
components: {ProjectHistoryDialog, DuplicateProjectDialog, CreateProjectDialog},
data() {
return {
appVersion: 'v1.0.0',
currentPage: 1,
pageSize: PROJECT_PAGE_SIZE,
onlyMine: true,
}
},
mounted() {
playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {});
playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {
});
},
computed: {
showDeleteButton() {
return localStorage.getItem('---zeroing-editor-delete-button');
},
...mapState([
'projects',
'env',
]),
},
watch: {
onlyMine() {
this.handleCurrentChange(1);
}
},
methods: {
prepare(){
prepare() {
return Promise.all([
this.updateEnv(),
this.updateProjects(),
this.handleCurrentChange(1),
])
},
moment(time) {
......@@ -88,22 +131,42 @@
showCreateProjectDialog() {
this.$refs.createProjectDialog.show();
},
importProject() {
selectFile((files) => {
readTextFile(files[0])
.then(
txt => {
let data = JSON.parse(txt);
return playWaiting(this.createProject(data), this.$t('Importing project'));
}
)
});
},
async exportProject({id}) {
const project = await playWaiting(this.fetchProject(id), this.$t('Exporting project'));
saveAs(new Blob([JSON.stringify(project)]), project.name + '.json');
},
showDuplicateProjectDialog(project) {
this.$refs.duplicateProjectDialog.show(project);
},
selectProject(row, event) {
this.editProject(row.id);
},
selectFromHistory(row, event) {
this.$refs.projectHistoryDialog.show(row)
},
async onDeleteProject(project, event) {
this.$confirm(this.$t('This action will permanently delete project', {projectName: project.name}), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(
()=>{
playWaiting(this.deleteProject(project.id), this.$t('Deleting')).catch(e => {});
() => {
playWaiting(this.deleteProject(project.id), this.$t('Deleting')).catch(e => {
});
},
()=>{}
() => {
}
)
},
onCreateProject(projectID) {
......@@ -112,11 +175,26 @@
onDuplicateProject(projectID) {
this.editProject(projectID);
},
onSelectProject(project, dataUrl) {
this.$router.push({name: 'editor', params: {projectID: project.id, project, dataUrl}});
},
editProject(projectID) {
this.$router.push({name: 'editor', params: {projectID}});
},
async handleCurrentChange(page) {
const loading = this.$loading({
lock: true,
text: this.$t('In processing'),
});
this.currentPage = page;
await this.fetchProjects({currentPage: page, pageSize: this.pageSize, onlyMine: this.onlyMine});
loading.close();
},
...mapActions([
'updateProjects',
'fetchProjects',
'fetchProject',
'fetchHistory',
'createProject',
'deleteProject',
'updateEnv',
]),
......
<template>
<el-dialog :title="$t('Select from history')" width="70%" :visible.sync="visible" @open="onOpen"
:append-to-body="true">
<div class="project-history-table" v-if="project">
<el-table :data="historyProjects" stripe height="100%">
<el-table-column
label="时间"
width="200">
<template slot-scope="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ moment(scope.row.operator_time) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作人"
width="100"
prop="operator"
>
</el-table-column>
<el-table-column
label="版本备注"
prop="remark"
>
<template slot-scope="scope">
<el-popover
class="popover-remark"
placement="top"
width="200"
title="版本备注"
:open-delay="500"
trigger="hover"
:content="scope.row.remark">
<span slot="reference" class="short-remark">{{scope.row.remark}}</span>
</el-popover>
</template>
</el-table-column>
<el-table-column
width="60">
<template slot-scope="scope">
<el-button
@click.native.prevent="selectProject(scope.row)"
type="primary" icon="el-icon-edit"
size="small" circle plain>
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
@current-change="handleCurrentChange"
:current-page.sync="currentPage"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="historyProjectCount">
</el-pagination>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="visible=false">{{$t('Cancel')}}</el-button>
</div>
</el-dialog>
</template>
<script>
import {mapState, mapActions} from 'vuex'
import {HISTORY_PAGE_SIZE} from "../../config";
import moment from "moment";
export default {
name: "ProjectHistoryDialog",
data() {
return {
visible: false,
project: null,
currentPage: 1,
historyProjectCount: 0,
pageSize: HISTORY_PAGE_SIZE,
historyProjects: [],
}
},
computed: {
...mapState([
'env',
]),
},
watch: {},
methods: {
async show(project) {
this.project = project;
this.visible = true;
this.handleCurrentChange(1);
},
onOpen() {
},
moment(time) {
return moment(time).format('YYYY-MM-DD HH:mm:ss');
},
async handleCurrentChange(page) {
const loading = this.$loading({
lock: true,
text: this.$t('In processing'),
});
this.currentPage = page;
const {data, total} = await this.fetchHistory({
projectID: this.project.id,
currentPage: page,
pageSize: this.pageSize
});
this.historyProjects = data;
this.historyProjectCount = total;
loading.close();
},
selectProject(item){
this.$emit('select-history-project', this.project, 'http:' + item.url);
this.visible = false;
},
...mapActions([
'fetchHistory'
]),
}
}
</script>
<style lang="scss">
</style>
\ No newline at end of file
<template>
<div class="wrapper">
<iframe v-if="flag" ref="iframe" class="player-wrapper"></iframe>
</div>
</template>
<script>
import {applyMock} from "./Preview/mock-serve";
import db from "../utils/db-storage";
import {newScriptEl} from "../utils";
export default {
name: "Preview",
components: {},
data() {
return {
ts: '',
flag: false,
}
},
async mounted() {
this.codes = {};
this.urls = {};
if (!this.ts) {
this.ts = localStorage.getItem('preview-ts');
}
document.addEventListener("visibilitychange", this.onVisibilityChange);
await db.open();
this.reload();
},
destroyed() {
document.removeEventListener("visibilitychange", this.onVisibilityChange);
},
methods: {
reload() {
this.flag = false;
setTimeout(() => {
this.flag = true;
this.buildPage();
}, 300);
},
onVisibilityChange(e) {
if (!document.hidden) {
let ts = localStorage.getItem('preview-ts');
if (this.ts !== ts) {
//this.$refs.iframe.contentDocument.location.reload();
//document.location.reload();
this.ts = ts;
this.reload();
}
}
},
async buildPage() {
const {projectID} = this.$route.params;
const {data, codes} = await db.get('preview', projectID);
console.log(data);
let {options: {tpl, pageTitle, containerId}} = data;
//const dataName = projectID + "_data";
//const launchCode = `engine.launchWithWindowVariable("${dataName}");`;
const scripts = [];
for (let key in codes) {
let oldCode = this.codes[key];
let code = codes[key];
let url;
if (oldCode !== code) {
url = this.urls[key] = URL.createObjectURL(new Blob([code]));
} else {
url = this.urls[key];
}
scripts.push(newScriptEl(url));
}
this.codes = codes;
const dataUrl = URL.createObjectURL(new Blob([JSON.stringify(data)]));
document.title = pageTitle;
tpl = tpl
.replace('$PAGE_TITLE$', pageTitle)
.replace('$CONTAINER_ID$', containerId)
.replace('$SCRIPTS$', scripts.join('\n'))
.replace('//yun.duiba.com.cn/aurora/$VERSION$-data.json', dataUrl);
const doc = this.$refs.iframe.contentDocument;
const win = this.$refs.iframe.contentWindow;
doc.write(tpl);
applyMock(projectID, win);
},
}
}
</script>
<style scoped>
.wrapper {
width: 100%;
height: 100%;
}
.player-wrapper {
border: 0;
width: 100%;
height: 100%;
}
.mock-button {
position: absolute;
right: 10px;
bottom: 10px;
}
</style>
\ No newline at end of file
/**
* Created by rockyl on 2019-12-18.
*/
import db from "../../utils/db-storage";
import {getMockServeEnabled} from "../../utils";
db.open();
const logStyle = "color: rgb(63, 172, 203)";
export function applyMock(projectID, win) {
win.mock = function ({url, method, params = {}, type}, callback, resolve, reject) {
const mockServeEnabled = getMockServeEnabled(projectID);
if (!mockServeEnabled) {
callback();
return;
}
if(url.includes('projectx')){
url = url.replace(/projectx\/\w+\//, '');
}
db.get('mock', url)
.then(
rule => {
if (rule && !rule.disabled) {
const {timeout = 0, successRatio = 1, data} = rule;
setTimeout(function () {
if (Math.random() < successRatio) {
try {
console.log(`%c[mock] ${url} ${method} ${JSON.stringify(params)}`, logStyle, JSON.parse(data));
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
} else {
reject(url + ' 404 (Not Found)')
}
}, timeout);
callback(rule);
} else {
callback();
}
},
e => {
callback();
}
);
};
}
\ No newline at end of file
/**
* Created by rockyl on 2019-12-20.
*/
import {clonePureObj} from "../../utils";
import {divideCode} from "zeroing-code-divider";
import db from "../../utils/db-storage";
const storeName = 'preview';
function getPack(packs) {
return function (ids) {
return packs.filter(pack => ids.includes(pack.id))
}
}
export async function preprocess(project, data, processes, scripts, customs) {
let newData = clonePureObj(data);
const codes = await divideCode(newData, {
debug: true,
getProcesses: getPack(processes),
getScripts: getPack(scripts),
getCustoms: getPack(customs),
});
db.set(storeName, {
id: project.id,
data: newData,
codes,
});
localStorage.setItem('preview-ts', Date.now().toString());
}
const serverHost = 'http://10.10.95.74:7777';
const serverHost = 'http://10.10.95.74:7777'
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '//yun.duiba.com.cn/editor/zeroing/v1/' : '',
......@@ -10,7 +12,7 @@ module.exports = {
enableInSFC: true
}
},
devServer: {
/*devServer: {
host: '0.0.0.0',
port: '8080',
proxy: {
......@@ -21,8 +23,13 @@ module.exports = {
target: serverHost,
},
}
},
},*/
configureWebpack: {
plugins: [
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'json'],
publicPath: "/monaco"
})
]
}
};
}
......@@ -1292,6 +1292,11 @@ address@>=0.0.1, address@^1.0.0, address@^1.0.3:
resolved "https://registry.npm.taobao.org/address/download/address-1.1.2.tgz?cache=0&sync_timestamp=1566806470420&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faddress%2Fdownload%2Faddress-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha1-vxEWycdYxRt6kz0pa3LCIe2UKLY=
after@0.8.2:
version "0.8.2"
resolved "https://registry.npm.taobao.org/after/download/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.npm.taobao.org/agent-base/download/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
......@@ -1535,6 +1540,11 @@ array-unique@^0.3.2:
resolved "https://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.npm.taobao.org/arraybuffer.slice/download/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
integrity sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=
asap@~2.0.3:
version "2.0.6"
resolved "https://registry.npm.taobao.org/asap/download/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
......@@ -1689,11 +1699,21 @@ babel-runtime@6.x:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.npm.taobao.org/backo2/download/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.npm.taobao.org/base64-arraybuffer/download/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.npm.taobao.org/base64-js/download/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
......@@ -1724,6 +1744,13 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
better-assert@~1.0.0:
version "1.0.2"
resolved "https://registry.npm.taobao.org/better-assert/download/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
dependencies:
callsite "1.0.0"
bfj@^6.1.1:
version "6.1.2"
resolved "https://registry.npm.taobao.org/bfj/download/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
......@@ -1761,6 +1788,11 @@ bl@^3.0.0:
dependencies:
readable-stream "^3.0.1"
blob@0.0.5:
version "0.0.5"
resolved "https://registry.npm.taobao.org/blob/download/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
integrity sha1-1oDu7yX4zZGtUz9bAe7UjmTK9oM=
block-stream@*:
version "0.0.9"
resolved "https://registry.npm.taobao.org/block-stream/download/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
......@@ -2057,6 +2089,11 @@ caller-path@^2.0.0:
dependencies:
caller-callsite "^2.0.0"
callsite@1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/callsite/download/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
callsites@^2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/callsites/download/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
......@@ -2365,6 +2402,11 @@ code-point-at@^1.0.0:
resolved "https://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codemirror@^5.41.0, codemirror@^5.50.0:
version "5.50.0"
resolved "https://registry.npm.taobao.org/codemirror/download/codemirror-5.50.0.tgz#aeacd18f225735b17cbab98908edace87fedcdab"
integrity sha1-rqzRjyJXNbF8urmJCO2s6H/tzas=
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
......@@ -2443,11 +2485,26 @@ commondir@^1.0.1:
resolved "https://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/component-bind/download/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
component-emitter@1.2.1:
version "1.2.1"
resolved "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=
component-inherit@0.0.3:
version "0.0.3"
resolved "https://registry.npm.taobao.org/component-inherit/download/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
compressible@~2.0.16:
version "2.0.17"
resolved "https://registry.npm.taobao.org/compressible/download/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1"
......@@ -2936,14 +2993,14 @@ debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@3.1.0:
debug@3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.1.1:
debug@4, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
version "4.1.1"
resolved "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=
......@@ -3117,6 +3174,11 @@ detect-node@^2.0.4:
resolved "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha1-AU7o+PZpxcWAI9pkuBecCDooxGw=
diff-match-patch@^1.0.0:
version "1.0.4"
resolved "https://registry.npm.taobao.org/diff-match-patch/download/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha1-asS1UjdGN2HE2vDcYD64aRJHRLE=
diff@^4.0.1:
version "4.0.1"
resolved "https://registry.npm.taobao.org/diff/download/diff-4.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdiff%2Fdownload%2Fdiff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
......@@ -3383,6 +3445,34 @@ end-or-error@^1.0.1:
resolved "https://registry.npm.taobao.org/end-or-error/download/end-or-error-1.0.1.tgz#dc7a6210fe78d372fee24a8b4899dbd155414dcb"
integrity sha1-3HpiEP5403L+4kqLSJnb0VVBTcs=
engine.io-client@~3.4.0:
version "3.4.0"
resolved "https://registry.npm.taobao.org/engine.io-client/download/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
integrity sha1-gqZCtChiqbP3oYj0F3ay3qtkNwA=
dependencies:
component-emitter "1.2.1"
component-inherit "0.0.3"
debug "~4.1.0"
engine.io-parser "~2.2.0"
has-cors "1.1.0"
indexof "0.0.1"
parseqs "0.0.5"
parseuri "0.0.5"
ws "~6.1.0"
xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
engine.io-parser@~2.2.0:
version "2.2.0"
resolved "https://registry.npm.taobao.org/engine.io-parser/download/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
integrity sha1-MSxIlPV9UqArQgho2ntcHISvgO0=
dependencies:
after "0.8.2"
arraybuffer.slice "~0.0.7"
base64-arraybuffer "0.1.5"
blob "0.0.5"
has-binary2 "~1.0.2"
enhanced-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.npm.taobao.org/enhanced-resolve/download/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
......@@ -4260,6 +4350,18 @@ has-ansi@^2.0.0:
dependencies:
ansi-regex "^2.0.0"
has-binary2@~1.0.2:
version "1.0.3"
resolved "https://registry.npm.taobao.org/has-binary2/download/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
integrity sha1-d3asYn8+p3JQz8My2rfd9eT10R0=
dependencies:
isarray "2.0.1"
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.npm.taobao.org/has-cors/download/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
......@@ -4647,11 +4749,21 @@ indent-string@^2.1.0:
dependencies:
repeating "^2.0.0"
indexdbwrapper@^1.0.4:
version "1.0.4"
resolved "https://registry.npm.taobao.org/indexdbwrapper/download/indexdbwrapper-1.0.4.tgz#a2dce8f7e67d89331e87726636fa67fb472f0b3e"
integrity sha1-otzo9+Z9iTMeh3JmNvpn+0cvCz4=
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/indexes-of/download/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.npm.taobao.org/indexof/download/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
infer-owner@^1.0.3:
version "1.0.4"
resolved "https://registry.npm.taobao.org/infer-owner/download/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
......@@ -5108,6 +5220,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@2.0.1:
version "2.0.1"
resolved "https://registry.npm.taobao.org/isarray/download/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
......@@ -5852,6 +5969,18 @@ moment@^2.24.0:
resolved "https://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s=
monaco-editor-webpack-plugin@^1.8.1:
version "1.8.1"
resolved "https://registry.npm.taobao.org/monaco-editor-webpack-plugin/download/monaco-editor-webpack-plugin-1.8.1.tgz#5701bfce16c14c51503a607726d97fd92784af26"
integrity sha1-VwG/zhbBTFFQOmB3Jtl/2SeEryY=
dependencies:
loader-utils "^1.2.3"
monaco-editor@^0.19.0:
version "0.19.0"
resolved "https://registry.npm.taobao.org/monaco-editor/download/monaco-editor-0.19.0.tgz#c6e774210f3b292ff739e96f45a1cc78bf5ac2e7"
integrity sha1-xud0IQ87KS/3OelvRaHMeL9awuc=
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
......@@ -6217,6 +6346,11 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
resolved "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-component@0.0.3:
version "0.0.3"
resolved "https://registry.npm.taobao.org/object-component/download/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
object-copy@^0.1.0:
version "0.1.0"
resolved "https://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
......@@ -6619,6 +6753,20 @@ parse5@^4.0.0:
resolved "https://registry.npm.taobao.org/parse5/download/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
integrity sha1-bXhlbj2o14tOwLkG98CO8d/j9gg=
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.npm.taobao.org/parseqs/download/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
dependencies:
better-assert "~1.0.0"
parseuri@0.0.5:
version "0.0.5"
resolved "https://registry.npm.taobao.org/parseuri/download/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
dependencies:
better-assert "~1.0.0"
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
......@@ -7894,6 +8042,11 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=
semver@^7.1.1:
version "7.1.1"
resolved "https://registry.npm.taobao.org/semver/download/semver-7.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667"
integrity sha1-KRBFmKGX1svkcz7uy+lo97Q6lmc=
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.npm.taobao.org/semver/download/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
......@@ -8322,6 +8475,35 @@ snyk@^1.231.0:
uuid "^3.3.2"
wrap-ansi "^5.1.0"
socket.io-client@^2.3.0:
version "2.3.0"
resolved "https://registry.npm.taobao.org/socket.io-client/download/socket.io-client-2.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsocket.io-client%2Fdownload%2Fsocket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
integrity sha1-FNW6LgC5vNFFrkQ6uWs/hsvMG7Q=
dependencies:
backo2 "1.0.2"
base64-arraybuffer "0.1.5"
component-bind "1.0.0"
component-emitter "1.2.1"
debug "~4.1.0"
engine.io-client "~3.4.0"
has-binary2 "~1.0.2"
has-cors "1.1.0"
indexof "0.0.1"
object-component "0.0.3"
parseqs "0.0.5"
parseuri "0.0.5"
socket.io-parser "~3.3.0"
to-array "0.1.4"
socket.io-parser@~3.3.0:
version "3.3.0"
resolved "https://registry.npm.taobao.org/socket.io-parser/download/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
integrity sha1-K1KpalCf3zFEC6QP7WCUx9TxJi8=
dependencies:
component-emitter "1.2.1"
debug "~3.1.0"
isarray "2.0.1"
sockjs-client@1.3.0:
version "1.3.0"
resolved "https://registry.npm.taobao.org/sockjs-client/download/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
......@@ -8947,6 +9129,11 @@ tmp@^0.1.0:
dependencies:
rimraf "^2.6.3"
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.npm.taobao.org/to-array/download/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
......@@ -9430,6 +9617,14 @@ vue-cli-plugin-i18n@^0.6.0:
vue-i18n "^8.0.0"
vue-i18n-extract "^0.4.13"
vue-codemirror@^4.0.6:
version "4.0.6"
resolved "https://registry.npm.taobao.org/vue-codemirror/download/vue-codemirror-4.0.6.tgz#b786bb80d8d762a93aab8e46f79a81006f0437c4"
integrity sha1-t4a7gNjXYqk6q45G95qBAG8EN8Q=
dependencies:
codemirror "^5.41.0"
diff-match-patch "^1.0.0"
vue-draggable-resizable@^2.0.1:
version "2.0.1"
resolved "https://registry.npm.taobao.org/vue-draggable-resizable/download/vue-draggable-resizable-2.0.1.tgz#fb98d0997b1cfa8e3ba90f723cf8b605012cd96d"
......@@ -9769,6 +9964,13 @@ ws@^6.0.0, ws@^6.2.1:
dependencies:
async-limiter "~1.0.0"
ws@~6.1.0:
version "6.1.4"
resolved "https://registry.npm.taobao.org/ws/download/ws-6.1.4.tgz?cache=0&sync_timestamp=1576314828024&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fws%2Fdownload%2Fws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
integrity sha1-W1yIAK+rkl6UzLKdFTyNAsF3bvk=
dependencies:
async-limiter "~1.0.0"
xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.npm.taobao.org/xdg-basedir/download/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
......@@ -9801,6 +10003,11 @@ xmlbuilder@~9.0.1:
resolved "https://registry.npm.taobao.org/xmlbuilder/download/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.npm.taobao.org/xmlhttprequest-ssl/download/xmlhttprequest-ssl-1.5.5.tgz?cache=0&sync_timestamp=1564594466893&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxmlhttprequest-ssl%2Fdownload%2Fxmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
xregexp@2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
......@@ -9919,3 +10126,12 @@ yargs@^7.0.0:
which-module "^1.0.0"
y18n "^3.2.1"
yargs-parser "^5.0.0"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.npm.taobao.org/yeast/download/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
"zeroing-code-divider@http://gitlab2.dui88.com/laoqifeng/zeroing-code-divider.git":
version "1.0.0"
resolved "http://gitlab2.dui88.com/laoqifeng/zeroing-code-divider.git#15af443267c46e32e84af786e3f01ef06e7da237"
......@@ -2,8 +2,11 @@
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jshint" level="application" />
</component>
</module>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment