Commit a945a697 authored by bianlongting's avatar bianlongting 💬

merge dev

parents b41682cd acdb6949
......@@ -9,6 +9,7 @@
"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",
......@@ -20,6 +21,7 @@
"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",
......
......@@ -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);
}
}
......@@ -71,9 +72,13 @@ export async function fetchApi(uri, {params, method = 'get', contentType = 'json
const jsonObj = await response.json();
//console.log(jsonObj);
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',
})
}
......@@ -45,6 +45,14 @@ export async function fetchOne(id) {
})
}
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},
......@@ -53,7 +61,8 @@ export async function fetchHistory(id, currentPage, pageSize) {
})
}
export async function saveOne(project) {
export async function saveOne(project, remark) {
project.remark = remark;
return await fetchApi('/api/project/update', {
params: project,
method: 'post',
......
......@@ -27,16 +27,17 @@ export function stop() {
}
export function editCode(code) {
if(socket && socket.connected){
socket.emit('edit-open', code);
}
}
function onConnect() {
function onConnect(t) {
codeSyncServeEnabled = true;
events.$emit('code-sync-start');
}
function onDisconnect() {
socket = null;
codeSyncServeEnabled = false;
events.$emit('code-sync-stop');
}
......
......@@ -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>
// 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>
// import { set, lensPath } from 'ramda';
import properties from '../../../utils/properties';
export default {
component: require('./index.vue'),
properties: {
...properties.node,
...properties.textinput
},
props: {
type: 'textinput'
},
title: 'textinput'
};
<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>
......@@ -23,7 +23,8 @@ export const SSO_VERIFY_PAGE_URL = '/sso/logout';
export const DOCK_POINT_OFFSET = 4;
export const PAGE_SIZE = 20;
export const PROJECT_PAGE_SIZE = 20;
export const HISTORY_PAGE_SIZE = 20;
//文件类型图标 t表示展示缩略图
export const fileTypeIcon = {
......
......@@ -9,6 +9,7 @@
"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",
......@@ -69,6 +70,7 @@
"Packing": "Packing",
"Type": "Type",
"Group": "Group",
"Select from history": "Select from history",
"Meta Search": "Meta Search",
"Access denied": "Access denied",
"Invalid router": "Invalid router",
......@@ -92,6 +94,7 @@
"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",
......@@ -100,6 +103,7 @@
"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",
......@@ -123,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",
......@@ -153,21 +158,39 @@
"Import multi": "Import multi",
"Import view success": "Import view success",
"menu": {
"save": "Save",
"details": "Details",
"preview-fast": "Preview",
"publish": "Publish",
"mock": "Mock",
"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",
"scrollList": "ScrollList",
"svga": "SVGA"
},
"panes": {
......@@ -193,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",
......
......@@ -9,6 +9,7 @@
"Save And Close": "保存并关闭",
"Reset": "重置",
"Copy": "复制",
"Assets config": "素材配置",
"Paste same level":"粘贴(同级)",
"Paste child":"粘贴(子级)",
"Exit": "退出",
......@@ -70,6 +71,7 @@
"Packing": "打包",
"Type": "类型",
"Group": "分组",
"Select from history": "打开历史版本",
"Meta Search": "过程元查找",
"Access denied": "无权限",
"Invalid router": "无效的页面",
......@@ -93,6 +95,7 @@
"Env editor": "环境编辑器",
"As inline": "作为内联",
"Project": "项目",
"Check template code": "查看模板代码",
"Missing behavior": "行为丢失",
"Add behavior": "添加行为",
"Edit trigger": "编辑触发",
......@@ -104,6 +107,7 @@
"Builtin event should add directly": "内部事件请直接添加",
"Env constant": "自定义常量",
"Custom module": "自定义模块",
"Custom module asset mapping": "自定义模块素材映射",
"Copy template to clipboard": "复制模板到粘贴板",
"Copied process to clipboard": "复制过程到粘贴板",
"Link to parent": "连接到父节点",
......@@ -127,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": "本地发现了未保存保本,请选择版本打开",
......@@ -157,23 +162,41 @@
"Import multi": "导入多",
"Import view success": "视图导入成功",
"menu": {
"save": "保存",
"details": "详情",
"preview-fast": "预览",
"publish": "发布",
"mock": "Mock",
"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": "矩形",
"circle": "圆形",
"textinput": "输入框",
"scrollView": "滚动视图",
"scrollList": "滚动列表",
"svga": "SVGA"
},
"panes": {
......@@ -199,11 +222,16 @@
"dynamic": "动态",
"map": "字典"
},
"nodeFilterPresets": {
"trigger": "行为触发",
"type": "节点类型"
},
"events": {
"init": "初始化",
"awake": "激活",
"sleep": "入眠",
"data-center": "数据中心",
"update-data": "更新数据",
"click": "触摸点击",
"touchstart": "触摸按下",
"touchend": "触摸弹起",
......
......@@ -29,6 +29,7 @@ export default new Vuex.Store({
plugins: [
SaveToLocalPlugin({
mutationTypes: [
'makeDirty',
'modifyProject',
'addNode',
'deleteNode',
......
......@@ -91,6 +91,7 @@ export const projectStore = {
processes: [],
customs: [],
mock: [],
dependencies: {},
},
mockServeEnabled: false,
activeComponent: {},
......@@ -100,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;
......@@ -114,7 +119,7 @@ export const projectStore = {
const localData = state.data;
if (data) {
const {views, assets, dataMapping, processes, options, customs, mock} = 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 || []);
......@@ -123,6 +128,7 @@ export const projectStore = {
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', []);
......@@ -131,21 +137,23 @@ export const projectStore = {
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){
modifyOptions(state, value) {
state.data.options = value;
},
modifyEnv(state, value){
modifyEnv(state, value) {
state.data.options.env = value;
},
modifyDataMapping(state, value){
modifyDataMapping(state, value) {
state.data.dataMapping = value;
},
modifyCustoms(state, value) {
console.log(value);
state.data.customs = value;
},
modifyProjectDetails(state) {
......@@ -429,7 +437,7 @@ export const projectStore = {
updateMock(mocks);
},
setMockServeEnabled(state, enabled){
setMockServeEnabled(state, enabled) {
state.mockServeEnabled = enabled;
}
},
......@@ -541,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
......@@ -768,7 +796,6 @@ async function packAssets(assets) {
delete asset.frames;
}
}
console.log(newAssets);
return newAssets;
}
......
......@@ -39,8 +39,8 @@ export const projectsStore = {
async fetchProject({commit}, projectId) {
return await projectApi.fetchOne(projectId);
},
async fetchHistory({commit}, {projectId, currentPage, pageSize}) {
return await projectApi.fetchHistory(projectId, currentPage, pageSize);
async fetchHistory({commit}, {projectID, currentPage, pageSize}) {
return await projectApi.fetchHistory(projectID, currentPage, pageSize);
},
async createProject({commit}, data) {
const project = await projectApi.createOne(data);
......
......@@ -54,7 +54,18 @@
.assets-scrollbar {
flex: 1;
.file-list {
}
}
.scrollbar-view{
padding-bottom: 5px;
}
}
}
.file-list {
display: flex;
flex: 1;
flex-direction: row;
......@@ -159,15 +170,6 @@
text-align: center;
}
}
}
}
}
.scrollbar-view{
padding-bottom: 5px;
}
}
}
.assets-show {
......
......@@ -46,8 +46,31 @@
}
.bottom-bar{
.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;
}
}
......@@ -9,6 +9,10 @@
@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;
......@@ -72,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 _ 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: 'textinput'
},
{
label: '滚动视图',
value: 'scrollView'
},
{
label: 'SVGA',
value: 'svga'
},
];
// 属性的计算方法
const propsComputeRules = {
x: 'add',
......@@ -115,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) {
......@@ -349,21 +314,28 @@ export const styles = {
}
}
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 = {
..._nodeProps,
..._typeProps
};
let result = Object.assign({}, ...inherits);
cmpPropsCache[type] = result;
delete result.base;
delete result.groupName;
}
return result
return cmpProps;
}
}
......@@ -2,11 +2,12 @@
* Created by rockyl on 2019-12-18.
*/
const version = 3;
const version = 4;
const storeConfigs = [
{name: 'project', key: 'id'},
{name: 'mock', key: 'path'},
{name: 'preview', key: 'id'},
{name: 'packages', key: 'id'},
];
export default {
......
// 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
......@@ -128,6 +128,7 @@ export default {
},
label: {
base: 'node',
groupName: '文本',
text: {
title: '文本内容',
......@@ -189,20 +190,45 @@ export default {
title: '纵向对齐',
type: 'select',
options: [
{ label: '靠上', value: 'top' },
{ label: '靠上', value: 'up' },
{ label: '居中', value: 'middle' },
{ label: '靠下', value: 'bottom' }
{ label: '靠下', value: 'down' }
],
value: 'top'
},
},
textinput: {
groupName: '输入框',
bitmapText: {
base: 'node',
groupName: '图片文本',
text: {
title: '文本内容',
type: 'textArea',
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',
......@@ -217,20 +243,6 @@ export default {
type: 'input',
value: ''
},
fillColor: {
title: '颜色',
type: 'colorPicker',
value: '#000'
},
size: {
title: '字体大小',
type: 'inputNumber',
/*type: 'swSelect',
props: {
optionType: 'fontSize'
},*/
value: 12
},
maxLength: {
title: '最大长度',
type: 'inputNumber',
......@@ -248,6 +260,7 @@ export default {
},
},
image: {
base: 'node',
groupName: '图片',
source: {
title: '来源',
......@@ -274,6 +287,7 @@ export default {
}
},
svga: {
base: 'node',
groupName: 'SVGA',
source: {
title: '来源',
......@@ -297,8 +311,8 @@ export default {
value: false
},
},
rect: {
groupName: '矩形',
shape: {
base: 'node',
fillColor: {
title: '填充色',
type: 'colorPicker',
......@@ -316,7 +330,11 @@ export default {
props: {
min: 0
}
}
},
rect: {
base: 'shape',
groupName: '矩形',
borderRadius: {
title: '圆角',
type: 'inputNumber',
......@@ -327,27 +345,11 @@ 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: '滚动视图',
direction: {
title: '滚动方向',
......@@ -366,11 +368,6 @@ export default {
},
value: true,
},
/*maxDistance: {
title: '最大距离',
type: 'inputNumber',
value: 1040,
},*/
maxSpeed: {
title: '最大速度',
type: 'inputNumber',
......@@ -382,6 +379,41 @@ export default {
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: {
......
......@@ -116,7 +116,7 @@
}
},
async loadProject() {
const {projectID} = this.$route.params;
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,
......@@ -124,18 +124,18 @@
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) {
......@@ -145,10 +145,11 @@
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 => {
......@@ -170,11 +171,27 @@
localStorage.panesConfig = JSON.stringify(this.panesConfig);
},
async saveProject(closeLoading) {
await playWaiting(this.saveToRemote(), this.$t('Saving'), 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) {
......@@ -213,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) {
}
......@@ -232,14 +249,18 @@
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 {
if (!debug) {
await this.saveProject(false);
}
const packResult = await this.packProject(debug);
this.$message({
message: this.$t('Pack project successfully'),
......@@ -291,6 +312,7 @@
'localVersionExist',
'loadFromLocal',
'loadFromRemote',
'loadFromDataUrl',
"saveToLocal",
"saveToRemote",
'updateEnv',
......
......@@ -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" @dragover.prevent @drop="onDropFile">
<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>
......@@ -43,10 +41,11 @@
import AssetsShow from "./Assets/AssetsShow";
import SplitPanes from 'splitpanes'
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'],
......@@ -85,8 +84,8 @@
}
this.uploadFiles(allFiles);
},
showFileDetails(file) {
this.$refs.assetsShow.show(file);
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 @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" :src="thumbnailUrl"
<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 class="operate-bar">
<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"/>
......@@ -26,7 +28,10 @@
export default {
name: "FileItem",
props: ['data'],
props: {
data: {type: Object},
editable: {type: Boolean, default: false},
},
data() {
return {}
},
......
......@@ -34,7 +34,7 @@
<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"></input>
<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"
......@@ -115,6 +115,8 @@
this.addBehaviorDirect({
alias, behaviors,
});
this.makeDirty();
},
editTriggerName(name) {
this.$prompt(this.$t('Input event name'), this.$t('Rename event'), {
......@@ -134,6 +136,8 @@
} else {
this.$set(events, value, events[name]);
this.$delete(events, name);
this.makeDirty();
}
}
}).catch(() => {
......@@ -141,6 +145,8 @@
},
deleteTrigger(name) {
this.$delete(this.activeComponent.events, name);
this.makeDirty();
},
editBehavior(behavior, behaviors) {
this.$refs.behaviorEditorDialog.show(behavior, behaviors);
......@@ -149,6 +155,11 @@
if (isPreview) {
events.$emit('save-and-preview')
}
this.makeDirty();
},
onBehaviorNameChange(){
this.makeDirty();
},
async deleteBehavior(index, behaviors) {
let deleteMeta = false;
......@@ -160,6 +171,8 @@
type: 'warning'
}).then(() => {
deleteMeta = true;
this.makeDirty();
}).catch(action => {
if(action === 'close'){
close = true;
......@@ -179,7 +192,10 @@
'getProcess',
'addBehaviorDirect',
'deleteBehaviorDirect',
])
]),
...mapMutations([
'makeDirty',
]),
}
}
</script>
......
......@@ -7,7 +7,7 @@
</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-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">
......@@ -29,7 +29,7 @@
<script>
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 = {
......@@ -86,6 +86,7 @@ export default {
name: 'PropsTab',
components: { 'dynamic-component': dynamicComponent },
data() {
const componentsMap = this.$t('view_node_menu');
return {
componentsMap,
form: {
......
......@@ -32,7 +32,6 @@
<script>
import { mapGetters, mapState } from 'vuex';
import _ from 'lodash';
import { componentsMap, getCmpProps } from '../../../utils/common';
import dynamicComponent from '../components/dynamicComponent';
const scriptTypeMap = {
......@@ -70,7 +69,6 @@ export default {
components: { 'dynamic-component': dynamicComponent },
data() {
return {
componentsMap,
form: {
scripts: []
},
......
......@@ -28,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
......
......@@ -12,7 +12,17 @@
</el-dropdown-menu>
</el-dropdown>
</div>
<el-input v-model="filterText" size="mini"/>
<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"
......@@ -79,7 +89,8 @@
uploadHeaders: {
authorization: 'Bearer ' + window['zeroing_token'],
},
expandedKeys: []
expandedKeys: [],
nodeFilterPresets: this.$t('nodeFilterPresets'),
};
},
computed: {
......@@ -89,7 +100,7 @@
})
},
watch: {
activeComponentId: function(val) {
activeComponentId: function (val) {
this.$refs.tree.setCheckedKeys([val]); // 设置选中节点
this.$refs.tree.setCurrentKey(val); // 设置高亮节点
this.expandedKeys = [val]; // 展开对应的节点
......@@ -99,13 +110,63 @@
},
},
methods: {
updateFilter(){
if(this.$refs.tree){
updateFilter() {
if (this.$refs.tree) {
this.$refs.tree.filter(this.filterText);
}
},
filterNodeMethod(value, data) {
return data.name.toUpperCase().indexOf(value.toUpperCase()) >= 0;
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;
......@@ -179,14 +240,14 @@
this.pasteNode({
node: data,
parentNode: node.parent.data,
pasteState:1
pasteState: 1
});
break;
case 'pasteChild':
this.pasteNode({
node: data,
parentNode: node.parent.data,
pasteState:2
pasteState: 2
});
break;
case 'export':
......@@ -204,11 +265,23 @@
break;
}
},
...mapMutations(['copyNode','pasteNode','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
<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
......@@ -25,13 +25,25 @@
>
<template>
<el-form-item prop="id" :label="$t('ID')">
<el-input v-model="meta.id" :placeholder="$t('ID')" :readonly="!editable" />
<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-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-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"/>
......@@ -40,16 +52,21 @@
<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">
<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>
>{{ key }}</el-tag
>
</template>
<template v-else>{{$t('Empty')}}</template>
<template v-else>{{ $t("Empty") }}</template>
</el-link>
</el-form-item>
<el-form-item :label="$t('Output')">
......@@ -69,7 +86,9 @@
</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>
<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>-->
......@@ -80,7 +99,12 @@
:options="cmOptions"
@cursorActivity="onCodeChange"
/>-->
<monaco-editor ref="codeEditor" v-if="meta" :code="meta.script" :options="monacoConfig" />
<monaco-editor
ref="codeEditor"
v-if="meta"
:code="meta.script"
:options="monacoConfig"
/>
</div>
<div slot="footer" class="dialog-footer">
<div class="button-bar">
......@@ -88,12 +112,18 @@
<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-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="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>
<el-button size="mini" type="primary" plain @click="save(false)">{{
$t("Save")
}}</el-button>
</div>
</div>
<props-editor-dialog ref="propsEditorDialog" />
......@@ -231,5 +261,4 @@ export default {
};
</script>
<style scoped>
</style>
\ No newline at end of file
<style scoped></style>
<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"
......
......@@ -17,7 +17,7 @@
<data-mapping-editor ref="dataMappingEditor"/>
</el-tab-pane>
<el-tab-pane :label="$t('Custom module')" name="custom">
<custom-editor ref="customEditor"/>
<custom-module-editor ref="customModuleEditor"/>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
......@@ -31,19 +31,19 @@
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',
'customEditor',
'customModuleEditor',
];
export default {
name: "DetailsDialog",
components: {DataMappingEditor, CustomEditor, EnvEditor, ProjectEditor},
components: {DataMappingEditor, CustomModuleEditor, EnvEditor, ProjectEditor},
data() {
return {
visible: false,
......
......@@ -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>
......
<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
......@@ -17,6 +17,14 @@
: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"
......@@ -27,15 +35,18 @@
</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: "CustomEditor",
name: "CustomModuleEditor",
components: {AssetMappingEditorDialog},
data() {
return {
visible: false,
......@@ -53,10 +64,12 @@
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,
selected: this.editData.includes(meta.id),
assetMapping: data && (data.assetMapping || {}),
selected: !!data,
})
}
},
......@@ -64,12 +77,21 @@
const editData = this.editData;
editData.splice(0);
for (let meta of this.customMetas) {
if(meta.selected){
editData.push(meta.id);
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',
]),
......
......@@ -72,7 +72,6 @@
import 'codemirror/addon/edit/closebrackets.js'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/javascript-hint.js'
import {clonePureObj} from "../../../../utils";
export default {
......
......@@ -22,7 +22,7 @@
</el-table-column>
<el-table-column
fixed="right"
width="140">
width="180">
<template slot="header" slot-scope="scope">
<el-checkbox
v-model="onlyMine"
......@@ -35,6 +35,11 @@
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"
......@@ -70,6 +75,7 @@
</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>
......@@ -80,16 +86,17 @@
import {playWaiting, readTextFile, saveAs, selectFile} from "../utils";
import moment from "moment";
import DuplicateProjectDialog from "./Home/DuplicateProjectDialog";
import {PAGE_SIZE} from "../config";
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: PAGE_SIZE,
pageSize: PROJECT_PAGE_SIZE,
onlyMine: true,
}
},
......@@ -99,7 +106,7 @@
},
computed: {
showDeleteButton() {
return location.host.startsWith('localhost');
return localStorage.getItem('---zeroing-editor-delete-button');
},
...mapState([
'projects',
......@@ -145,6 +152,9 @@
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'),
......@@ -165,6 +175,9 @@
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}});
},
......
<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
......@@ -59,6 +59,7 @@
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";
......
......@@ -2402,9 +2402,9 @@ 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.41.0, codemirror@^5.50.0:
version "5.50.0"
resolved "https://registry.npm.taobao.org/codemirror/download/codemirror-5.50.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcodemirror%2Fdownload%2Fcodemirror-5.50.0.tgz#aeacd18f225735b17cbab98908edace87fedcdab"
resolved "https://registry.npm.taobao.org/codemirror/download/codemirror-5.50.0.tgz#aeacd18f225735b17cbab98908edace87fedcdab"
integrity sha1-rqzRjyJXNbF8urmJCO2s6H/tzas=
collection-visit@^1.0.0:
......@@ -8042,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"
......@@ -10129,4 +10134,4 @@ yeast@0.1.2:
"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#0b495d6ecae34322ad1f553a624cd31b9b4e5931"
resolved "http://gitlab2.dui88.com/laoqifeng/zeroing-code-divider.git#15af443267c46e32e84af786e3f01ef06e7da237"
......@@ -7,5 +7,6 @@
</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