提交 dda01e29 编写于 作者: Y Yan Chunwei 提交者: GitHub

Merge branch 'develop' into feature/apply_frontend_travis_ci

/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: [
{
"wall_time": 1512549785.061623,
"step": 60,
"query":
"sample=0&index=0&tag=input_reshape%2Finput%2Fimage%2F0&run=test",
"width": 28,
"height": 28
},
{"wall_time": 1512886109.672786, "step": 60, "query": "sample=0&index=1&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886124.266915, "step": 210, "query": "sample=0&index=2&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886138.898628, "step": 330, "query": "sample=0&index=3&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886139.883663, "step": 340, "query": "sample=0&index=4&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886147.195567, "step": 410, "query": "sample=0&index=5&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886156.47856, "step": 500, "query": "sample=0&index=6&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886187.82793, "step": 810, "query": "sample=0&index=7&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886200.386198, "step": 950, "query": "sample=0&index=8&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28},
{"wall_time": 1512886204.224405, "step": 990, "query": "sample=0&index=9&tag=input_reshape%2Finput%2Fimage%2F0&run=test", "width": 28, "height": 28}
]
}
};
};
/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: ''
}
};
};
/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: {
"test": {
"input_reshape/input/image/6": {
"displayName": "input_reshape/input/image/6", "description": "", "samples": 1}, "input_reshape/input/image/7": {"displayName": "input_reshape/input/image/7", "description": "", "samples": 1}, "input_reshape/input/image/4": {"displayName": "input_reshape/input/image/4", "description": "", "samples": 1}, "input_reshape/input/image/5": {"displayName": "input_reshape/input/image/5", "description": "", "samples": 1}, "input_reshape/input/image/2": {"displayName": "input_reshape/input/image/2", "description": "", "samples": 1}, "input_reshape/input/image/3": {"displayName": "input_reshape/input/image/3", "description": "", "samples": 1}, "input_reshape/input/image/0": {"displayName": "input_reshape/input/image/0", "description": "", "samples": 1}, "input_reshape/input/image/1": {"displayName": "input_reshape/input/image/1", "description": "", "samples": 1}, "input_reshape/input/image/8": {"displayName": "input_reshape/input/image/8", "description": "", "samples": 1}, "input_reshape/input/image/9": {"displayName": "input_reshape/input/image/9", "description": "", "samples": 1}}, "train": {"input_reshape/input/image/6": {"displayName": "input_reshape/input/image/6", "description": "", "samples": 1}, "input_reshape/input/image/7": {"displayName": "input_reshape/input/image/7", "description": "", "samples": 1}, "input_reshape/input/image/4": {"displayName": "input_reshape/input/image/4", "description": "", "samples": 1}, "input_reshape/input/image/5": {"displayName": "input_reshape/input/image/5", "description": "", "samples": 1}, "input_reshape/input/image/2": {"displayName": "input_reshape/input/image/2", "description": "", "samples": 1}, "input_reshape/input/image/3": {"displayName": "input_reshape/input/image/3", "description": "", "samples": 1}, "input_reshape/input/image/0": {"displayName": "input_reshape/input/image/0", "description": "", "samples": 1}, "input_reshape/input/image/1": {"displayName": "input_reshape/input/image/1", "description": "", "samples": 1}, "input_reshape/input/image/8": {"displayName": "input_reshape/input/image/8", "description": "", "samples": 1}, "input_reshape/input/image/9": {"displayName": "input_reshape/input/image/9", "description": "", "samples": 1}}}
}
};
};
...@@ -16,7 +16,7 @@ module.exports = function (path, queryParam, postParam) { ...@@ -16,7 +16,7 @@ module.exports = function (path, queryParam, postParam) {
_data: { _data: {
status: 0, status: 0,
msg: 'SUCCESS', msg: 'SUCCESS',
data: ["train", "test", "model"] data: ["train", "test"]
} }
}; };
}; };
...@@ -6,9 +6,7 @@ ...@@ -6,9 +6,7 @@
"release": "cross-env NODE_ENV=production node ./tool/build.js", "release": "cross-env NODE_ENV=production node ./tool/build.js",
"build": "cross-env NODE_ENV=dev node ./tool/build.js", "build": "cross-env NODE_ENV=dev node ./tool/build.js",
"dev": "cross-env NODE_ENV=dev node tool/dev-server.js", "dev": "cross-env NODE_ENV=dev node tool/dev-server.js",
"lint": "./node_modules/fecs/bin/fecs --rule", "lint": "./node_modules/fecs/bin/fecs --rule"
"precommit": "npm run lint",
"prepush": "npm run lint"
}, },
"engines": { "engines": {
"node": ">= 6.4.0" "node": ">= 6.4.0"
...@@ -52,7 +50,6 @@ ...@@ -52,7 +50,6 @@
"html-loader": "^0.4.4", "html-loader": "^0.4.4",
"html-webpack-plugin": "^2.28.0", "html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.4", "http-proxy-middleware": "^0.17.4",
"husky": "^0.14.3",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"opn": "^5.1.0", "opn": "^5.1.0",
"optimize-css-assets-webpack-plugin": "^1.3.2", "optimize-css-assets-webpack-plugin": "^1.3.2",
......
...@@ -32,7 +32,7 @@ export default { ...@@ -32,7 +32,7 @@ export default {
}, },
{ {
value: '2', value: '2',
url: '/image', url: '/images',
title: 'IMAGES' title: 'IMAGES'
} }
] ]
......
...@@ -3,26 +3,52 @@ ...@@ -3,26 +3,52 @@
<div class="visual-dl-chart-box" style="{{computedStyle}}"> <div class="visual-dl-chart-box" style="{{computedStyle}}">
</div> </div>
<div class="visual-dl-chart-actions"> <div class="visual-dl-chart-actions">
<san-button on-click="expandArea">
<san-icon size="20">settings_overscan</san-icon>
</san-button>
<ui-dropdown-menu <ui-dropdown-menu
san-if="downloadLink"
stlye="width:100px;"
hintText="download type" hintText="download type"
items="{{runsItems}}" items="{{runsItems}}"
value="{=downloadType=}" value="{=downloadType=}"
/> />
<san-button on-click="handleDownLoad"> <san-button
<san-icon>file_download</san-icon> san-if="downloadLink"
on-click="handleDownLoad">
<san-icon size="20">file_download</san-icon>
</san-button> </san-button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// components
import Button from 'san-mui/Button'; import Button from 'san-mui/Button';
import Icon from 'san-mui/Icon'; import Icon from 'san-mui/Icon';
import DropDownMenu from '../../../common/component/DropDownMenu'; import DropDownMenu from '../../../common/component/DropDownMenu';
import {generateJsonAndDownload} from '../../../common/util/downLoadFile';
// libs
import echarts from 'echarts'; import echarts from 'echarts';
import axios from 'axios'; import axios from 'axios';
import {isFinite, flatten, maxBy, minBy, sortBy, max, min} from 'lodash';
import {generateJsonAndDownload} from '../../../common/util/downLoadFile';
import {quantile} from '../../../common/util/index';
import moment from 'moment';
// service
import {getPluginScalarsScalars} from '../../../service'; import {getPluginScalarsScalars} from '../../../service';
const originLinesOpacity = 0.3;
const defaultSmoothWeight = 0.6;
const lineWidth = 1.5;
const minQuantile = 0.05;
const maxQuantile = 0.95;
// the time to refresh chart data
const intervalTime = 30;
export default { export default {
components: { components: {
'ui-dropdown-menu': DropDownMenu, 'ui-dropdown-menu': DropDownMenu,
'san-button': Button, 'san-button': Button,
...@@ -40,8 +66,6 @@ export default { ...@@ -40,8 +66,6 @@ export default {
return { return {
width: 400, width: 400,
height: 300, height: 300,
// line config
options: {},
data: [ data: [
{ {
name: 'train', name: 'train',
...@@ -56,11 +80,45 @@ export default { ...@@ -56,11 +80,45 @@ export default {
this.watch('runsItems', val => { this.watch('runsItems', val => {
this.initDownloadType(); this.initDownloadType();
}); });
this.watch('originData', originData => {
this.setChartData();
this.setChartsOutlier();
this.setChartHorizon();
});
this.watch('smoothing', smoothing => {
this.setChartData();
});
this.watch('outlier', outlier => {
// outlier
this.setChartsOutlier();
});
this.watch('horizontal', horizontal => {
this.setChartHorizon();
});
this.watch('runs', runs => {
this.setChartsRuns();
});
}, },
attached() { attached() {
let tagInfo = this.data.get('tagInfo'); let tagInfo = this.data.get('tagInfo');
this.initCharts(tagInfo); this.initCharts(tagInfo);
if (this.data.get('running')) {
this.startInterval();
}
this.watch('running', running => {
running ? this.startInterval() : this.stopInterval();
});
},
detached() {
this.stopInterval();
}, },
initDownloadType() { initDownloadType() {
...@@ -74,25 +132,50 @@ export default { ...@@ -74,25 +132,50 @@ export default {
initCharts(tagInfo) { initCharts(tagInfo) {
this.createChart(); this.createChart();
this.setChartsOptions(tagInfo); this.setChartsOptions(tagInfo);
this.setChartsData(tagInfo); this.getOriginChartsData(tagInfo);
}, },
createChart() { createChart() {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0]; let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
this.myChart = echarts.init(el); this.myChart = echarts.init(el);
}, },
setChartsOptions({tagList, tag}) { setChartsOptions({tagList, tag}) {
let seriesOption = tagList.map(item => { let seriesOption = tagList.map(item => [
return { {
name: item.run, name: item.run,
type: 'line', type: 'line',
showSymbol: false, showSymbol: false,
hoverAnimation: false, hoverAnimation: false,
data: [], z: 0,
smooth: true data: [],
}; animationDuration: 100,
}); lineStyle: {
normal: {
opacity: originLinesOpacity,
width: lineWidth
}
}
},
{
name: item.run,
type: 'line',
showSymbol: false,
hoverAnimation: false,
z: 1,
data: [],
animationDuration: 100,
lineStyle: {
normal: {
width: lineWidth
}
}
}
]
);
seriesOption = flatten(seriesOption);
let legendOptions = tagList.map(item => item.run); let legendOptions = tagList.map(item => item.run);
let that = this;
let option = { let option = {
title: { title: {
text: tag, text: tag,
...@@ -103,9 +186,14 @@ export default { ...@@ -103,9 +186,14 @@ export default {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
animation: false animation: true
}, },
position: [10, 300] position: ['10%', '90%'],
formatter(params, ticket, callback) {
let data = that.getFormatterPoints(params[0].data);
return that.tansformFormatterData(data);
}
}, },
toolbox: { toolbox: {
show: true, show: true,
...@@ -115,39 +203,26 @@ export default { ...@@ -115,39 +203,26 @@ export default {
restore: {}, restore: {},
saveAsImage: {} saveAsImage: {}
}, },
left: 40, left: '10%',
top: 270 top: '90%'
}, },
legend: { legend: {
data: legendOptions, data: legendOptions,
top: 30 top: '10%'
}, },
grid: { grid: {
top: 70, left: '10%',
bottom: 50 top: '20%',
right: '10%',
bottom: '20%'
},
xAxis: {
type: 'value'
}, },
xAxis: [
{
type: 'value',
boundaryGap: false
},
{
type: 'value',
boundaryGap: false
}
],
yAxis: { yAxis: {
type: 'value', type: 'value',
boundaryGap: [0, '100%'],
min(value) {
return value.min;
},
max(value) {
return value.max;
},
axisLabel: { axisLabel: {
formatter(value, index) { formatter(value, index) {
// keep 0.11111 to 0.111
return value.toString().slice(0, 5); return value.toString().slice(0, 5);
} }
} }
...@@ -156,7 +231,20 @@ export default { ...@@ -156,7 +231,20 @@ export default {
}; };
this.myChart.setOption(option); this.myChart.setOption(option);
}, },
setChartsData({tagList, tag}) {
// get origin data per 60 seconds
startInterval() {
this.getOringDataInterval = setInterval(() => {
let tagInfo = this.data.get('tagInfo');
this.getOriginChartsData(tagInfo);
}, intervalTime * 1000);
},
stopInterval() {
clearInterval(this.getOringDataInterval);
},
getOriginChartsData({tagList, tag}) {
let requestList = tagList.map(item => { let requestList = tagList.map(item => {
let params = { let params = {
run: item.run, run: item.run,
...@@ -165,22 +253,41 @@ export default { ...@@ -165,22 +253,41 @@ export default {
return getPluginScalarsScalars(params); return getPluginScalarsScalars(params);
}); });
axios.all(requestList).then(resArray => { axios.all(requestList).then(resArray => {
let seriesData = resArray.map(res => { this.data.set('originData', resArray.map(res => res.data));
return { });
data: res.data, },
setChartData() {
let originData = this.data.get('originData');
let seriesData = originData.map(lineData => {
// add the smoothed data
this.tansformDataset(lineData);
return [
{
data: lineData,
encode: { encode: {
// map 1 to xAixs // map 1 to xAixs
x: [1], x: [1],
// map 2 to yAixs // map 2 to yAixs
y: [2] y: [2]
} }
}; },
}); {
this.myChart.setOption({ data: lineData,
series: seriesData encode: {
}); // map 1 to xAixs
x: [1],
// map 3 to yAixs, the third number is smoothed value
y: [3]
}
}
];
});
this.myChart.setOption({
series: flatten(seriesData)
}); });
}, },
getChartOptions() { getChartOptions() {
return this.myChart.getOption() || {}; return this.myChart.getOption() || {};
}, },
...@@ -192,15 +299,307 @@ export default { ...@@ -192,15 +299,307 @@ export default {
let tagInfo = this.data.get('tagInfo'); let tagInfo = this.data.get('tagInfo');
let fileName = tagInfo.tag.replace(/\//g, '-'); let fileName = tagInfo.tag.replace(/\//g, '-');
generateJsonAndDownload(sery.data, fileName); generateJsonAndDownload(sery.data, fileName);
},
tansformDataset(seriesData) {
let smoothing = this.data.get('smoothing');
// smooth
this.tansformData(seriesData, smoothing || defaultSmoothWeight);
},
/**
* @desc 1、add smooth data depend on smoothingWeight. see https://en.wikipedia.org/wiki/Moving_average for detail
* 2、add relative data
* @param {Object} echarts series Object
* @param {number} smoothingWeight smooth weight, between 0 ~ 1
*/
tansformData(seriesData, smoothingWeight) {
let data = seriesData;
let last = data.length > 0 ? 0 : NaN;
let numAccum = 0;
let startValue;
data.forEach((d, i) => {
let nextVal = d[2];
// second to millisecond
let millisecond = Math.floor(d[0] * 1000);
if (i === 0) {
startValue = millisecond;
}
// relative time, millisecond to hours
d[4] = Math.floor(millisecond - startValue) / (60 * 60 * 1000);
if (!isFinite(nextVal)) {
d[3] = nextVal;
} else {
last = last * smoothingWeight + (1 - smoothingWeight) * nextVal;
numAccum++;
let debiasWeight = 1;
if (smoothingWeight !== 1.0) {
debiasWeight = 1.0 - Math.pow(smoothingWeight, numAccum);
}
d[3] = last / debiasWeight;
}
});
},
// chart outlier options methods and functions ---- start
// compute Y domain from originData
setChartsOutlier(seriesData) {
let outlier = this.data.get('outlier');
let originData = this.data.get('originData');
let domainRangeArray = originData.map(seriesData => this.computeDataRange(seriesData, outlier));
// compare,get the best Y domain。
let flattenNumbers = flatten(domainRangeArray);
let finalMax = max(flattenNumbers);
let finalMin = min(flattenNumbers);
// add padding
let PaddedYDomain = this.paddedYDomain(finalMin, finalMax);
this.setChartOutlierOptions(PaddedYDomain);
// store Y domain,if originData is not change,Y domain keep same.
},
// compute max and min from array, if outlier is true, return quantile range
computeDataRange(arr, isQuantile) {
// get data range
let max;
let min;
if (!isQuantile) {
// get the orgin data range
max = maxBy(arr, item => item[2])[2];
min = minBy(arr, item => item[2])[2];
}
else {
// get the quantile range
let sorted = sortBy(arr, [item => item[2]]);
min = quantile(sorted, minQuantile, item => item[2]);
max = quantile(arr, maxQuantile, item => item[2]);
}
return [min, max];
},
paddedYDomain(min, max) {
return {
max: max > 0 ? max * 1.1 : max * 0.9,
min: min > 0 ? min * 0.9 : min * 1.1
};
},
setChartOutlierOptions({min, max}) {
this.myChart.setOption({
yAxis: {
min,
max
}
});
},
// chart horizontal options methods and functions ---- start
setChartHorizon() {
let horizontal = this.data.get('horizontal');
let seriesOption = this.myChart.getOption().series;
let encodeSeries = val => {
return {
encode: {
x: [val]
}
};
};
let stepSeies = seriesOption.map(item => encodeSeries(1));
let relativeSeies = seriesOption.map(item => encodeSeries(4));
let wallSeries = seriesOption.map(item => encodeSeries(0));
let horizontalToxAxisOptions = {
step: {
xAxis: {
type: 'value'
},
series: stepSeies
},
relative: {
xAxis: {
type: 'value'
},
series: relativeSeies
},
wall: {
xAxis: {
type: 'time'
},
series: wallSeries
}
};
this.myChart.setOption(horizontalToxAxisOptions[horizontal]);
},
expandArea() {
let isExpand = this.data.get('isExpand');
if (!isExpand) {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
el.style.width = '800px';
el.style.height = '600px';
this.data.set('isExpand', true);
this.myChart.resize({
width: 800,
height: 600
});
}
else {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
el.style.width = '400px';
el.style.height = '300px';
this.data.set('isExpand', false);
this.myChart.resize({
width: 400,
height: 300
});
}
},
getFormatterPoints(data) {
let originData = this.data.get('originData');
let tagList = this.data.get('tagInfo.tagList');
let sortingMethod = this.data.get('sortingMethod');
// Can't know exactly the tigger runs.
// If the step is same, regard the point as the trigger point.
let [, step, triggerValue] = data;
let points = originData.map((series, index) => {
let nearestItem;
if (step === 0) {
nearestItem = series[0];
}
else {
for (let i = 0; i < series.length; i++) {
let item = series[i];
if (item[1] === step) {
nearestItem = item;
break;
}
if (item[1] > step) {
nearestItem = series[i - 1];
break;
}
if (!nearestItem) {
nearestItem = series[series.length - 1];
}
}
}
return {
run: tagList[index].run,
item: nearestItem
};
});
if (sortingMethod === 'default' || !sortingMethod) {
return points;
}
let sortedPoints;
switch (sortingMethod) {
case 'desc':
sortedPoints = sortBy(points, one => one.item[3]);
sortedPoints.reverse();
break;
case 'asc':
sortedPoints = sortBy(points, one => one.item[3]);
break;
case 'nearest':
// compare other ponts width the trigger point, caculate the nearest sort.
sortedPoints = sortBy(points, one => one.item[3] - triggerValue);
break;
default:
sortedPoints = points;
}
return sortedPoints;
},
tansformFormatterData(data) {
let indexPropMap = {
Time: 0,
Step: 1,
Value: 2,
Smoothed: 3,
Relative: 4
};
let widthPropMap = {
Run: 40,
Time: 90,
Step: 40,
Value: 50,
Smoothed: 50,
Relative: 40
};
let tranformedData = data.map(item => {
let data = item.item;
return {
Run: item.run,
// keep six number for easy-read
Smoothed: data[indexPropMap.Smoothed].toString().slice(0, 6),
Value: data[indexPropMap.Value].toString().slice(0, 6),
Step: data[indexPropMap.Step],
Time: moment(Math.floor(data[indexPropMap.Time] * 1000), 'X').format('YYYY-MM-DD HH:mm:ss'),
// relative display value should take easy-read into consideration.
// better to tranform data to 'day:hour', 'hour:minutes', 'minute: seconds' and second only.
Relative: Math.floor(data[indexPropMap.Relative] * 60 * 60) + 's'
};
});
let headerHtml = '<tr style="font-size:14px;">';
headerHtml += Object.keys(tranformedData[0]).map(key => {
return '<td style="padding: 0 4px; font-weight: bold; width:' + widthPropMap[key] + 'px;">' + key + '</td>';
}).join('');
headerHtml += '</tr>';
let content = tranformedData.map(item => {
let str = '<tr style="font-size:12px;">';
str += Object.keys(item).map(val => {
return '<td style="padding: 0 4px">' + item[val] + '</td>';
}).join('');
str += '</tr>';
return str;
}).join('');
return '<table style="text-align: left;table-layout: fixed;width: 480px;"><thead>' + headerHtml + '</thead>'
+ '<tbody>' + content + '</tbody><table>';
},
setChartsRuns() {
// let tagInfo = this.data.get('tagInfo');
// let runs = this.data.get('config.runs');
// let seriesOption = [
// {
// lineStyle: {
// normal: {
// opacity: originLinesOpacity
// }
// }
// }
// ];
} }
}; };
</script> </script>
<style lang="stylus"> <style lang="stylus">
.visual-dl-charts .visual-dl-charts
float left float left
margin-bottom 20px
.visual-dl-chart-actions .visual-dl-chart-actions
height 50px
margin-left 10%
.sm-form-item .sm-form-item
width 300px float left
display inline-block width 100px
margin-top 0px
display block
.sm-button
float left
display block
height 20px
line-height 20px
margin-top 10px
padding 0 10px
</style> </style>
<template>
<div class="visual-dl-image">
<h3>{{tagInfo.tag.displayName}}
<span>{{tagInfo.run}}</span>
</h3>
<p>
<span>Step:</span>
<span>{{imgData.step}};</span>
<span>{{imgData.wall_time | formatTime}}</span>
</p>
<san-slider
on-change="handleSlideChange($event)"
value="{{currentIndex}}"
min="{{slider.min}}"
max="{{steps}}"
step="{{1}}"
/>
<img src="{{imgData.imgSrc}}" width="{{imgData.width}}" height="{{imgData.height}}"/>
</div>
</template>
<script>
import Slider from 'san-mui/Slider';
import {getPluginImagesImages} from '../../../service';
export default {
components: {
'san-slider': Slider
},
computed: {
steps() {
let data = this.data.get('data') || [];
return data.length - 1;
}
},
filters: {
formatTime(value) {
if (!value) {
return;
}
let time = new Date();
time.setTime(value.toString().split('.')[0]);
return time;
}
},
initData() {
return {
slider: {
value: '0',
label: '',
min: 0,
step: 1
},
currentIndex: 0
};
},
inited() {
let {run, tag} = this.data.get('tagInfo');
let {displayName, samples} = tag;
let params = {
run,
displayName,
samples
};
getPluginImagesImages(params).then(({data}) => {
this.data.set('data', data);
this.data.set('currentIndex', data.length - 1);
});
// currentIndex change event
this.watch('currentIndex', index => {
/* eslint-disable fecs-camelcase */
let currentImgInfo = this.data.get('data') ? this.data.get('data')[index] : {};
let {height, width, query, step, wall_time} = currentImgInfo;
let url = '/data/plugin/images/individualImage?ts=' + wall_time;
let imgSrc = [url, query].join('&');
this.data.set('imgData', {
imgSrc,
height,
width,
step,
wall_time
});
/* eslint-enable fecs-camelcase */
});
},
handleSlideChange(val) {
this.data.set('currentIndex', val);
}
};
</script>
<style lang="stylus">
.visual-dl-image
float left
padding 10px
font-size 12px
.visual-dl-chart-actions
.sm-form-item
width 300px
display inline-block
</style>
...@@ -5,9 +5,13 @@ ...@@ -5,9 +5,13 @@
on-click="handleHeadClick()" on-click="handleHeadClick()"
> >
<span>{{title}}</span> <span>{{title}}</span>
<span class="visaul-dl-expand-head-info">
<ui-icon class="visaul-dl-expand-head-arrow" size="20">{{iconName}}</ui-icon>
<span class="visaul-dl-expand-head-num">({{info}})</span>
</span>
</h3> </h3>
<div <div
style="display:{{displayStyle}}" san-if="{{isShow}}"
class="visaul-dl-expand-panel-content" class="visaul-dl-expand-panel-content"
> >
<slot></slot> <slot></slot>
...@@ -15,11 +19,15 @@ ...@@ -15,11 +19,15 @@
</div> </div>
</template> </template>
<script> <script>
import Icon from 'san-mui/Icon';
export default { export default {
components: {
'ui-icon': Icon
},
computed: { computed: {
displayStyle() { iconName() {
let isShow = this.data.get('isShow'); let isShow = this.data.get('isShow');
return isShow ? 'block' : 'none'; return isShow ? 'expand_less' : 'expand_more';
} }
}, },
initData() { initData() {
...@@ -44,6 +52,16 @@ export default { ...@@ -44,6 +52,16 @@ export default {
height 50px height 50px
padding 0 20px padding 0 20px
cursor pointer cursor pointer
position relative
.visaul-dl-expand-head-info
position absolute
left 90%
.visaul-dl-expand-head-arrow
vertical-align middle
.visaul-dl-expand-head-num
line-height 20px
font-size 12px
font-weight normal
.visaul-dl-expand-panel-content .visaul-dl-expand-panel-content
padding 0 20px padding 0 20px
.visaul-dl-expand-panel-content:after .visaul-dl-expand-panel-content:after
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
max="{{max}}" max="{{max}}"
step="{{step}}" step="{{step}}"
/> />
<span>{{value}}</span>
<!--
<san-input-number <san-input-number
min="{{min}}" min="{{min}}"
max="{{max}}" max="{{max}}"
...@@ -17,6 +19,7 @@ ...@@ -17,6 +19,7 @@
on-change="handlerChange($event)" on-change="handlerChange($event)"
size="small"> size="small">
</san-input-number> </san-input-number>
-->
</div> </div>
</div> </div>
</template> </template>
...@@ -24,6 +27,7 @@ ...@@ -24,6 +27,7 @@
<script> <script>
import Slider from 'san-mui/Slider'; import Slider from 'san-mui/Slider';
import InputNumber from 'san-mui/InputNumber'; import InputNumber from 'san-mui/InputNumber';
import {debounce} from 'lodash';
export default { export default {
components: { components: {
'san-slider': Slider, 'san-slider': Slider,
...@@ -35,11 +39,18 @@ export default { ...@@ -35,11 +39,18 @@ export default {
label: '', label: '',
max: 0, max: 0,
min: 0, min: 0,
step: 0 step: 0,
debounce: 400
}; };
}, },
handleSlideChange(val) { inited() {
this.data.set('value', val.toString()); let debounceTime = this.data.get('debounce');
this.handleSlideChange = debounce(val => {
this.data.set('value', val.toString());
}, debounceTime);
this.handlerChange = debounce(e => {
this.data.set('value', e.target.value);
}, debounceTime);
} }
}; };
</script> </script>
...@@ -53,7 +64,7 @@ export default { ...@@ -53,7 +64,7 @@ export default {
margin-right 20px margin-right 20px
margin-top 4px margin-top 4px
float left float left
width 100px width 160px
.sm-slider-thumb .sm-slider-thumb
transform translate(0, -50%) transform translate(0, -50%)
.sm-inputNumber .sm-inputNumber
......
html {
font-family: "RobotoDraft", "Roboto", sans-serif;
}
// initual style // initual style
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; } h1, h2, h3, h4, h5, h6{ font-size:100%; } address, cite, dfn, em, var { font-style:normal; } code, kbd, pre, samp { font-family:couriernew, courier, monospace; } small{ font-size:12px; } ul, ol { list-style:none; } a { text-decoration:none; } a:hover { text-decoration:underline; } sup { vertical-align:text-top; } sub{ vertical-align:text-bottom; } legend { color:#000; } fieldset, img { border:0; } button, input, select, textarea { font-size:100%; } table { border-collapse:collapse; border-spacing:0; } body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; } h1, h2, h3, h4, h5, h6{ font-size:100%; } address, cite, dfn, em, var { font-style:normal; } code, kbd, pre, samp { font-family:couriernew, courier, monospace; } small{ font-size:12px; } ul, ol { list-style:none; } a { text-decoration:none; } a:hover { text-decoration:underline; } sup { vertical-align:text-top; } sub{ vertical-align:text-bottom; } legend { color:#000; } fieldset, img { border:0; } button, input, select, textarea { font-size:100%; } table { border-collapse:collapse; border-spacing:0; }
...@@ -5,8 +9,8 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo ...@@ -5,8 +9,8 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo
// modify some styles of san-mui // modify some styles of san-mui
.sm-icon .sm-icon
width 100% width 20px
height 100% height 20px
overflow hidden overflow hidden
.sm-radio, .sm-radio,
...@@ -52,7 +56,22 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo ...@@ -52,7 +56,22 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo
color #58666e color #58666e
.sm-dropdown-menu .sm-dropdown-menu
.sm-text-field
margin-bottom 0px
min-height 30px
border-bottom solid 1px #e4e4e4
.sm-text-field-content
padding 0
padding-left 4px
input
cursor pointer
.sm-dropdown-menu-icon .sm-dropdown-menu-icon
top 6px
right 0
bottom 0
.sm-icon .sm-icon
color #58666e color #58666e
...@@ -64,12 +83,14 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo ...@@ -64,12 +83,14 @@ body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fo
.sm-menu-item.state-selected .sm-menu-item.state-selected
color #ff4081 color #ff4081
.sm-text-field
width 100%
.sm-text-field-input .sm-text-field-input
color #58666e color #58666e
.sm-form-item .sm-form-item
margin-top 10px margin-top 10px
overflow hidden
.label .label
color rgba(0,0,0,0.54) color rgba(0,0,0,0.54)
......
import axios from 'axios'; import axios from 'axios';
import qs from 'qs'; import qs from 'qs';
import Notification from '../ui/Notification'; import Notification from '../component/Notification';
const STATUS = 'status'; const STATUS = 'status';
const STATUSINFO = 'msg'; const STATUSINFO = 'msg';
......
import quantile from './quantile';
export {
quantile
};
export default function (x) {
return x === null ? NaN : +x;
}
import number from './number';
export default function (values, p, valueof) {
if (valueof == null) {
return valueof = number;
}
let n = values.length;
if (!(n)) {
return;
}
if ((p = +p) <= 0 || n < 2) {
return +valueof(values[0], 0, values);
}
if (p >= 1) {
return +valueof(values[n - 1], n - 1, values);
}
let i = (n - 1) * p;
let i0 = Math.floor(i);
let value0 = +valueof(values[i0], i0, values);
let value1 = +valueof(values[i0 + 1], i0 + 1, values);
return value0 + (value1 - value0) * (i - i0);
}
<template>
<div class="visual-dl-scalar-container">
<div class="visual-dl-scalar-left">
<div class="visual-dl-scalar-config-container">
<ui-config
runsItems="{{runsItems}}"
config="{=config=}"
></ui-config>
</div>
</div>
<div class="visual-dl-scalar-right">
<ui-chart-page
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{filteredTagsList}}"
title="Tags matching {{config.groupNameReg}}"
></ui-chart-page>
<ui-chart-page
san-for="item in groupedTags"
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{item.tags}}"
title="{{item.group}}"
></ui-chart-page>
</div>
</div>
</template>
<script>
import {getPluginImagesTags, getRuns} from '../service';
import config from './ui/config';
import chartPage from './ui/chartPage';
import {debounce, flatten, uniq} from 'lodash';
export default {
components: {
'ui-config': config,
'ui-chart-page': chartPage
},
computed: {
runsItems() {
let runsArray = this.data.get('runsArray') || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.data.get('tags');
let runs = Object.keys(tags);
let tagsArray = runs.map(run => Object.keys(tags[run]));
let allUniqTags = uniq(flatten(tagsArray));
// get the data for every chart
return allUniqTags.map(tag => {
let tagList = runs.map(run => {
return {
run,
tag: tags[run][tag]
};
});
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.data.get('tagsList') || [];
// put data in group
let groupData = {};
tagsList.forEach(item => {
let group = item.group;
if (groupData[group] === undefined) {
groupData[group] = [];
groupData[group].push(item);
}
else {
groupData[group].push(item);
}
});
// to array
let groups = Object.keys(groupData);
return groups.map(group => {
return {
group,
tags: groupData[group]
};
});
}
},
initData() {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
smoothing: '0.5',
horizontal: '1',
sortingMethod: '2',
link: [],
chart: []
}
};
},
inited() {
getPluginImagesTags().then(({errno, data}) => {
this.data.set('tags', data);
// filter when inited
let groupNameReg = this.data.get('config.groupNameReg');
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.data.set('runsArray', data);
});
// need debounce, can't use computed
this.watch('config.groupNameReg', debounce(this.filterTagsList, 300));
},
filterTagsList(groupNameReg) {
let tagsList = this.data.get('tagsList') || [];
let regExp = new RegExp(groupNameReg);
let filtedTagsList = tagsList.filter(item => regExp.test(item.tag));
this.data.set('filteredTagsList', filtedTagsList);
}
};
</script>
<style lang="stylus">
@import '../style/variables';
+prefix-classes('visual-dl-scalar-')
.container
padding-left 300px
position relative
.left
width 280px
min-height 300px
border solid 1px #e4e4e4
position absolute
left 0
.right
width 100%
border solid 1px #e4e4e4
min-height 300px
padding 20px
</style>
import {router} from 'san-router';
import Images from './Images';
router.add({
target: '#content',
rule: '/images',
Component: Images
});
<template>
<div class="visual-dl-chart-page">
<ui-expand-panel title="{{title}}">
<div san-for="tag in filteredTagList" class="visual-dl-chart-box">
<ui-image
san-for="tag in tag.tagList"
tagInfo="{{tag}}"
config="{{config}}"
runsItems="{{runsItems}}"
></ui-image>
</div>
<ui-pagination
san-if="total > pageSize"
on-pageChange="handlePageChange($event)"
current="{{currentPage}}"
pageSize="{{pageSize}}"
total="{{total}}"
showSizeChanger="{{false}}"
/>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/component/ExpandPanel';
import image from '../../common/component/Charts/image';
import Pagination from 'san-mui/Pagination';
export default {
components: {
'ui-image': image,
'ui-expand-panel': ExpandPanel,
'ui-pagination': Pagination
},
computed: {
filteredTagList() {
let tagList = this.data.get('tagList') || [];
let currentPage = this.data.get('currentPage');
let pageSize = this.data.get('pageSize');
return tagList.slice((currentPage - 1) * pageSize, currentPage * pageSize);
},
total() {
let tagList = this.data.get('tagList') || [];
return tagList.length;
}
},
initData() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 8
};
},
handlePageChange({pageNum}) {
this.data.set('currentPage', pageNum);
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-')
.chart-page
.chart-box:after
content: "";
clear: both;
display: block;
</style>
<template>
<div class="visual-dl-scalar-config-com">
<san-text-field
hintText="input a tag group name to search"
label="Group name RegExp"
inputValue="{=config.groupNameReg=}"
/>
<ui-checkbox-group
value="{=config.imageSize=}"
items="{{imageSizeItems}}"
/>
<ui-checkbox-group
value="{=config.run=}"
label="Runs"
items="{{runsItems}}"
/>
<san-button class="visual-dl-scalar-run-toggle" variants="raised secondery">Toggle All Runs</san-button>
</div>
</template>
<script>
import TextField from 'san-mui/TextField';
import CheckBoxGroup from '../../common/component/CheckBoxGroup';
export default {
components: {
'san-text-field': TextField,
'ui-checkbox-group': CheckBoxGroup
},
initData() {
return {
config: {
groupName: 'aa',
imageSize: '0.5',
run: ''
},
runsItems: [],
imageSizeItems: [
{
value: '1',
name: 'Show actual image size'
}
]
};
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-scalar-')
.config-com
width 90%
margin 0 auto
.run-toggle
width 100%
margin-top 20px
</style>
...@@ -4,6 +4,7 @@ import './common/component/ui-common.styl'; ...@@ -4,6 +4,7 @@ import './common/component/ui-common.styl';
import './home/index'; import './home/index';
import './scalars/index'; import './scalars/index';
import './images/index';
import App from './App'; import App from './App';
new App({ new App({
......
...@@ -10,14 +10,14 @@ ...@@ -10,14 +10,14 @@
</div> </div>
<div class="visual-dl-scalar-right"> <div class="visual-dl-scalar-right">
<ui-chart-page <ui-chart-page
config="{{config}}" config="{{filteredConfig}}"
runsItems="{{runsItems}}" runsItems="{{runsItems}}"
tagList="{{filteredTagsList}}" tagList="{{filteredTagsList}}"
title="Tags matching {{config.groupNameReg}}" title="Tags matching {{config.groupNameReg}}"
></ui-chart-page> ></ui-chart-page>
<ui-chart-page <ui-chart-page
san-for="item in groupedTags" san-for="item in groupedTags"
config="{{config}}" config="{{filteredConfig}}"
runsItems="{{runsItems}}" runsItems="{{runsItems}}"
tagList="{{item.tags}}" tagList="{{item.tags}}"
title="{{item.group}}" title="{{item.group}}"
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
import {getPluginScalarsTags, getRuns} from '../service'; import {getPluginScalarsTags, getRuns} from '../service';
import config from './ui/config'; import config from './ui/config';
import chartPage from './ui/chartPage'; import chartPage from './ui/chartPage';
import {debounce, flatten, uniq} from 'lodash'; import {debounce, flatten, uniq, isArray} from 'lodash';
export default { export default {
components: { components: {
'ui-config': config, 'ui-config': config,
...@@ -91,6 +91,21 @@ export default { ...@@ -91,6 +91,21 @@ export default {
tags: groupData[group] tags: groupData[group]
}; };
}); });
},
filteredConfig() {
let tansformArr = ['downloadLink', 'outlier'];
let config = this.data.get('config') || {};
let filteredConfig = {};
Object.keys(config).forEach(key => {
let val = config[key];
if (tansformArr.indexOf(key) > -1) {
filteredConfig[key] = isArray(val) && val[0] === 'yes';
}
else {
filteredConfig[key] = val;
}
});
return filteredConfig;
} }
}, },
initData() { initData() {
...@@ -99,11 +114,13 @@ export default { ...@@ -99,11 +114,13 @@ export default {
tags: [], tags: [],
config: { config: {
groupNameReg: '.*', groupNameReg: '.*',
smoothing: '0.5', smoothing: 0.6,
horizontal: '1', horizontal: 'step',
sortingMethod: '2', sortingMethod: 'default',
link: [], downloadLink: [],
chart: [] outlier: [],
runs: [],
running: true
} }
}; };
}, },
...@@ -117,6 +134,7 @@ export default { ...@@ -117,6 +134,7 @@ export default {
}); });
getRuns().then(({errno, data}) => { getRuns().then(({errno, data}) => {
this.data.set('runsArray', data); this.data.set('runsArray', data);
this.data.set('config.runs', data);
}); });
// need debounce, can't use computed // need debounce, can't use computed
......
<template> <template>
<div class="visaul-dl-chart-page"> <div class="visual-dl-chart-page">
<ui-expand-panel title="{{title}}"> <ui-expand-panel info="{{tagList.length}}" title="{{title}}">
<div class="visaul-dl-chart-box"> <div class="visual-dl-chart-box">
<ui-chart <ui-chart
san-for="tag in filteredTagList" san-for="tag in filteredTagList"
tagInfo="{{tag}}" tagInfo="{{tag}}"
config="{{config}}" groupNameReg="{{config.groupNameReg}}"
smoothing="{{config.smoothing}}"
horizontal="{{config.horizontal}}"
sortingMethod="{{config.sortingMethod}}"
downloadLink="{{config.downloadLink}}"
outlier="{{config.outlier}}"
runs="{{config.runs}}"
running="{{config.running}}"
runsItems="{{runsItems}}" runsItems="{{runsItems}}"
></ui-chart> ></ui-chart>
</div> </div>
...@@ -61,11 +68,11 @@ export default { ...@@ -61,11 +68,11 @@ export default {
@import '../../style/variables'; @import '../../style/variables';
+prefix-classes('visual-dl-') +prefix-classes('visual-dl-')
.visaul-dl-chart-page .chart-page
.visaul-dl-chart-box:after .chart-box:after
content: ""; content: "";
clear: both; clear: both;
display: block; display: block;
</style> </style>
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
label="Smoothing" label="Smoothing"
value="{=config.smoothing=}" value="{=config.smoothing=}"
min="{{0}}" min="{{0}}"
max="{{1}}" max="{{0.999}}"
step="{{0.001}}" step="{{0.001}}"
/> />
<ui-radio-group <ui-radio-group
label="Horizontal" label="Horizontal"
value="{{config.horizontal}}" value="{=config.horizontal=}"
items="{{horizontalItems}}" items="{{horizontalItems}}"
/> />
<ui-dropdown-menu <ui-dropdown-menu
...@@ -23,18 +23,25 @@ ...@@ -23,18 +23,25 @@
value="{=config.sortingMethod=}" value="{=config.sortingMethod=}"
/> />
<ui-checkbox-group <ui-checkbox-group
value="{=config.link=}" value="{=config.downloadLink=}"
items="{{lnksItems}}" items="{{lnksItems}}"
/> />
<ui-checkbox-group <ui-checkbox-group
value="{=config.chart=}" value="{=config.outlier=}"
items="{{chartItems}}" items="{{chartItems}}"
/> />
<ui-checkbox-group <ui-checkbox-group
value="{=config.runs=}"
label="Runs" label="Runs"
items="{{runsItems}}" items="{{runsItems}}"
/> />
<san-button class="visual-dl-scalar-run-toggle" variants="raised secondery">Toggle All Runs</san-button> <san-button
class="visual-dl-scalar-run-toggle"
variants="raised {{config.running ? 'secondery' : 'primary'}}"
on-click="toggleAllRuns"
>
{{config.running ? 'Running' : 'Stopped'}}
</san-button>
</div> </div>
</template> </template>
<script> <script>
...@@ -56,59 +63,65 @@ export default { ...@@ -56,59 +63,65 @@ export default {
initData() { initData() {
return { return {
config: { config: {
groupName: 'aa', groupNameReg: '.*',
smoothing: '0.5', smoothing: '0.6',
horizontal: '1', horizontal: 'step',
sortingMethod: '2', sortingMethod: 'default',
link: [], downloadLink: [],
chart: [] outlier: [],
running: true
}, },
horizontalItems: [ horizontalItems: [
{ {
name: 'Step', name: 'Step',
value: '1' value: 'step'
}, },
{ {
name: 'Relative', name: 'Relative',
value: '2' value: 'relative'
}, },
{ {
name: 'Wall', name: 'Wall',
value: '3' value: 'wall'
} }
], ],
sortingMethodItems: [ sortingMethodItems: [
{ {
name: 'default', name: 'default',
value: '1' value: 'default'
}, },
{ {
name: 'descending', name: 'descending',
value: '2' value: 'desc'
}, },
{ {
name: 'ascending', name: 'ascending',
value: '3' value: 'asc'
}, },
{ {
name: 'nearest', name: 'nearest',
value: '4' value: 'nearest'
} }
], ],
runsItems: [], runsItems: [],
lnksItems: [ lnksItems: [
{ {
value: '1', value: 'yes',
name: 'Show data download links' name: 'Show data download links'
} }
], ],
chartItems: [ chartItems: [
{ {
value: '1', value: 'yes',
name: 'Ignore outliers in chart scaling' name: 'Ignore outliers in chart scaling'
} }
] ]
}; };
},
toggleAllRuns() {
let running = this.data.get('config.running');
this.data.set('config.running', !running);
this.fire('runningChange', running);
} }
}; };
</script> </script>
......
...@@ -5,3 +5,7 @@ export const getPluginScalarsTags = makeService('/data/plugin/scalars/tags'); ...@@ -5,3 +5,7 @@ export const getPluginScalarsTags = makeService('/data/plugin/scalars/tags');
export const getRuns = makeService('/data/runs'); export const getRuns = makeService('/data/runs');
export const getPluginScalarsScalars = makeService('/data/plugin/scalars/scalars'); export const getPluginScalarsScalars = makeService('/data/plugin/scalars/scalars');
export const getPluginImagesTags = makeService('/data/plugin/images/tags');
export const getPluginImagesImages = makeService('/data/plugin/images/images');
'use strict';
function noopReplace (val) { return val; } function noopReplace (val) { return val; }
function HtmlReplacePlugin(options) { function HtmlReplacePlugin(options) {
......
'use strict';
const webpack = require('webpack'); const webpack = require('webpack');
const rm = require('rimraf'); const rm = require('rimraf');
const ora = require('ora'); const ora = require('ora');
......
'use strict';
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) { hotClient.subscribe(function (event) {
......
'use strict';
process.env.NODE_ENV = 'dev'; process.env.NODE_ENV = 'dev';
let devPort = 8999; let devPort = 8999;
let opn = require('opn'); let opn = require('opn');
......
'use strict';
const path = require('path'); const path = require('path');
const projectPath = path.resolve(__dirname, '..'); const projectPath = path.resolve(__dirname, '..');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
......
'use strict';
const webpack = require('webpack'); const webpack = require('webpack');
const path = require('path'); const path = require('path');
const projectPath = path.resolve(__dirname, '..'); const projectPath = path.resolve(__dirname, '..');
......
'use strict';
const webpack = require('webpack'); const webpack = require('webpack');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
let merge = require('webpack-merge'); let merge = require('webpack-merge');
......
'use strict';
const webpack = require('webpack'); const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const path = require('path'); const path = require('path');
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册