Commit 480c0363 authored by 张晨辰's avatar 张晨辰

feat: merge

parents 6bdad43a f8719341
......@@ -59,7 +59,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);
}
}
......
......@@ -6,8 +6,9 @@
import {fetchApi} from "./common";
export async function fetchAll() {
export async function fetchAll(currentPage, pageSize) {
return await fetchApi('/api/project/query', {
params: {currentPage, pageSize,},
errMessage: 'Failed to fetch projects',
})
}
......@@ -52,9 +53,9 @@ export async function saveOne(project) {
})
}
export async function pack(id, debug) {
export async function pack(id, debug, packedAssets) {
return await fetchApi('/api/project/pack', {
params: {id, debug},
params: {id, debug, packedAssets},
method: 'post',
errMessage: 'Failed to pack project',
})
......@@ -73,11 +74,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',
......
......@@ -6,6 +6,7 @@ export let API_HOST;
if (process.env.NODE_ENV === 'development') {
//API_HOST = 'http://10.10.95.74: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;
......@@ -22,6 +23,8 @@ export const SSO_VERIFY_PAGE_URL = '/sso/logout';
export const DOCK_POINT_OFFSET = 4;
export const PAGE_SIZE = 20;
//文件类型图标 t表示展示缩略图
export const fileTypeIcon = {
'': 'file-empty',
......
......@@ -17,6 +17,8 @@
"Delete all": "Delete all",
"Import": "Import",
"Export": "Export",
"Importing project": "Importing project",
"Exporting project": "Exporting project",
"Upload": "Upload",
"Uploading": "Uploading",
"Edit": "Edit",
......@@ -55,6 +57,7 @@
"Packing": "Packing",
"Type": "Type",
"Group": "Group",
"Meta Search": "Meta Search",
"Access denied": "Access denied",
"Invalid router": "Invalid router",
"Jump after": "Jump after {cd}s",
......@@ -133,7 +136,8 @@
"save": "Save",
"details": "Details",
"preview": "Preview",
"pack": "Pack",
"preview2": "Fast-Preview",
"publish": "Publish",
"exit": "Exit",
"undo": "Undo",
"redo": "Redo"
......
......@@ -17,6 +17,8 @@
"Delete all": "删除全部",
"Import": "导入",
"Export": "导出",
"Importing project": "项目导入中",
"Exporting project": "项目导出中",
"Upload": "上传",
"Uploading": "上传中",
"Edit": "编辑",
......@@ -55,6 +57,7 @@
"Packing": "打包",
"Type": "类型",
"Group": "分组",
"Meta Search": "过程元查找",
"Access denied": "无权限",
"Invalid router": "无效的页面",
"Jump after": "{cd}秒后跳转",
......@@ -133,7 +136,8 @@
"save": "保存",
"details": "详情",
"preview": "预览",
"pack": "发布",
"preview2": "快速预览",
"publish": "发布",
"exit": "退出",
"undo": "撤销",
"redo": "重做"
......
......@@ -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')
},
]
})
......@@ -186,7 +186,10 @@ export const behaviorStore = {
addProcessMeta(commit, isInline, masterProcess, meta);
return meta;
}
},
searchMeta({state}, {keyword}) {
searchMeta(state.data.processes, keyword);
},
}
};
......@@ -204,3 +207,11 @@ 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){
}*/
}
}
......@@ -11,7 +11,7 @@ export const envStore = {
state: {
initialized: false,
name: 'Zeroing Editor',
version: '1.0.1',
version: '1.0.2',
templates: {
builtin: ['blank'],
custom: [],
......@@ -19,6 +19,7 @@ export const envStore = {
processes: [],
scripts: [],
customs: [],
projectCount: 0,
},
mutations: {
updateEnv(state, env) {
......
......@@ -11,6 +11,7 @@ import { clonePureObj, saveAs } from "../../utils";
import { template } from "../../template";
import { importView, uploadFile } from "../../api/project";
import events from "@/global-events";
import {packImages} from "../../utils/sheet-pack";
const defaultOptions = {
pageTitle: 'no title',
......@@ -313,6 +314,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++) {
......@@ -429,7 +437,7 @@ export const projectStore = {
/**
* 返回Array格式的views
*/
views: state => {
views(state) {
// 如果有选中的节点,则展示对应的视图组
// 否则展示第一个视图组
let _view = [];
......@@ -440,6 +448,9 @@ export const projectStore = {
}
return _view;
},
assets(state) {
return state.data.assets;
}
},
actions: {
......@@ -558,11 +569,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 || []);
......@@ -596,38 +606,91 @@ export const projectStore = {
saveAs(content, `view-${view.name}.zrv`);
});
},
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);
async uploadFiles({commit}, files) {
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 }, debug) {
const result = await projectApi.pack(state.id, debug);
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());
}
},
};
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;
}
}
console.log(newAssets);
return newAssets;
}
......@@ -25,10 +25,13 @@ export const projectsStore = {
},
},
actions: {
async updateProjects({commit}) {
const projects = await projectApi.fetchAll();
async fetchProjects({commit}, {currentPage, pageSize}) {
const projects = await projectApi.fetchAll(currentPage, pageSize);
commit('updateProjects', projects);
},
async fetchProject({commit}, projectId) {
return await projectApi.fetchOne(projectId);
},
async createProject({commit}, data) {
const project = await projectApi.createOne(data);
commit('addProject', project);
......
......@@ -34,10 +34,8 @@ content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, u
<body>
<div id="$CONTAINER_ID$" style="line-height:0;font-size:0"></div>
<script src="${process.env.NODE_ENV === 'development' ? 'http://10.10.94.134:4002/dist/index.js' : 'http://yun.duiba.com.cn/editor/zeroing/libs/engine.a913456caee2ee40c20108a1c4571f61479e54a1.js'}"></script>
<script src="$PROCESSES_URL$"></script>
<script src="$SCRIPTS_URL$"></script>
<script src="$CUSTOMS_URL$"></script>
<script src="${process.env.NODE_ENV === 'development' ? 'http://10.10.94.134:4002/dist/index.js' : 'http://yun.duiba.com.cn/editor/zeroing/libs/engine.d20665eae76962ad21c153fe7a719130b08e292c.js.js'}"></script>
$SCRIPTS$
<script>
engine.launch('//yun.duiba.com.cn/aurora/$VERSION$-data.json');
</script>
......
......@@ -67,11 +67,11 @@
&:hover {
& > .icon > .operate-bar {
display: block;
visibility: visible;
}
& > .name > .full-name {
display: block;
visibility: visible;
}
}
......@@ -93,14 +93,18 @@
}
.operate-bar {
display: none;
visibility: hidden;
position: absolute;
right: 1px;
bottom: 1px;
display: flex;
.el-button + .el-button{
margin-left: 2px;
}
.el-button {
padding: 2px;
margin-left: 2px;
}
}
}
......@@ -121,7 +125,7 @@
}
.full-name {
display: none;
visibility: hidden;
position: absolute;
top: 0;
left: 0;
......
......@@ -64,13 +64,27 @@ $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{
padding: 3px;
......
......@@ -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,9 @@
.project-detail {
}
.bottom-bar{
display: flex;
justify-content: space-between;
}
}
\ No newline at end of file
......@@ -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);
}
}
}
};
......@@ -47,26 +47,6 @@ 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';
}
......@@ -101,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();
......
/**
* Created by rockyl on 2019-12-08.
*/
import MaxRectsBinPack from "./MaxRectsBinPack";
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] = sprite;
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);
}
......@@ -72,8 +72,8 @@
destroyed() {
document.removeEventListener('keydown', this.onKeyPress)
},
created(){
events.$on('saveAndPreview',()=>{
created() {
events.$on('saveAndPreview', () => {
this.clickMenu("preview");
});
},
......@@ -87,7 +87,9 @@
onKeyPress(e) {
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.clickMenu("preview");
this.saveProject();
return false;
}
},
......@@ -166,8 +168,11 @@
case 'preview':
await this.pack(true);
break;
case 'pack':
await this.pack();
case 'preview2':
this.preview();
break;
case 'publish':
await this.publish();
break;
case 'undo':
this.$store.commit('undoRedo', 1);
......@@ -197,13 +202,18 @@
break;
}
},
async publish(){
this.pack();
},
async pack(debug = false) {
const loading = this.$loading({
lock: true,
text: this.$t('Packing'),
});
try {
await this.saveProject(false);
if(!debug){
await this.saveProject(false);
}
const packResult = await this.packProject(debug);
this.$message({
message: this.$t('Pack project successfully'),
......@@ -227,6 +237,7 @@
});*/
} catch (e) {
console.log(e);
this.$message({
message: this.$t('Pack project failed'),
type: 'error',
......@@ -238,6 +249,18 @@
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',
......@@ -246,6 +269,8 @@
"saveToRemote",
'updateEnv',
'packProject',
'savePreview',
'packImages',
])
}
}
......
......@@ -37,7 +37,7 @@
</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";
......@@ -57,9 +57,7 @@
}
},
computed: {
...mapState({
assets: state => state.project.data.assets
}),
...mapGetters(['assets']),
},
mounted() {
this.currentItem = null;
......
......@@ -2,8 +2,11 @@
<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()">
<img @dragstart="assetDragStart(data)" v-if="showThumbnail" draggable="true" class="thumbnail" :src="thumbnailUrl"
alt="thumb" @dblclick="onDbclick()">
<div class="operate-bar">
<el-button circle size="mini" type="success" icon="el-icon-upload2" @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 +20,17 @@
</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 {
}
},
watch: {
return {}
},
watch: {},
computed: {
fileIcon() {
let icon = fileTypeIcon[this.data.ext];
......@@ -43,7 +42,7 @@
thumbnailUrl() {
return this.data.url;
},
ellipsisName(){
ellipsisName() {
return strEllipsis(this.data.name, 14, 5);
},
},
......@@ -78,11 +77,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>
......
......@@ -10,7 +10,10 @@
: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="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)"/>
......@@ -25,6 +28,7 @@
</div>
</split-panes>
<meta-editor-dialog ref="metaEditorDialog" @input="onSaveMeta"/>
<meta-search-dialog ref="metaSearchDialog"/>
</div>
</template>
......@@ -38,11 +42,12 @@
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 {
......@@ -99,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);
......
......@@ -115,7 +115,13 @@
},
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'));
......@@ -192,8 +198,8 @@
this.addProcessFromCopy(dataStr);
}
},
addProcessFromCopy(avatar){
if(typeof avatar === 'string'){
addProcessFromCopy(avatar) {
if (typeof avatar === 'string') {
avatar = JSON.parse(avatar);
}
......@@ -249,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) {
......@@ -299,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;
......@@ -309,18 +334,20 @@
}
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){ //内联直接复制
if (meta.isInline) { //内联直接复制
let avatar = clonePureObj(data);
this.addProcessFromCopy(avatar);
}else{
} else {
copy(JSON.stringify(data), {format: 'process-data'});
this.$message({
message: this.$t('Copied process to clipboard'),
......
<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
......@@ -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="150px" 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>
<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
......@@ -33,6 +34,11 @@
type="success" icon="el-icon-document-copy"
size="small" circle plain>
</el-button>
<el-button
@click.native.prevent="exportProject(scope.row)"
type="warning" icon="el-icon-receiving"
size="small" circle plain>
</el-button>
<!--<el-button
@click.native.prevent="onDeleteProject(scope.row)"
type="danger" icon="el-icon-delete"
......@@ -42,8 +48,18 @@
</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="env.projectCount">
</el-pagination>
</div>
<create-project-dialog ref="createProjectDialog" @success="onCreateProject"/>
<duplicate-project-dialog ref="duplicateProjectDialog" @success="onDuplicateProject"/>
......@@ -54,9 +70,10 @@
<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 {PAGE_SIZE} from "../config";
export default {
name: "Home",
......@@ -64,10 +81,13 @@
data() {
return {
appVersion: 'v1.0.0',
currentPage: 1,
pageSize: PAGE_SIZE,
}
},
mounted() {
playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {});
playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {
});
},
computed: {
...mapState([
......@@ -76,10 +96,10 @@
]),
},
methods: {
prepare(){
prepare() {
return Promise.all([
this.updateEnv(),
this.updateProjects(),
this.handleCurrentChange(1),
])
},
moment(time) {
......@@ -88,6 +108,21 @@
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);
},
......@@ -100,10 +135,12 @@
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) {
......@@ -115,8 +152,18 @@
editProject(projectID) {
this.$router.push({name: 'editor', params: {projectID}});
},
async handleCurrentChange(page) {
const loading = this.$loading({
lock: true,
text: this.$t('In processing'),
});
await this.fetchProjects({currentPage: page, pageSize: this.pageSize});
loading.close();
},
...mapActions([
'updateProjects',
'fetchProjects',
'fetchProject',
'createProject',
'deleteProject',
'updateEnv',
]),
......
<template>
<iframe ref="iframe" class="player-wrapper">
</iframe>
</template>
<script>
export default {
name: "Preview",
data() {
return {
ts: '',
}
},
mounted() {
if(!this.ts){
this.ts = localStorage.getItem('preview-ts');
}
document.addEventListener("visibilitychange", this.onVisibilityChange);
setTimeout(()=>{
this.buildPage();
}, 100);
},
destroyed(){
document.removeEventListener("visibilitychange", this.onVisibilityChange);
},
methods: {
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;
}
}
},
buildPage() {
const {projectID} = this.$route.params;
const storeKey = 'preview-project-' + projectID;
let data = localStorage.getItem(storeKey);
let dataObj = JSON.parse(data);
let {options: {tpl, pageTitle, containerId}} = dataObj.data;
const launchCode = `engine.launchWithLocalStorage("${projectID}");`;
tpl = tpl
.replace('$PAGE_TITLE$', pageTitle)
.replace('$CONTAINER_ID$', containerId)
.replace('$SCRIPTS$', '')
.replace('engine.launch(\'//yun.duiba.com.cn/aurora/$VERSION$-data.json\');', launchCode);
const doc = this.$refs.iframe.contentDocument;
doc.write(tpl);
},
}
}
</script>
<style scoped>
.player-wrapper{
border: 0;
width: 100%;
height: 100%;
}
</style>
\ 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