fix: 修改 拖拽高度的问题,以及 chart-line nodata的判断
This commit is contained in:
@@ -37,7 +37,14 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
test: /\.(js)$/,
|
test: /\.(js)$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client'), resolve('node_modules/element-ui/packages/scrollbar')],
|
include: [
|
||||||
|
resolve('src'),
|
||||||
|
resolve('test'),
|
||||||
|
resolve('node_modules/webpack-dev-server/client'),
|
||||||
|
resolve('node_modules/element-ui/packages/scrollbar'),
|
||||||
|
resolve('node_modules/@interactjs'),
|
||||||
|
resolve('node_modules/vue-grid-layout')
|
||||||
|
],
|
||||||
exclude: '/node_modules/',
|
exclude: '/node_modules/',
|
||||||
options: {
|
options: {
|
||||||
presets: [
|
presets: [
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"vue-color": "^2.8.1",
|
"vue-color": "^2.8.1",
|
||||||
"vue-countupjs": "^1.0.0",
|
"vue-countupjs": "^1.0.0",
|
||||||
"vue-draggable-resizable": "^2.3.0",
|
"vue-draggable-resizable": "^2.3.0",
|
||||||
"vue-grid-layout": "2.3.7",
|
"vue-grid-layout": "^2.3.12",
|
||||||
"vue-i18n": "^8.15.1",
|
"vue-i18n": "^8.15.1",
|
||||||
"vue-quill-editor": "^3.0.6",
|
"vue-quill-editor": "^3.0.6",
|
||||||
"vue-resource": "^1.5.1",
|
"vue-resource": "^1.5.1",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="chartInfo.param.showHeader ? '' : 'padding-top: 15px;'" class="nz-chart" :class="chartInfo.param.showHeader ? '' : 'no-header'" >
|
<div :style="chartInfo.param.showHeader ? '' : 'padding-top: 15px;'" class="nz-chart" :class="chartInfo.param.showHeader ? '' : 'no-header'" >
|
||||||
<loading :loading="loading"></loading>
|
<loading :loading="loading"></loading>
|
||||||
<chart-no-data v-if="isNoData || isError"></chart-no-data>
|
<chart-no-data v-if="isNoData || isError || chartChildrenData"></chart-no-data>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<chart-time-series
|
<chart-time-series
|
||||||
v-if="isTimeSeries(chartInfo.type)"
|
v-if="isTimeSeries(chartInfo.type)"
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
:chart-info="chartInfo"
|
:chart-info="chartInfo"
|
||||||
:chart-option="chartOption"
|
:chart-option="chartOption"
|
||||||
:is-fullscreen="isFullscreen"
|
:is-fullscreen="isFullscreen"
|
||||||
|
@chartIsNoData="chartIsNoData"
|
||||||
></chart-time-series>
|
></chart-time-series>
|
||||||
<chart-pie
|
<chart-pie
|
||||||
:ref="'chart' + chartInfo.id"
|
:ref="'chart' + chartInfo.id"
|
||||||
@@ -205,6 +206,11 @@ export default {
|
|||||||
from: String,
|
from: String,
|
||||||
isError: Boolean
|
isError: Boolean
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
chartChildrenData: false
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isNoData () {
|
isNoData () {
|
||||||
return lodash.isEmpty(this.chartData) && ['text', 'url'].indexOf(this.chartInfo.type) === -1
|
return lodash.isEmpty(this.chartData) && ['text', 'url'].indexOf(this.chartInfo.type) === -1
|
||||||
@@ -215,7 +221,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return getOption(this.chartInfo.type)
|
return getOption(this.chartInfo.type)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isTimeSeries,
|
isTimeSeries,
|
||||||
@@ -235,6 +241,9 @@ export default {
|
|||||||
isMap,
|
isMap,
|
||||||
isTable,
|
isTable,
|
||||||
isGauge,
|
isGauge,
|
||||||
|
chartIsNoData (flag) {
|
||||||
|
this.chartChildrenData = flag
|
||||||
|
},
|
||||||
resize () {
|
resize () {
|
||||||
this.$refs['chart' + this.chartInfo.id].resize()
|
this.$refs['chart' + this.chartInfo.id].resize()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export default {
|
|||||||
gauge.min = getMetricTypeValue(data.values, 'min')
|
gauge.min = getMetricTypeValue(data.values, 'min')
|
||||||
gauge.label = data.metric
|
gauge.label = data.metric
|
||||||
gauge.legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
|
gauge.legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
|
||||||
|
gauge.name = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
|
||||||
gauge.showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(gauge.value, null, -1, 2)
|
gauge.showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(gauge.value, null, -1, 2)
|
||||||
// gauge.value = gauge.showValue
|
// gauge.value = gauge.showValue
|
||||||
gauge.mapping = this.selectMapping(gauge.value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
|
gauge.mapping = this.selectMapping(gauge.value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
|
||||||
@@ -155,6 +156,7 @@ export default {
|
|||||||
this.gaugeData.forEach((item, index) => {
|
this.gaugeData.forEach((item, index) => {
|
||||||
const myChart = echarts.init(document.getElementById('chart-gauge-' + this.chartInfo.id + '-' + index))
|
const myChart = echarts.init(document.getElementById('chart-gauge-' + this.chartInfo.id + '-' + index))
|
||||||
const option = lodash.cloneDeep(this.chartOption)
|
const option = lodash.cloneDeep(this.chartOption)
|
||||||
|
option.tooltip = {}
|
||||||
option.series[0].data.push(item)
|
option.series[0].data.push(item)
|
||||||
option.series[0].max = item.max
|
option.series[0].max = item.max
|
||||||
option.series[0].detail = {
|
option.series[0].detail = {
|
||||||
@@ -179,6 +181,8 @@ export default {
|
|||||||
color: lodash.get(item, 'mapping.color.bac', randomcolor())
|
color: lodash.get(item, 'mapping.color.bac', randomcolor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
option.tooltip.formatter = this.formatterFunc
|
||||||
|
// option.tooltip.position = this.formatterFunc
|
||||||
option.series[0].min = item.max == item.min ? 0 : item.min
|
option.series[0].min = item.max == item.min ? 0 : item.min
|
||||||
myChart.setOption(option)
|
myChart.setOption(option)
|
||||||
this.chartInstances.push(myChart)
|
this.chartInstances.push(myChart)
|
||||||
@@ -186,7 +190,6 @@ export default {
|
|||||||
},
|
},
|
||||||
gaugeChartResize () {
|
gaugeChartResize () {
|
||||||
this.chartInstances.forEach(item => {
|
this.chartInstances.forEach(item => {
|
||||||
console.log(123123123, item)
|
|
||||||
if (item && item.resize) {
|
if (item && item.resize) {
|
||||||
item.resize()
|
item.resize()
|
||||||
}
|
}
|
||||||
@@ -198,12 +201,27 @@ export default {
|
|||||||
this.gaugeChartResize()
|
this.gaugeChartResize()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
formatterFunc: function (params, ticket, callback) {
|
||||||
|
const self = this
|
||||||
|
return `<div>
|
||||||
|
<div style="white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis; min-width: 150px; max-width: 600px; line-height: 18px; font-size: 14px;">
|
||||||
|
<div style="max-width: 500px;white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis;margin-bottom: 5px">${params.data.name}</div>
|
||||||
|
<div style="font-size:12px;display:flex;justify-content: space-between;">
|
||||||
|
<div>value</div>
|
||||||
|
<div style="display: ${params.data.mapping && params.data.mapping.display ? 'none' : 'inline-block'}">${params.data.showValue}</div>
|
||||||
|
<div style="display: ${params.data.mapping && params.data.mapping.display ? 'inline-block' : 'none'}">${self.handleDisplay(params.data.mapping.display, { ...params.data.labels, value: params.data.showValue })}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.chartOption.color || (this.chartOption.color = initColor(20))
|
this.chartOption.color || (this.chartOption.color = initColor(20))
|
||||||
this.colorList = this.chartOption.color
|
this.colorList = this.chartOption.color
|
||||||
this.initChart()
|
this.initChart()
|
||||||
|
console.log(this.isFullscreen , 'isFullscreen')
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.chartInstances.forEach(item => {
|
this.chartInstances.forEach(item => {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<span class="text">
|
||||||
|
{{text}}
|
||||||
|
<button>xxx</button>
|
||||||
|
<span class="vue-draggable-handle"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.vue-draggable-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
|
||||||
|
background-position: bottom right;
|
||||||
|
padding: 0 8px 8px 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-origin: content-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CustomDragElement',
|
||||||
|
props: {
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: 'x'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
console.log('### ' + this.text + ' ready!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
46
nezha-fronted/src/components/chart/chart/grid/DOM.js
Normal file
46
nezha-fronted/src/components/chart/chart/grid/DOM.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
let currentDir = 'auto'
|
||||||
|
// let currentDir = "auto";
|
||||||
|
|
||||||
|
function hasDocument () {
|
||||||
|
return (typeof document !== 'undefined')
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasWindow () {
|
||||||
|
return (typeof window !== 'undefined')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocumentDir () {
|
||||||
|
if (!hasDocument()) {
|
||||||
|
return currentDir
|
||||||
|
}
|
||||||
|
const direction = (typeof document.dir !== 'undefined')
|
||||||
|
? document.dir
|
||||||
|
: document.getElementsByTagName('html')[0].getAttribute('dir')
|
||||||
|
return direction
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDocumentDir (dir = 'auto') {
|
||||||
|
// export function setDocumentDir(dir){
|
||||||
|
if (!hasDocument) {
|
||||||
|
currentDir = dir
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = document.getElementsByTagName('html')[0]
|
||||||
|
html.setAttribute('dir', dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addWindowEventListener (event, callback) {
|
||||||
|
if (!hasWindow) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.addEventListener(event, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeWindowEventListener (event, callback) {
|
||||||
|
if (!hasWindow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.removeEventListener(event, callback)
|
||||||
|
}
|
||||||
864
nezha-fronted/src/components/chart/chart/grid/GridItem.vue
Normal file
864
nezha-fronted/src/components/chart/chart/grid/GridItem.vue
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="item"
|
||||||
|
class="vue-grid-item"
|
||||||
|
:class="classObj"
|
||||||
|
:style="style"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
<span v-if="resizableAndNotStatic" ref="handle" :class="resizableHandleClass"></span>
|
||||||
|
<!--<span v-if="draggable" ref="dragHandle" class="vue-draggable-handle"></span>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.vue-grid-item {
|
||||||
|
transition: all 200ms ease;
|
||||||
|
transition-property: left, top, right;
|
||||||
|
/* add right for rtl */
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.no-touch {
|
||||||
|
-ms-touch-action: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.cssTransforms {
|
||||||
|
transition-property: transform;
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.cssTransforms.render-rtl {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.resizing {
|
||||||
|
opacity: 0.6;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.vue-draggable-dragging {
|
||||||
|
transition:none;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.vue-grid-placeholder {
|
||||||
|
background: red;
|
||||||
|
opacity: 0.2;
|
||||||
|
transition-duration: 100ms;
|
||||||
|
z-index: 2;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item > .vue-resizable-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
|
||||||
|
background-position: bottom right;
|
||||||
|
padding: 0 3px 3px 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-origin: content-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: se-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item > .vue-rtl-resizable-handle {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAuMDAwMDAwMDAwMDAwMDAyIiBoZWlnaHQ9IjEwLjAwMDAwMDAwMDAwMDAwMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDwhLS0gQ3JlYXRlZCB3aXRoIE1ldGhvZCBEcmF3IC0gaHR0cDovL2dpdGh1Yi5jb20vZHVvcGl4ZWwvTWV0aG9kLURyYXcvIC0tPgogPGc+CiAgPHRpdGxlPmJhY2tncm91bmQ8L3RpdGxlPgogIDxyZWN0IGZpbGw9Im5vbmUiIGlkPSJjYW52YXNfYmFja2dyb3VuZCIgaGVpZ2h0PSIxMiIgd2lkdGg9IjEyIiB5PSItMSIgeD0iLTEiLz4KICA8ZyBkaXNwbGF5PSJub25lIiBvdmVyZmxvdz0idmlzaWJsZSIgeT0iMCIgeD0iMCIgaGVpZ2h0PSIxMDAlIiB3aWR0aD0iMTAwJSIgaWQ9ImNhbnZhc0dyaWQiPgogICA8cmVjdCBmaWxsPSJ1cmwoI2dyaWRwYXR0ZXJuKSIgc3Ryb2tlLXdpZHRoPSIwIiB5PSIwIiB4PSIwIiBoZWlnaHQ9IjEwMCUiIHdpZHRoPSIxMDAlIi8+CiAgPC9nPgogPC9nPgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxsaW5lIGNhbnZhcz0iI2ZmZmZmZiIgY2FudmFzLW9wYWNpdHk9IjEiIHN0cm9rZS1saW5lY2FwPSJ1bmRlZmluZWQiIHN0cm9rZS1saW5lam9pbj0idW5kZWZpbmVkIiBpZD0ic3ZnXzEiIHkyPSItNzAuMTc4NDA3IiB4Mj0iMTI0LjQ2NDE3NSIgeTE9Ii0zOC4zOTI3MzciIHgxPSIxNDQuODIxMjg5IiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSIjMDAwIiBmaWxsPSJub25lIi8+CiAgPGxpbmUgc3Ryb2tlPSIjNjY2NjY2IiBzdHJva2UtbGluZWNhcD0idW5kZWZpbmVkIiBzdHJva2UtbGluZWpvaW49InVuZGVmaW5lZCIgaWQ9InN2Z181IiB5Mj0iOS4xMDY5NTciIHgyPSIwLjk0NzI0NyIgeTE9Ii0wLjAxODEyOCIgeDE9IjAuOTQ3MjQ3IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8bGluZSBzdHJva2UtbGluZWNhcD0idW5kZWZpbmVkIiBzdHJva2UtbGluZWpvaW49InVuZGVmaW5lZCIgaWQ9InN2Z183IiB5Mj0iOSIgeDI9IjEwLjA3MzUyOSIgeTE9IjkiIHgxPSItMC42NTU2NCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiM2NjY2NjYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+);
|
||||||
|
background-position: bottom left;
|
||||||
|
padding-left: 3px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-origin: content-box;
|
||||||
|
cursor: sw-resize;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-grid-item.disable-userselect {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import { setTopLeft, setTopRight, setTransformRtl, setTransform } from './utils'
|
||||||
|
import { getControlPosition, createCoreData } from './draggableUtils'
|
||||||
|
import { getColsFromBreakpoint } from './responsiveUtils'
|
||||||
|
import { getDocumentDir } from './DOM'
|
||||||
|
// var eventBus = require('./eventBus');
|
||||||
|
|
||||||
|
import '@interactjs/auto-start'
|
||||||
|
import '@interactjs/actions/drag'
|
||||||
|
import '@interactjs/actions/resize'
|
||||||
|
import '@interactjs/modifiers'
|
||||||
|
import '@interactjs/dev-tools'
|
||||||
|
import interact from '@interactjs/interact'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GridItem',
|
||||||
|
props: {
|
||||||
|
/* cols: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}, */
|
||||||
|
/* containerWidth: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
|
||||||
|
},
|
||||||
|
rowHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
maxRows: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}, */
|
||||||
|
isDraggable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
isResizable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
/* useCssTransforms: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
static: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
minH: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
minW: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
maxH: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: Infinity
|
||||||
|
},
|
||||||
|
maxW: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: Infinity
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
w: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
i: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dragIgnoreFrom: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'a, button'
|
||||||
|
},
|
||||||
|
dragAllowFrom: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
resizeIgnoreFrom: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'a, button'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['eventBus', 'layout'],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
cols: 1,
|
||||||
|
containerWidth: 100,
|
||||||
|
rowHeight: 30,
|
||||||
|
margin: [10, 10],
|
||||||
|
maxRows: Infinity,
|
||||||
|
draggable: null,
|
||||||
|
resizable: null,
|
||||||
|
useCssTransforms: true,
|
||||||
|
useStyleCursor: true,
|
||||||
|
|
||||||
|
isDragging: false,
|
||||||
|
dragging: null,
|
||||||
|
isResizing: false,
|
||||||
|
resizing: null,
|
||||||
|
lastX: NaN,
|
||||||
|
lastY: NaN,
|
||||||
|
lastW: NaN,
|
||||||
|
lastH: NaN,
|
||||||
|
style: {},
|
||||||
|
rtl: false,
|
||||||
|
|
||||||
|
dragEventSet: false,
|
||||||
|
resizeEventSet: false,
|
||||||
|
|
||||||
|
previousW: null,
|
||||||
|
previousH: null,
|
||||||
|
previousX: null,
|
||||||
|
previousY: null,
|
||||||
|
innerX: this.x,
|
||||||
|
innerY: this.y,
|
||||||
|
innerW: this.w,
|
||||||
|
innerH: this.h
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
const self = this
|
||||||
|
|
||||||
|
// Accessible refernces of functions for removing in beforeDestroy
|
||||||
|
self.updateWidthHandler = function (width) {
|
||||||
|
self.updateWidth(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.compactHandler = function (layout) {
|
||||||
|
self.compact(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setDraggableHandler = function (isDraggable) {
|
||||||
|
if (self.isDraggable === null) {
|
||||||
|
self.draggable = isDraggable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setResizableHandler = function (isResizable) {
|
||||||
|
if (self.isResizable === null) {
|
||||||
|
self.resizable = isResizable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setRowHeightHandler = function (rowHeight) {
|
||||||
|
self.rowHeight = rowHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setMaxRowsHandler = function (maxRows) {
|
||||||
|
self.maxRows = maxRows
|
||||||
|
}
|
||||||
|
|
||||||
|
self.directionchangeHandler = () => {
|
||||||
|
this.rtl = getDocumentDir() === 'rtl'
|
||||||
|
this.compact()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setColNum = (colNum) => {
|
||||||
|
self.cols = parseInt(colNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventBus.$on('updateWidth', self.updateWidthHandler)
|
||||||
|
this.eventBus.$on('compact', self.compactHandler)
|
||||||
|
this.eventBus.$on('setDraggable', self.setDraggableHandler)
|
||||||
|
this.eventBus.$on('setResizable', self.setResizableHandler)
|
||||||
|
this.eventBus.$on('setRowHeight', self.setRowHeightHandler)
|
||||||
|
this.eventBus.$on('setMaxRows', self.setMaxRowsHandler)
|
||||||
|
this.eventBus.$on('directionchange', self.directionchangeHandler)
|
||||||
|
this.eventBus.$on('setColNum', self.setColNum)
|
||||||
|
|
||||||
|
this.rtl = getDocumentDir() === 'rtl'
|
||||||
|
},
|
||||||
|
beforeDestroy: function () {
|
||||||
|
const self = this
|
||||||
|
// Remove listeners
|
||||||
|
this.eventBus.$off('updateWidth', self.updateWidthHandler)
|
||||||
|
this.eventBus.$off('compact', self.compactHandler)
|
||||||
|
this.eventBus.$off('setDraggable', self.setDraggableHandler)
|
||||||
|
this.eventBus.$off('setResizable', self.setResizableHandler)
|
||||||
|
this.eventBus.$off('setRowHeight', self.setRowHeightHandler)
|
||||||
|
this.eventBus.$off('setMaxRows', self.setMaxRowsHandler)
|
||||||
|
this.eventBus.$off('directionchange', self.directionchangeHandler)
|
||||||
|
this.eventBus.$off('setColNum', self.setColNum)
|
||||||
|
if (this.interactObj) {
|
||||||
|
this.interactObj.unset() // destroy interact intance
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
if (this.layout.responsive && this.layout.lastBreakpoint) {
|
||||||
|
this.cols = getColsFromBreakpoint(this.layout.lastBreakpoint, this.layout.cols)
|
||||||
|
} else {
|
||||||
|
this.cols = this.layout.colNum
|
||||||
|
}
|
||||||
|
this.rowHeight = this.layout.rowHeight
|
||||||
|
this.containerWidth = this.layout.width !== null ? this.layout.width : 100
|
||||||
|
this.margin = this.layout.margin !== undefined ? this.layout.margin : [10, 10]
|
||||||
|
this.maxRows = this.layout.maxRows
|
||||||
|
|
||||||
|
if (this.isDraggable === null) {
|
||||||
|
this.draggable = this.layout.isDraggable
|
||||||
|
} else {
|
||||||
|
this.draggable = this.isDraggable
|
||||||
|
}
|
||||||
|
if (this.isResizable === null) {
|
||||||
|
this.resizable = this.layout.isResizable
|
||||||
|
} else {
|
||||||
|
this.resizable = this.isResizable
|
||||||
|
}
|
||||||
|
this.useCssTransforms = this.layout.useCssTransforms
|
||||||
|
this.useStyleCursor = this.layout.useStyleCursor
|
||||||
|
this.createStyle()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isDraggable: function () {
|
||||||
|
this.draggable = this.isDraggable
|
||||||
|
},
|
||||||
|
static: function () {
|
||||||
|
this.tryMakeDraggable()
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
draggable: function () {
|
||||||
|
this.tryMakeDraggable()
|
||||||
|
},
|
||||||
|
isResizable: function () {
|
||||||
|
this.resizable = this.isResizable
|
||||||
|
},
|
||||||
|
resizable: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
rowHeight: function () {
|
||||||
|
this.createStyle()
|
||||||
|
this.emitContainerResized()
|
||||||
|
},
|
||||||
|
cols: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
this.createStyle()
|
||||||
|
this.emitContainerResized()
|
||||||
|
},
|
||||||
|
containerWidth: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
this.createStyle()
|
||||||
|
this.emitContainerResized()
|
||||||
|
},
|
||||||
|
x: function (newVal) {
|
||||||
|
this.innerX = newVal
|
||||||
|
this.createStyle()
|
||||||
|
},
|
||||||
|
y: function (newVal) {
|
||||||
|
this.innerY = newVal
|
||||||
|
this.createStyle()
|
||||||
|
},
|
||||||
|
h: function (newVal) {
|
||||||
|
this.innerH = newVal
|
||||||
|
this.createStyle()
|
||||||
|
// this.emitContainerResized();
|
||||||
|
},
|
||||||
|
w: function (newVal) {
|
||||||
|
this.innerW = newVal
|
||||||
|
this.createStyle()
|
||||||
|
// this.emitContainerResized();
|
||||||
|
},
|
||||||
|
renderRtl: function () {
|
||||||
|
// console.log("### renderRtl");
|
||||||
|
this.tryMakeResizable()
|
||||||
|
this.createStyle()
|
||||||
|
},
|
||||||
|
minH: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
maxH: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
minW: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
maxW: function () {
|
||||||
|
this.tryMakeResizable()
|
||||||
|
},
|
||||||
|
'$parent.margin': function (margin) {
|
||||||
|
if (!margin || (margin[0] == this.margin[0] && margin[1] == this.margin[1])) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.margin = margin.map(m => Number(m))
|
||||||
|
this.createStyle()
|
||||||
|
this.emitContainerResized()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classObj () {
|
||||||
|
return {
|
||||||
|
'vue-resizable': this.resizableAndNotStatic,
|
||||||
|
static: this.static,
|
||||||
|
resizing: this.isResizing,
|
||||||
|
'vue-draggable-dragging': this.isDragging,
|
||||||
|
cssTransforms: this.useCssTransforms,
|
||||||
|
'render-rtl': this.renderRtl,
|
||||||
|
'disable-userselect': this.isDragging,
|
||||||
|
'no-touch': this.isAndroid && this.draggableOrResizableAndNotStatic
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resizableAndNotStatic () {
|
||||||
|
return this.resizable && !this.static
|
||||||
|
},
|
||||||
|
draggableOrResizableAndNotStatic () {
|
||||||
|
return (this.draggable || this.resizable) && !this.static
|
||||||
|
},
|
||||||
|
isAndroid () {
|
||||||
|
return navigator.userAgent.toLowerCase().indexOf('android') !== -1
|
||||||
|
},
|
||||||
|
renderRtl () {
|
||||||
|
return (this.layout.isMirrored) ? !this.rtl : this.rtl
|
||||||
|
},
|
||||||
|
resizableHandleClass () {
|
||||||
|
if (this.renderRtl) {
|
||||||
|
return 'vue-resizable-handle vue-rtl-resizable-handle'
|
||||||
|
} else {
|
||||||
|
return 'vue-resizable-handle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createStyle: function () {
|
||||||
|
if (this.x + this.w > this.cols) {
|
||||||
|
this.innerX = 0
|
||||||
|
this.innerW = (this.w > this.cols) ? this.cols : this.w
|
||||||
|
} else {
|
||||||
|
this.innerX = this.x
|
||||||
|
this.innerW = this.w
|
||||||
|
}
|
||||||
|
const pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH)
|
||||||
|
|
||||||
|
if (this.isDragging) {
|
||||||
|
pos.top = this.dragging.top
|
||||||
|
// Add rtl support
|
||||||
|
if (this.renderRtl) {
|
||||||
|
pos.right = this.dragging.left
|
||||||
|
} else {
|
||||||
|
pos.left = this.dragging.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.isResizing) {
|
||||||
|
pos.width = this.resizing.width
|
||||||
|
pos.height = this.resizing.height
|
||||||
|
}
|
||||||
|
|
||||||
|
let style
|
||||||
|
// CSS Transforms support (default)
|
||||||
|
if (this.useCssTransforms) {
|
||||||
|
// Add rtl support
|
||||||
|
if (this.renderRtl) {
|
||||||
|
style = setTransformRtl(pos.top, pos.right, pos.width, pos.height)
|
||||||
|
} else {
|
||||||
|
style = setTransform(pos.top, pos.left, pos.width, pos.height)
|
||||||
|
}
|
||||||
|
} else { // top,left (slow)
|
||||||
|
// Add rtl support
|
||||||
|
if (this.renderRtl) {
|
||||||
|
style = setTopRight(pos.top, pos.right, pos.width, pos.height)
|
||||||
|
} else {
|
||||||
|
style = setTopLeft(pos.top, pos.left, pos.width, pos.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.style = style
|
||||||
|
},
|
||||||
|
emitContainerResized () {
|
||||||
|
// this.style has width and height with trailing 'px'. The
|
||||||
|
// resized event is without them
|
||||||
|
const styleProps = {}
|
||||||
|
for (const prop of ['width', 'height']) {
|
||||||
|
const val = this.style[prop]
|
||||||
|
const matches = val.match(/^(\d+)px$/)
|
||||||
|
if (!matches) { return }
|
||||||
|
styleProps[prop] = matches[1]
|
||||||
|
}
|
||||||
|
this.$emit('container-resized', this.i, this.h, this.w, styleProps.height, styleProps.width)
|
||||||
|
},
|
||||||
|
handleResize: function (event) {
|
||||||
|
if (this.static) return
|
||||||
|
const position = getControlPosition(event)
|
||||||
|
// Get the current drag point from the event. This is used as the offset.
|
||||||
|
if (position == null) return // not possible but satisfies flow
|
||||||
|
const { x, y } = position
|
||||||
|
|
||||||
|
const newSize = { width: 0, height: 0 }
|
||||||
|
let pos
|
||||||
|
switch (event.type) {
|
||||||
|
case 'resizestart': {
|
||||||
|
this.previousW = this.innerW
|
||||||
|
this.previousH = this.innerH
|
||||||
|
pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH)
|
||||||
|
newSize.width = pos.width
|
||||||
|
newSize.height = pos.height
|
||||||
|
this.resizing = newSize
|
||||||
|
this.isResizing = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'resizemove': {
|
||||||
|
// console.log("### resize => " + event.type + ", lastW=" + this.lastW + ", lastH=" + this.lastH);
|
||||||
|
const coreEvent = createCoreData(this.lastW, this.lastH, x, y)
|
||||||
|
if (this.renderRtl) {
|
||||||
|
newSize.width = this.resizing.width - coreEvent.deltaX
|
||||||
|
} else {
|
||||||
|
newSize.width = this.resizing.width + coreEvent.deltaX
|
||||||
|
}
|
||||||
|
newSize.height = this.resizing.height + coreEvent.deltaY
|
||||||
|
|
||||||
|
/// console.log("### resize => " + event.type + ", deltaX=" + coreEvent.deltaX + ", deltaY=" + coreEvent.deltaY);
|
||||||
|
this.resizing = newSize
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'resizeend': {
|
||||||
|
// console.log("### resize end => x=" +this.innerX + " y=" + this.innerY + " w=" + this.innerW + " h=" + this.innerH);
|
||||||
|
pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH)
|
||||||
|
newSize.width = pos.width
|
||||||
|
newSize.height = pos.height
|
||||||
|
// console.log("### resize end => " + JSON.stringify(newSize));
|
||||||
|
this.resizing = null
|
||||||
|
this.isResizing = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new WH
|
||||||
|
pos = this.calcWH(newSize.height, newSize.width)
|
||||||
|
if (pos.w < this.minW) {
|
||||||
|
pos.w = this.minW
|
||||||
|
}
|
||||||
|
if (pos.w > this.maxW) {
|
||||||
|
pos.w = this.maxW
|
||||||
|
}
|
||||||
|
if (pos.h < this.minH) {
|
||||||
|
pos.h = this.minH
|
||||||
|
}
|
||||||
|
if (pos.h > this.maxH) {
|
||||||
|
pos.h = this.maxH
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.h < 1) {
|
||||||
|
pos.h = 1
|
||||||
|
}
|
||||||
|
if (pos.w < 1) {
|
||||||
|
pos.w = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastW = x
|
||||||
|
this.lastH = y
|
||||||
|
|
||||||
|
if (this.innerW !== pos.w || this.innerH !== pos.h) {
|
||||||
|
this.$emit('resize', this.i, pos.h, pos.w, newSize.height, newSize.width)
|
||||||
|
}
|
||||||
|
if (event.type === 'resizeend' && (this.previousW !== this.innerW || this.previousH !== this.innerH)) {
|
||||||
|
this.$emit('resized', this.i, pos.h, pos.w, newSize.height, newSize.width)
|
||||||
|
}
|
||||||
|
this.eventBus.$emit('resizeEvent', event.type, this.i, this.innerX, this.innerY, pos.h, pos.w)
|
||||||
|
},
|
||||||
|
handleDrag (event) {
|
||||||
|
if (this.static) return
|
||||||
|
if (this.isResizing) return
|
||||||
|
|
||||||
|
const position = getControlPosition(event)
|
||||||
|
|
||||||
|
// Get the current drag point from the event. This is used as the offset.
|
||||||
|
if (position === null) return // not possible but satisfies flow
|
||||||
|
const { x, y } = position
|
||||||
|
|
||||||
|
// let shouldUpdate = false;
|
||||||
|
const newPosition = { top: 0, left: 0 }
|
||||||
|
switch (event.type) {
|
||||||
|
case 'dragstart': {
|
||||||
|
this.previousX = this.innerX
|
||||||
|
this.previousY = this.innerY
|
||||||
|
|
||||||
|
const parentRect = event.target.offsetParent.getBoundingClientRect()
|
||||||
|
const clientRect = event.target.getBoundingClientRect()
|
||||||
|
if (this.renderRtl) {
|
||||||
|
newPosition.left = (clientRect.right - parentRect.right) * -1
|
||||||
|
} else {
|
||||||
|
newPosition.left = clientRect.left - parentRect.left
|
||||||
|
}
|
||||||
|
newPosition.top = clientRect.top - parentRect.top
|
||||||
|
this.dragging = newPosition
|
||||||
|
this.isDragging = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'dragend': {
|
||||||
|
if (!this.isDragging) return
|
||||||
|
const parentRect = event.target.offsetParent.getBoundingClientRect()
|
||||||
|
const clientRect = event.target.getBoundingClientRect()
|
||||||
|
// Add rtl support
|
||||||
|
if (this.renderRtl) {
|
||||||
|
newPosition.left = (clientRect.right - parentRect.right) * -1
|
||||||
|
} else {
|
||||||
|
newPosition.left = clientRect.left - parentRect.left
|
||||||
|
}
|
||||||
|
newPosition.top = clientRect.top - parentRect.top
|
||||||
|
// console.log("### drag end => " + JSON.stringify(newPosition));
|
||||||
|
// console.log("### DROP: " + JSON.stringify(newPosition));
|
||||||
|
this.dragging = null
|
||||||
|
this.isDragging = false
|
||||||
|
// shouldUpdate = true;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'dragmove': {
|
||||||
|
const coreEvent = createCoreData(this.lastX, this.lastY, x, y)
|
||||||
|
// Add rtl support
|
||||||
|
if (this.renderRtl) {
|
||||||
|
newPosition.left = this.dragging.left - coreEvent.deltaX
|
||||||
|
} else {
|
||||||
|
newPosition.left = this.dragging.left + coreEvent.deltaX
|
||||||
|
}
|
||||||
|
newPosition.top = this.dragging.top + coreEvent.deltaY
|
||||||
|
// console.log("### drag => " + event.type + ", x=" + x + ", y=" + y);
|
||||||
|
// console.log("### drag => " + event.type + ", deltaX=" + coreEvent.deltaX + ", deltaY=" + coreEvent.deltaY);
|
||||||
|
// console.log("### drag end => " + JSON.stringify(newPosition));
|
||||||
|
this.dragging = newPosition
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new XY
|
||||||
|
let pos
|
||||||
|
if (this.renderRtl) {
|
||||||
|
pos = this.calcXY(newPosition.top, newPosition.left)
|
||||||
|
} else {
|
||||||
|
pos = this.calcXY(newPosition.top, newPosition.left)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastX = x
|
||||||
|
this.lastY = y
|
||||||
|
|
||||||
|
if (this.innerX !== pos.x || this.innerY !== pos.y) {
|
||||||
|
this.$emit('move', this.i, pos.x, pos.y)
|
||||||
|
}
|
||||||
|
if (event.type === 'dragend' && (this.previousX !== this.innerX || this.previousY !== this.innerY)) {
|
||||||
|
this.$emit('moved', this.i, pos.x, pos.y)
|
||||||
|
}
|
||||||
|
this.eventBus.$emit('dragEvent', event.type, this.i, pos.x, pos.y, this.innerH, this.innerW)
|
||||||
|
},
|
||||||
|
calcPosition: function (x, y, w, h) {
|
||||||
|
const colWidth = this.calcColWidth()
|
||||||
|
// add rtl support
|
||||||
|
let out
|
||||||
|
if (this.renderRtl) {
|
||||||
|
out = {
|
||||||
|
right: Math.round(colWidth * x + (x + 1) * this.margin[0]),
|
||||||
|
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
|
||||||
|
// 0 * Infinity === NaN, which causes problems with resize constriants;
|
||||||
|
// Fix this if it occurs.
|
||||||
|
// Note we do it here rather than later because Math.round(Infinity) causes deopt
|
||||||
|
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
|
||||||
|
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = {
|
||||||
|
left: Math.round(colWidth * x + (x + 1) * this.margin[0]),
|
||||||
|
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
|
||||||
|
// 0 * Infinity === NaN, which causes problems with resize constriants;
|
||||||
|
// Fix this if it occurs.
|
||||||
|
// Note we do it here rather than later because Math.round(Infinity) causes deopt
|
||||||
|
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
|
||||||
|
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Translate x and y coordinates from pixels to grid units.
|
||||||
|
* @param {Number} top Top position (relative to parent) in pixels.
|
||||||
|
* @param {Number} left Left position (relative to parent) in pixels.
|
||||||
|
* @return {Object} x and y in grid units.
|
||||||
|
*/
|
||||||
|
// TODO check if this function needs change in order to support rtl.
|
||||||
|
calcXY (top, left) {
|
||||||
|
const colWidth = this.calcColWidth()
|
||||||
|
|
||||||
|
// left = colWidth * x + margin * (x + 1)
|
||||||
|
// l = cx + m(x+1)
|
||||||
|
// l = cx + mx + m
|
||||||
|
// l - m = cx + mx
|
||||||
|
// l - m = x(c + m)
|
||||||
|
// (l - m) / (c + m) = x
|
||||||
|
// x = (left - margin) / (coldWidth + margin)
|
||||||
|
let x = Math.round((left - this.margin[0]) / (colWidth + this.margin[0]))
|
||||||
|
let y = Math.round((top - this.margin[1]) / (this.rowHeight + this.margin[1]))
|
||||||
|
|
||||||
|
// Capping
|
||||||
|
x = Math.max(Math.min(x, this.cols - this.innerW), 0)
|
||||||
|
y = Math.max(Math.min(y, this.maxRows - this.innerH), 0)
|
||||||
|
|
||||||
|
return { x, y }
|
||||||
|
},
|
||||||
|
// Helper for generating column width
|
||||||
|
calcColWidth () {
|
||||||
|
const colWidth = (this.containerWidth - (this.margin[0] * (this.cols + 1))) / this.cols
|
||||||
|
// console.log("### COLS=" + this.cols + " COL WIDTH=" + colWidth + " MARGIN " + this.margin[0]);
|
||||||
|
return colWidth
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a height and width in pixel values, calculate grid units.
|
||||||
|
* @param {Number} height Height in pixels.
|
||||||
|
* @param {Number} width Width in pixels.
|
||||||
|
* @return {Object} w, h as grid units.
|
||||||
|
*/
|
||||||
|
calcWH (height, width) {
|
||||||
|
const colWidth = this.calcColWidth()
|
||||||
|
|
||||||
|
// width = colWidth * w - (margin * (w - 1))
|
||||||
|
// ...
|
||||||
|
// w = (width + margin) / (colWidth + margin)
|
||||||
|
let w = Math.round((width + this.margin[0]) / (colWidth + this.margin[0]))
|
||||||
|
let h = Math.round((height + this.margin[1]) / (this.rowHeight + this.margin[1]))
|
||||||
|
|
||||||
|
// Capping
|
||||||
|
w = Math.max(Math.min(w, this.cols - this.innerX), 0)
|
||||||
|
h = Math.max(Math.min(h, this.maxRows - this.innerY), 0)
|
||||||
|
return { w, h }
|
||||||
|
},
|
||||||
|
updateWidth: function (width, colNum) {
|
||||||
|
this.containerWidth = width
|
||||||
|
if (colNum !== undefined && colNum !== null) {
|
||||||
|
this.cols = colNum
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compact: function () {
|
||||||
|
this.createStyle()
|
||||||
|
},
|
||||||
|
tryMakeDraggable: function () {
|
||||||
|
const self = this
|
||||||
|
if (this.interactObj === null || this.interactObj === undefined) {
|
||||||
|
this.interactObj = interact(this.$refs.item)
|
||||||
|
if (!this.useStyleCursor) {
|
||||||
|
this.interactObj.styleCursor(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.draggable && !this.static) {
|
||||||
|
const opts = {
|
||||||
|
ignoreFrom: this.dragIgnoreFrom,
|
||||||
|
allowFrom: this.dragAllowFrom
|
||||||
|
}
|
||||||
|
this.interactObj.draggable(opts)
|
||||||
|
/* this.interactObj.draggable({allowFrom: '.vue-draggable-handle'}); */
|
||||||
|
if (!this.dragEventSet) {
|
||||||
|
this.dragEventSet = true
|
||||||
|
this.interactObj.on('dragstart dragmove dragend', function (event) {
|
||||||
|
self.handleDrag(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.interactObj.draggable({
|
||||||
|
enabled: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tryMakeResizable: function () {
|
||||||
|
const self = this
|
||||||
|
if (this.interactObj === null || this.interactObj === undefined) {
|
||||||
|
this.interactObj = interact(this.$refs.item)
|
||||||
|
if (!this.useStyleCursor) {
|
||||||
|
this.interactObj.styleCursor(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.resizable && !this.static) {
|
||||||
|
const maximum = this.calcPosition(0, 0, this.maxW, this.maxH)
|
||||||
|
const minimum = this.calcPosition(0, 0, this.minW, this.minH)
|
||||||
|
|
||||||
|
// console.log("### MAX " + JSON.stringify(maximum));
|
||||||
|
// console.log("### MIN " + JSON.stringify(minimum));
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
preserveAspectRatio: true,
|
||||||
|
// allowFrom: "." + this.resizableHandleClass.trim().replace(" ", "."),
|
||||||
|
edges: {
|
||||||
|
left: false,
|
||||||
|
right: '.' + this.resizableHandleClass.trim().replace(' ', '.'),
|
||||||
|
bottom: '.' + this.resizableHandleClass.trim().replace(' ', '.'),
|
||||||
|
top: false
|
||||||
|
},
|
||||||
|
ignoreFrom: this.resizeIgnoreFrom,
|
||||||
|
restrictSize: {
|
||||||
|
min: {
|
||||||
|
height: minimum.height,
|
||||||
|
width: minimum.width
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
height: maximum.height,
|
||||||
|
width: maximum.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.interactObj.resizable(opts)
|
||||||
|
if (!this.resizeEventSet) {
|
||||||
|
this.resizeEventSet = true
|
||||||
|
this.interactObj
|
||||||
|
.on('resizestart resizemove resizeend', function (event) {
|
||||||
|
self.handleResize(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.interactObj.resizable({
|
||||||
|
enabled: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoSize: function () {
|
||||||
|
// ok here we want to calculate if a resize is needed
|
||||||
|
this.previousW = this.innerW
|
||||||
|
this.previousH = this.innerH
|
||||||
|
|
||||||
|
const newSize = this.$slots.default[0].elm.getBoundingClientRect()
|
||||||
|
const pos = this.calcWH(newSize.height, newSize.width)
|
||||||
|
if (pos.w < this.minW) {
|
||||||
|
pos.w = this.minW
|
||||||
|
}
|
||||||
|
if (pos.w > this.maxW) {
|
||||||
|
pos.w = this.maxW
|
||||||
|
}
|
||||||
|
if (pos.h < this.minH) {
|
||||||
|
pos.h = this.minH
|
||||||
|
}
|
||||||
|
if (pos.h > this.maxH) {
|
||||||
|
pos.h = this.maxH
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.h < 1) {
|
||||||
|
pos.h = 1
|
||||||
|
}
|
||||||
|
if (pos.w < 1) {
|
||||||
|
pos.w = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.lastW = x; // basically, this is copied from resizehandler, but shouldn't be needed
|
||||||
|
// this.lastH = y;
|
||||||
|
|
||||||
|
if (this.innerW !== pos.w || this.innerH !== pos.h) {
|
||||||
|
this.$emit('resize', this.i, pos.h, pos.w, newSize.height, newSize.width)
|
||||||
|
}
|
||||||
|
if (this.previousW !== pos.w || this.previousH !== pos.h) {
|
||||||
|
this.$emit('resized', this.i, pos.h, pos.w, newSize.height, newSize.width)
|
||||||
|
this.eventBus.$emit('resizeEvent', 'resizeend', this.i, this.innerX, this.innerY, pos.h, pos.w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
462
nezha-fronted/src/components/chart/chart/grid/GridLayout.vue
Normal file
462
nezha-fronted/src/components/chart/chart/grid/GridLayout.vue
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="item" class="vue-grid-layout" :style="mergedStyle">
|
||||||
|
<slot></slot>
|
||||||
|
<grid-item class="vue-grid-placeholder"
|
||||||
|
v-show="isDragging"
|
||||||
|
:x="placeholder.x"
|
||||||
|
:y="placeholder.y"
|
||||||
|
:w="placeholder.w"
|
||||||
|
:h="placeholder.h"
|
||||||
|
:i="placeholder.i"></grid-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.vue-grid-layout {
|
||||||
|
position: relative;
|
||||||
|
transition: height 200ms ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import { bottom, compact, getLayoutItem, moveElement, validateLayout, cloneLayout, getAllCollisions } from './utils'
|
||||||
|
import { getBreakpointFromWidth, getColsFromBreakpoint, findOrGenerateResponsiveLayout } from './responsiveUtils'
|
||||||
|
// var eventBus = require('./eventBus');
|
||||||
|
|
||||||
|
import GridItem from './GridItem.vue'
|
||||||
|
import { addWindowEventListener, removeWindowEventListener } from './DOM'
|
||||||
|
const elementResizeDetectorMaker = require('element-resize-detector')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GridLayout',
|
||||||
|
provide () {
|
||||||
|
return {
|
||||||
|
eventBus: null,
|
||||||
|
layout: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
GridItem
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// If true, the container height swells and contracts to fit contents
|
||||||
|
autoSize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
colNum: {
|
||||||
|
type: Number,
|
||||||
|
default: 12
|
||||||
|
},
|
||||||
|
rowHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 150
|
||||||
|
},
|
||||||
|
maxRows: {
|
||||||
|
type: Number,
|
||||||
|
default: Infinity
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: Array,
|
||||||
|
default: function () {
|
||||||
|
return [10, 10]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isDraggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
isResizable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
isMirrored: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
useCssTransforms: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
verticalCompact: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
responsive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
responsiveLayouts: {
|
||||||
|
type: Object,
|
||||||
|
default: function () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
type: Object,
|
||||||
|
default: function () { return { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 } }
|
||||||
|
},
|
||||||
|
cols: {
|
||||||
|
type: Object,
|
||||||
|
default: function () { return { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 } }
|
||||||
|
},
|
||||||
|
preventCollision: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
useStyleCursor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
width: null,
|
||||||
|
mergedStyle: {},
|
||||||
|
lastLayoutLength: 0,
|
||||||
|
isDragging: false,
|
||||||
|
placeholder: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 0,
|
||||||
|
h: 0,
|
||||||
|
i: -1
|
||||||
|
},
|
||||||
|
layouts: {}, // array to store all layouts from different breakpoints
|
||||||
|
lastBreakpoint: null, // store last active breakpoint
|
||||||
|
originalLayout: null // store original Layout
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
const self = this
|
||||||
|
|
||||||
|
// Accessible refernces of functions for removing in beforeDestroy
|
||||||
|
self.resizeEventHandler = function (eventType, i, x, y, h, w) {
|
||||||
|
self.resizeEvent(eventType, i, x, y, h, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dragEventHandler = function (eventType, i, x, y, h, w) {
|
||||||
|
self.dragEvent(eventType, i, x, y, h, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._provided.eventBus = new Vue()
|
||||||
|
self.eventBus = self._provided.eventBus
|
||||||
|
self.eventBus.$on('resizeEvent', self.resizeEventHandler)
|
||||||
|
self.eventBus.$on('dragEvent', self.dragEventHandler)
|
||||||
|
self.$emit('layout-created', self.layout)
|
||||||
|
},
|
||||||
|
beforeDestroy: function () {
|
||||||
|
// Remove listeners
|
||||||
|
this.eventBus.$off('resizeEvent', this.resizeEventHandler)
|
||||||
|
this.eventBus.$off('dragEvent', this.dragEventHandler)
|
||||||
|
this.eventBus.$destroy()
|
||||||
|
removeWindowEventListener('resize', this.onWindowResize)
|
||||||
|
this.erd.uninstall(this.$refs.item)
|
||||||
|
},
|
||||||
|
beforeMount: function () {
|
||||||
|
this.$emit('layout-before-mount', this.layout)
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.$emit('layout-mounted', this.layout)
|
||||||
|
this.$nextTick(function () {
|
||||||
|
validateLayout(this.layout)
|
||||||
|
|
||||||
|
this.originalLayout = this.layout
|
||||||
|
const self = this
|
||||||
|
this.$nextTick(function () {
|
||||||
|
self.onWindowResize()
|
||||||
|
|
||||||
|
self.initResponsiveFeatures()
|
||||||
|
|
||||||
|
// self.width = self.$el.offsetWidth;
|
||||||
|
addWindowEventListener('resize', self.onWindowResize)
|
||||||
|
|
||||||
|
compact(self.layout, self.verticalCompact)
|
||||||
|
|
||||||
|
self.$emit('layout-updated', self.layout)
|
||||||
|
|
||||||
|
self.updateHeight()
|
||||||
|
self.$nextTick(function () {
|
||||||
|
this.erd = elementResizeDetectorMaker({
|
||||||
|
strategy: 'scroll', // <- For ultra performance.
|
||||||
|
// See https://github.com/wnr/element-resize-detector/issues/110 about callOnAdd.
|
||||||
|
callOnAdd: false
|
||||||
|
})
|
||||||
|
this.erd.listenTo(self.$refs.item, function () {
|
||||||
|
self.onWindowResize()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
width: function (newval, oldval) {
|
||||||
|
const self = this
|
||||||
|
this.$nextTick(function () {
|
||||||
|
// this.$broadcast("updateWidth", this.width);
|
||||||
|
this.eventBus.$emit('updateWidth', this.width)
|
||||||
|
if (oldval === null) {
|
||||||
|
/*
|
||||||
|
If oldval == null is when the width has never been
|
||||||
|
set before. That only occurs when mouting is
|
||||||
|
finished, and onWindowResize has been called and
|
||||||
|
this.width has been changed the first time after it
|
||||||
|
got set to null in the constructor. It is now time
|
||||||
|
to issue layout-ready events as the GridItems have
|
||||||
|
their sizes configured properly.
|
||||||
|
|
||||||
|
The reason for emitting the layout-ready events on
|
||||||
|
the next tick is to allow for the newly-emitted
|
||||||
|
updateWidth event (above) to have reached the
|
||||||
|
children GridItem-s and had their effect, so we're
|
||||||
|
sure that they have the final size before we emit
|
||||||
|
layout-ready (for this GridLayout) and
|
||||||
|
item-layout-ready (for the GridItem-s).
|
||||||
|
|
||||||
|
This way any client event handlers can reliably
|
||||||
|
invistigate stable sizes of GridItem-s.
|
||||||
|
*/
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('layout-ready', self.layout)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.updateHeight()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
layout: function () {
|
||||||
|
this.layoutUpdate()
|
||||||
|
},
|
||||||
|
colNum: function (val) {
|
||||||
|
this.eventBus.$emit('setColNum', val)
|
||||||
|
},
|
||||||
|
rowHeight: function () {
|
||||||
|
this.eventBus.$emit('setRowHeight', this.rowHeight)
|
||||||
|
},
|
||||||
|
isDraggable: function () {
|
||||||
|
this.eventBus.$emit('setDraggable', this.isDraggable)
|
||||||
|
},
|
||||||
|
isResizable: function () {
|
||||||
|
this.eventBus.$emit('setResizable', this.isResizable)
|
||||||
|
},
|
||||||
|
responsive () {
|
||||||
|
if (!this.responsive) {
|
||||||
|
this.$emit('update:layout', this.originalLayout)
|
||||||
|
this.eventBus.$emit('setColNum', this.colNum)
|
||||||
|
}
|
||||||
|
this.onWindowResize()
|
||||||
|
},
|
||||||
|
maxRows: function () {
|
||||||
|
this.eventBus.$emit('setMaxRows', this.maxRows)
|
||||||
|
},
|
||||||
|
margin () {
|
||||||
|
this.updateHeight()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
layoutUpdate () {
|
||||||
|
if (this.layout !== undefined && this.originalLayout !== null) {
|
||||||
|
if (this.layout.length !== this.originalLayout.length) {
|
||||||
|
// console.log("### LAYOUT UPDATE!", this.layout.length, this.originalLayout.length);
|
||||||
|
|
||||||
|
const diff = this.findDifference(this.layout, this.originalLayout)
|
||||||
|
if (diff.length > 0) {
|
||||||
|
// console.log(diff);
|
||||||
|
if (this.layout.length > this.originalLayout.length) {
|
||||||
|
this.originalLayout = this.originalLayout.concat(diff)
|
||||||
|
} else {
|
||||||
|
this.originalLayout = this.originalLayout.filter(obj => {
|
||||||
|
return !diff.some(obj2 => {
|
||||||
|
return obj.i === obj2.i
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastLayoutLength = this.layout.length
|
||||||
|
this.initResponsiveFeatures()
|
||||||
|
}
|
||||||
|
|
||||||
|
compact(this.layout, this.verticalCompact)
|
||||||
|
this.eventBus.$emit('updateWidth', this.width)
|
||||||
|
this.updateHeight()
|
||||||
|
|
||||||
|
this.$emit('layout-updated', this.layout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateHeight: function () {
|
||||||
|
this.mergedStyle = {
|
||||||
|
height: this.containerHeight()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onWindowResize: function () {
|
||||||
|
if (this.$refs !== null && this.$refs.item !== null && this.$refs.item !== undefined) {
|
||||||
|
this.width = this.$refs.item.offsetWidth
|
||||||
|
}
|
||||||
|
this.eventBus.$emit('resizeEvent')
|
||||||
|
},
|
||||||
|
containerHeight: function () {
|
||||||
|
if (!this.autoSize) return
|
||||||
|
// console.log("bottom: " + bottom(this.layout))
|
||||||
|
// console.log("rowHeight + margins: " + (this.rowHeight + this.margin[1]) + this.margin[1])
|
||||||
|
const containerHeight = bottom(this.layout) * (this.rowHeight + this.margin[1]) + this.margin[1] + 'px'
|
||||||
|
return containerHeight
|
||||||
|
},
|
||||||
|
dragEvent: function (eventName, id, x, y, h, w) {
|
||||||
|
// console.log(eventName + " id=" + id + ", x=" + x + ", y=" + y);
|
||||||
|
let l = getLayoutItem(this.layout, id)
|
||||||
|
// GetLayoutItem sometimes returns null object
|
||||||
|
if (l === undefined || l === null) {
|
||||||
|
l = { x: 0, y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventName === 'dragmove' || eventName === 'dragstart') {
|
||||||
|
this.placeholder.i = id
|
||||||
|
this.placeholder.x = l.x
|
||||||
|
this.placeholder.y = l.y
|
||||||
|
this.placeholder.w = w
|
||||||
|
this.placeholder.h = h
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.isDragging = true
|
||||||
|
})
|
||||||
|
// this.$broadcast("updateWidth", this.width);
|
||||||
|
this.eventBus.$emit('updateWidth', this.width)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.isDragging = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the element to the dragged location.
|
||||||
|
this.layout = moveElement(this.layout, l, x, y, true, this.preventCollision)
|
||||||
|
compact(this.layout, this.verticalCompact)
|
||||||
|
// needed because vue can't detect changes on array element properties
|
||||||
|
this.eventBus.$emit('compact')
|
||||||
|
this.updateHeight()
|
||||||
|
if (eventName === 'dragend') this.$emit('layout-updated', this.layout)
|
||||||
|
},
|
||||||
|
resizeEvent: function (eventName, id, x, y, h, w) {
|
||||||
|
let l = getLayoutItem(this.layout, id)
|
||||||
|
// GetLayoutItem sometimes return null object
|
||||||
|
if (l === undefined || l === null) {
|
||||||
|
l = { h: 0, w: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasCollisions
|
||||||
|
if (this.preventCollision) {
|
||||||
|
const collisions = getAllCollisions(this.layout, { ...l, w, h }).filter(
|
||||||
|
layoutItem => layoutItem.i !== l.i
|
||||||
|
)
|
||||||
|
hasCollisions = collisions.length > 0
|
||||||
|
|
||||||
|
// If we're colliding, we need adjust the placeholder.
|
||||||
|
if (hasCollisions) {
|
||||||
|
// adjust w && h to maximum allowed space
|
||||||
|
let leastX = Infinity
|
||||||
|
let leastY = Infinity
|
||||||
|
collisions.forEach(layoutItem => {
|
||||||
|
if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x)
|
||||||
|
if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Number.isFinite(leastX)) l.w = leastX - l.x
|
||||||
|
if (Number.isFinite(leastY)) l.h = leastY - l.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCollisions) {
|
||||||
|
// Set new width and height.
|
||||||
|
l.w = w
|
||||||
|
l.h = h
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventName === 'resizestart' || eventName === 'resizemove') {
|
||||||
|
this.placeholder.i = id
|
||||||
|
this.placeholder.x = x
|
||||||
|
this.placeholder.y = y
|
||||||
|
this.placeholder.w = l.w
|
||||||
|
this.placeholder.h = l.h
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.isDragging = true
|
||||||
|
})
|
||||||
|
// this.$broadcast("updateWidth", this.width);
|
||||||
|
this.eventBus.$emit('updateWidth', this.width)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.isDragging = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.responsive) this.responsiveGridLayout()
|
||||||
|
|
||||||
|
compact(this.layout, this.verticalCompact)
|
||||||
|
this.eventBus.$emit('compact')
|
||||||
|
this.updateHeight()
|
||||||
|
|
||||||
|
if (eventName === 'resizeend') this.$emit('layout-updated', this.layout)
|
||||||
|
},
|
||||||
|
|
||||||
|
// finds or generates new layouts for set breakpoints
|
||||||
|
responsiveGridLayout () {
|
||||||
|
const newBreakpoint = getBreakpointFromWidth(this.breakpoints, this.width)
|
||||||
|
const newCols = getColsFromBreakpoint(newBreakpoint, this.cols)
|
||||||
|
|
||||||
|
// save actual layout in layouts
|
||||||
|
if (this.lastBreakpoint != null && !this.layouts[this.lastBreakpoint]) { this.layouts[this.lastBreakpoint] = cloneLayout(this.layout) }
|
||||||
|
|
||||||
|
// Find or generate a new layout.
|
||||||
|
const layout = findOrGenerateResponsiveLayout(
|
||||||
|
this.originalLayout,
|
||||||
|
this.layouts,
|
||||||
|
this.breakpoints,
|
||||||
|
newBreakpoint,
|
||||||
|
this.lastBreakpoint,
|
||||||
|
newCols,
|
||||||
|
this.verticalCompact
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store the new layout.
|
||||||
|
this.layouts[newBreakpoint] = layout
|
||||||
|
|
||||||
|
if (this.lastBreakpoint !== newBreakpoint) {
|
||||||
|
this.$emit('breakpoint-changed', newBreakpoint, layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// new prop sync
|
||||||
|
this.$emit('update:layout', layout)
|
||||||
|
|
||||||
|
this.lastBreakpoint = newBreakpoint
|
||||||
|
this.eventBus.$emit('setColNum', getColsFromBreakpoint(newBreakpoint, this.cols))
|
||||||
|
},
|
||||||
|
|
||||||
|
// clear all responsive layouts
|
||||||
|
initResponsiveFeatures () {
|
||||||
|
// clear layouts
|
||||||
|
this.layouts = Object.assign({}, this.responsiveLayouts)
|
||||||
|
},
|
||||||
|
|
||||||
|
// find difference in layouts
|
||||||
|
findDifference (layout, originalLayout) {
|
||||||
|
// Find values that are in result1 but not in result2
|
||||||
|
const uniqueResultOne = layout.filter(function (obj) {
|
||||||
|
return !originalLayout.some(function (obj2) {
|
||||||
|
return obj.i === obj2.i
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find values that are in result2 but not in result1
|
||||||
|
const uniqueResultTwo = originalLayout.filter(function (obj) {
|
||||||
|
return !layout.some(function (obj2) {
|
||||||
|
return obj.i === obj2.i
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Combine the two arrays of unique entries#
|
||||||
|
return uniqueResultOne.concat(uniqueResultTwo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<span class="text">
|
||||||
|
{{text}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "TestElement",
|
||||||
|
props: {
|
||||||
|
text : {
|
||||||
|
type: String,
|
||||||
|
default: "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function() {
|
||||||
|
console.log("### " + this.text + " ready!");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
147
nezha-fronted/src/components/chart/chart/grid/aspectRatio.js
Normal file
147
nezha-fronted/src/components/chart/chart/grid/aspectRatio.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* @module modifiers/aspectRatio
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* This module forces elements to be resized with a specified dx/dy ratio.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* interact(target).resizable({
|
||||||
|
* modifiers: [
|
||||||
|
* interact.modifiers.snapSize({
|
||||||
|
* targets: [ interact.snappers.grid({ x: 20, y: 20 }) ],
|
||||||
|
* }),
|
||||||
|
* interact.aspectRatio({ ratio: 'preserve' }),
|
||||||
|
* ],
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
import extend from "../utils/extend.js";
|
||||||
|
import { addEdges } from "../utils/rect.js";
|
||||||
|
import Modification from "./Modification.js";
|
||||||
|
import { makeModifier } from "./base.js";
|
||||||
|
const aspectRatio = {
|
||||||
|
start(arg) {
|
||||||
|
const {
|
||||||
|
state,
|
||||||
|
rect,
|
||||||
|
edges: originalEdges,
|
||||||
|
pageCoords: coords
|
||||||
|
} = arg;
|
||||||
|
let {
|
||||||
|
ratio
|
||||||
|
} = state.options;
|
||||||
|
const {
|
||||||
|
equalDelta,
|
||||||
|
modifiers
|
||||||
|
} = state.options;
|
||||||
|
|
||||||
|
if (ratio === 'preserve') {
|
||||||
|
ratio = rect.width / rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.startCoords = extend({}, coords);
|
||||||
|
state.startRect = extend({}, rect);
|
||||||
|
state.ratio = ratio;
|
||||||
|
state.equalDelta = equalDelta;
|
||||||
|
const linkedEdges = state.linkedEdges = {
|
||||||
|
top: originalEdges.top || originalEdges.left && !originalEdges.bottom,
|
||||||
|
left: originalEdges.left || originalEdges.top && !originalEdges.right,
|
||||||
|
bottom: originalEdges.bottom || originalEdges.right && !originalEdges.top,
|
||||||
|
right: originalEdges.right || originalEdges.bottom && !originalEdges.left
|
||||||
|
};
|
||||||
|
state.xIsPrimaryAxis = !!(originalEdges.left || originalEdges.right);
|
||||||
|
|
||||||
|
if (state.equalDelta) {
|
||||||
|
state.edgeSign = (linkedEdges.left ? 1 : -1) * (linkedEdges.top ? 1 : -1);
|
||||||
|
} else {
|
||||||
|
const negativeSecondaryEdge = state.xIsPrimaryAxis ? linkedEdges.top : linkedEdges.left;
|
||||||
|
state.edgeSign = negativeSecondaryEdge ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(arg.edges, linkedEdges);
|
||||||
|
if (!modifiers || !modifiers.length) return;
|
||||||
|
const subModification = new Modification(arg.interaction);
|
||||||
|
subModification.copyFrom(arg.interaction.modification);
|
||||||
|
subModification.prepareStates(modifiers);
|
||||||
|
state.subModification = subModification;
|
||||||
|
subModification.startAll({ ...arg
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
set(arg) {
|
||||||
|
const {
|
||||||
|
state,
|
||||||
|
rect,
|
||||||
|
coords
|
||||||
|
} = arg;
|
||||||
|
const initialCoords = extend({}, coords);
|
||||||
|
const aspectMethod = state.equalDelta ? setEqualDelta : setRatio;
|
||||||
|
aspectMethod(state, state.xIsPrimaryAxis, coords, rect);
|
||||||
|
|
||||||
|
if (!state.subModification) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const correctedRect = extend({}, rect);
|
||||||
|
addEdges(state.linkedEdges, correctedRect, {
|
||||||
|
x: coords.x - initialCoords.x,
|
||||||
|
y: coords.y - initialCoords.y
|
||||||
|
});
|
||||||
|
const result = state.subModification.setAll({ ...arg,
|
||||||
|
rect: correctedRect,
|
||||||
|
edges: state.linkedEdges,
|
||||||
|
pageCoords: coords,
|
||||||
|
prevCoords: coords,
|
||||||
|
prevRect: correctedRect
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
delta
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
if (result.changed) {
|
||||||
|
const xIsCriticalAxis = Math.abs(delta.x) > Math.abs(delta.y); // do aspect modification again with critical edge axis as primary
|
||||||
|
|
||||||
|
aspectMethod(state, xIsCriticalAxis, result.coords, result.rect);
|
||||||
|
extend(coords, result.coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.eventProps;
|
||||||
|
},
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
ratio: 'preserve',
|
||||||
|
equalDelta: false,
|
||||||
|
modifiers: [],
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setEqualDelta({
|
||||||
|
startCoords,
|
||||||
|
edgeSign
|
||||||
|
}, xIsPrimaryAxis, coords) {
|
||||||
|
if (xIsPrimaryAxis) {
|
||||||
|
coords.y = startCoords.y + (coords.x - startCoords.x) * edgeSign;
|
||||||
|
} else {
|
||||||
|
coords.x = startCoords.x + (coords.y - startCoords.y) * edgeSign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRatio({
|
||||||
|
startRect,
|
||||||
|
startCoords,
|
||||||
|
ratio,
|
||||||
|
edgeSign
|
||||||
|
}, xIsPrimaryAxis, coords, rect) {
|
||||||
|
if (xIsPrimaryAxis) {
|
||||||
|
const newHeight = rect.width / ratio;
|
||||||
|
coords.y = startCoords.y + (newHeight - startRect.height) * edgeSign;
|
||||||
|
} else {
|
||||||
|
const newWidth = rect.height * ratio;
|
||||||
|
coords.x = startCoords.x + (newWidth - startRect.width) * edgeSign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeModifier(aspectRatio, 'aspectRatio');
|
||||||
|
export { aspectRatio };
|
||||||
|
//# sourceMappingURL=aspectRatio.js.map
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Get {x, y} positions from event.
|
||||||
|
export function getControlPosition (e) {
|
||||||
|
return offsetXYFromParentOf(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get from offsetParent
|
||||||
|
export function offsetXYFromParentOf (evt) {
|
||||||
|
const offsetParent = evt.target.offsetParent || document.body
|
||||||
|
const offsetParentRect = evt.offsetParent === document.body ? { left: 0, top: 0 } : offsetParent.getBoundingClientRect()
|
||||||
|
|
||||||
|
const x = evt.clientX + offsetParent.scrollLeft - offsetParentRect.left
|
||||||
|
const y = evt.clientY + offsetParent.scrollTop - offsetParentRect.top
|
||||||
|
|
||||||
|
/* const x = Math.round(evt.clientX + offsetParent.scrollLeft - offsetParentRect.left);
|
||||||
|
const y = Math.round(evt.clientY + offsetParent.scrollTop - offsetParentRect.top); */
|
||||||
|
|
||||||
|
return { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an data object exposed by <DraggableCore>'s events
|
||||||
|
export function createCoreData (lastX, lastY, x, y) {
|
||||||
|
// State changes are often (but not always!) async. We want the latest value.
|
||||||
|
const isStart = !isNum(lastX)
|
||||||
|
|
||||||
|
if (isStart) {
|
||||||
|
// If this is our first move, use the x and y as last coords.
|
||||||
|
return {
|
||||||
|
deltaX: 0,
|
||||||
|
deltaY: 0,
|
||||||
|
lastX: x,
|
||||||
|
lastY: y,
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise calculate proper values.
|
||||||
|
return {
|
||||||
|
deltaX: x - lastX,
|
||||||
|
deltaY: y - lastY,
|
||||||
|
lastX: lastX,
|
||||||
|
lastY: lastY,
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNum (num) {
|
||||||
|
return typeof num === 'number' && !isNaN(num)
|
||||||
|
}
|
||||||
34
nezha-fronted/src/components/chart/chart/grid/index.js
Normal file
34
nezha-fronted/src/components/chart/chart/grid/index.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import GridItem from './GridItem.vue'
|
||||||
|
import GridLayout from './GridLayout.vue'
|
||||||
|
// import ResponsiveGridLayout from './ResponsiveGridLayout.vue';
|
||||||
|
|
||||||
|
const VueGridLayout = {
|
||||||
|
// ResponsiveGridLayout,
|
||||||
|
GridLayout,
|
||||||
|
GridItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export function install (Vue) {
|
||||||
|
if (install.installed) return
|
||||||
|
install.installed = true
|
||||||
|
Object.keys(VueGridLayout).forEach(name => {
|
||||||
|
Vue.component(name, VueGridLayout[name])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
install
|
||||||
|
}
|
||||||
|
|
||||||
|
let GlobalVue = null
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
GlobalVue = window.Vue
|
||||||
|
} else if (typeof global !== 'undefined') {
|
||||||
|
GlobalVue = global.Vue
|
||||||
|
}
|
||||||
|
if (GlobalVue) {
|
||||||
|
GlobalVue.use(plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VueGridLayout
|
||||||
|
export { GridLayout, GridItem }
|
||||||
107
nezha-fronted/src/components/chart/chart/grid/responsiveUtils.js
Normal file
107
nezha-fronted/src/components/chart/chart/grid/responsiveUtils.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import { cloneLayout, compact, correctBounds, Layout } from './utils'
|
||||||
|
|
||||||
|
// export const ResponsiveLayout = {lg: Layout, md?: Layout, sm?: Layout, xs?: Layout, xxs?: Layout};
|
||||||
|
// type Breakpoint = string;
|
||||||
|
// type Breakpoints = {lg?: number, md?: number, sm?: number, xs?: number, xxs?: number};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a width, find the highest breakpoint that matches is valid for it (width > breakpoint).
|
||||||
|
*
|
||||||
|
* @param {Object} breakpoints Breakpoints object (e.g. {lg: 1200, md: 960, ...})
|
||||||
|
* @param {Number} width Screen width.
|
||||||
|
* @return {String} Highest breakpoint that is less than width.
|
||||||
|
*/
|
||||||
|
export function getBreakpointFromWidth (breakpoints, width) {
|
||||||
|
const sorted = sortBreakpoints(breakpoints)
|
||||||
|
let matching = sorted[0]
|
||||||
|
for (let i = 1, len = sorted.length; i < len; i++) {
|
||||||
|
const breakpointName = sorted[i]
|
||||||
|
if (width > breakpoints[breakpointName]) matching = breakpointName
|
||||||
|
}
|
||||||
|
return matching
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a breakpoint, get the # of cols set for it.
|
||||||
|
* @param {String} breakpoint Breakpoint name.
|
||||||
|
* @param {Object} cols Map of breakpoints to cols.
|
||||||
|
* @return {Number} Number of cols.
|
||||||
|
*/
|
||||||
|
export function getColsFromBreakpoint (breakpoint, cols) {
|
||||||
|
if (!cols[breakpoint]) {
|
||||||
|
throw new Error('ResponsiveGridLayout: `cols` entry for breakpoint ' + breakpoint + ' is missing!')
|
||||||
|
}
|
||||||
|
return cols[breakpoint]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given existing layouts and a new breakpoint, find or generate a new layout.
|
||||||
|
*
|
||||||
|
* This finds the layout above the new one and generates from it, if it exists.
|
||||||
|
*
|
||||||
|
* @param {Array} orgLayout Original layout.
|
||||||
|
* @param {Object} layouts Existing layouts.
|
||||||
|
* @param {Array} breakpoints All breakpoints.
|
||||||
|
* @param {String} breakpoint New breakpoint.
|
||||||
|
* @param {String} breakpoint Last breakpoint (for fallback).
|
||||||
|
* @param {Number} cols Column count at new breakpoint.
|
||||||
|
* @param {Boolean} verticalCompact Whether or not to compact the layout
|
||||||
|
* vertically.
|
||||||
|
* @return {Array} New layout.
|
||||||
|
*/
|
||||||
|
export function findOrGenerateResponsiveLayout (orgLayout, layouts, breakpoints,
|
||||||
|
breakpoint, lastBreakpoint,
|
||||||
|
cols, verticalCompact) {
|
||||||
|
// If it already exists, just return it.
|
||||||
|
if (layouts[breakpoint]) return cloneLayout(layouts[breakpoint])
|
||||||
|
// Find or generate the next layout
|
||||||
|
let layout = orgLayout
|
||||||
|
|
||||||
|
const breakpointsSorted = sortBreakpoints(breakpoints)
|
||||||
|
const breakpointsAbove = breakpointsSorted.slice(breakpointsSorted.indexOf(breakpoint))
|
||||||
|
for (let i = 0, len = breakpointsAbove.length; i < len; i++) {
|
||||||
|
const b = breakpointsAbove[i]
|
||||||
|
if (layouts[b]) {
|
||||||
|
layout = layouts[b]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout = cloneLayout(layout || []) // clone layout so we don't modify existing items
|
||||||
|
return compact(correctBounds(layout, { cols }), verticalCompact)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateResponsiveLayout (layout, breakpoints,
|
||||||
|
breakpoint, lastBreakpoint,
|
||||||
|
cols, verticalCompact) {
|
||||||
|
// If it already exists, just return it.
|
||||||
|
/* if (layouts[breakpoint]) return cloneLayout(layouts[breakpoint]);
|
||||||
|
// Find or generate the next layout
|
||||||
|
let layout = layouts[lastBreakpoint]; */
|
||||||
|
/* const breakpointsSorted = sortBreakpoints(breakpoints);
|
||||||
|
const breakpointsAbove = breakpointsSorted.slice(breakpointsSorted.indexOf(breakpoint));
|
||||||
|
for (let i = 0, len = breakpointsAbove.length; i < len; i++) {
|
||||||
|
const b = breakpointsAbove[i];
|
||||||
|
if (layouts[b]) {
|
||||||
|
layout = layouts[b];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
layout = cloneLayout(layout || []) // clone layout so we don't modify existing items
|
||||||
|
return compact(correctBounds(layout, { cols: cols }), verticalCompact)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given breakpoints, return an array of breakpoints sorted by width. This is usually
|
||||||
|
* e.g. ['xxs', 'xs', 'sm', ...]
|
||||||
|
*
|
||||||
|
* @param {Object} breakpoints Key/value pair of breakpoint names to widths.
|
||||||
|
* @return {Array} Sorted breakpoints.
|
||||||
|
*/
|
||||||
|
export function sortBreakpoints (breakpoints) {
|
||||||
|
const keys = Object.keys(breakpoints)
|
||||||
|
return keys.sort(function (a, b) {
|
||||||
|
return breakpoints[a] - breakpoints[b]
|
||||||
|
})
|
||||||
|
}
|
||||||
583
nezha-fronted/src/components/chart/chart/grid/utils.js
Normal file
583
nezha-fronted/src/components/chart/chart/grid/utils.js
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
// @flow
|
||||||
|
// export const LayoutItemRequired = {w: 4, h: 4, x: 0, y: 0, i: 1};
|
||||||
|
// export const LayoutItem = LayoutItemRequired &
|
||||||
|
// {minW?: number, minH?: number, maxW?: number, maxH?: number,
|
||||||
|
// moved?: boolean, static?: boolean,
|
||||||
|
// isDraggable?: ?boolean, isResizable?: ?boolean};
|
||||||
|
// export const Layout = Array<LayoutItem>
|
||||||
|
// export type Position = {left: number, top: number, width: number, height: number};
|
||||||
|
/*
|
||||||
|
export type DragCallbackData = {
|
||||||
|
node: HTMLElement,
|
||||||
|
x: number, y: number,
|
||||||
|
deltaX: number, deltaY: number,
|
||||||
|
lastX: number, lastY: number
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
// export type DragEvent = {e: Event} & DragCallbackData;
|
||||||
|
export const Size = { width: 4, height: 4 }
|
||||||
|
// export type ResizeEvent = {e: Event, node: HTMLElement, size: Size};
|
||||||
|
|
||||||
|
// const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
/**
|
||||||
|
* Return the bottom coordinate of the layout.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Layout array.
|
||||||
|
* @return {Number} Bottom coordinate.
|
||||||
|
*/
|
||||||
|
export function bottom (layout) {
|
||||||
|
let max = 0; let bottomY
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
bottomY = layout[i].y + layout[i].h
|
||||||
|
if (bottomY > max) max = bottomY
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cloneLayout (layout) {
|
||||||
|
const newLayout = Array(layout.length)
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
newLayout[i] = cloneLayoutItem(layout[i])
|
||||||
|
}
|
||||||
|
return newLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path to cloning, since this is monomorphic
|
||||||
|
export function cloneLayoutItem (layoutItem) {
|
||||||
|
/* return {
|
||||||
|
w: layoutItem.w, h: layoutItem.h, x: layoutItem.x, y: layoutItem.y, i: layoutItem.i,
|
||||||
|
minW: layoutItem.minW, maxW: layoutItem.maxW, minH: layoutItem.minH, maxH: layoutItem.maxH,
|
||||||
|
moved: Boolean(layoutItem.moved), static: Boolean(layoutItem.static),
|
||||||
|
// These can be null
|
||||||
|
isDraggable: layoutItem.isDraggable, isResizable: layoutItem.isResizable
|
||||||
|
}; */
|
||||||
|
return JSON.parse(JSON.stringify(layoutItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two layoutitems, check if they collide.
|
||||||
|
*
|
||||||
|
* @return {Boolean} True if colliding.
|
||||||
|
*/
|
||||||
|
export function collides (l1, l2) {
|
||||||
|
if (l1 === l2) return false // same element
|
||||||
|
if (l1.x + l1.w <= l2.x) return false // l1 is left of l2
|
||||||
|
if (l1.x >= l2.x + l2.w) return false // l1 is right of l2
|
||||||
|
if (l1.y + l1.h <= l2.y) return false // l1 is above l2
|
||||||
|
if (l1.y >= l2.y + l2.h) return false // l1 is below l2
|
||||||
|
return true // boxes overlap
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a layout, compact it. This involves going down each y coordinate and removing gaps
|
||||||
|
* between items.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Layout.
|
||||||
|
* @param {Boolean} verticalCompact Whether or not to compact the layout
|
||||||
|
* vertically.
|
||||||
|
* @return {Array} Compacted Layout.
|
||||||
|
*/
|
||||||
|
export function compact (layout, verticalCompact) {
|
||||||
|
// Statics go in the compareWith array right away so items flow around them.
|
||||||
|
const compareWith = getStatics(layout)
|
||||||
|
// We go through the items by row and column.
|
||||||
|
const sorted = sortLayoutItemsByRowCol(layout)
|
||||||
|
// Holding for new items.
|
||||||
|
const out = Array(layout.length)
|
||||||
|
|
||||||
|
for (let i = 0, len = sorted.length; i < len; i++) {
|
||||||
|
let l = sorted[i]
|
||||||
|
|
||||||
|
// Don't move static elements
|
||||||
|
if (!l.static) {
|
||||||
|
l = compactItem(compareWith, l, verticalCompact)
|
||||||
|
|
||||||
|
// Add to comparison array. We only collide with items before this one.
|
||||||
|
// Statics are already in this array.
|
||||||
|
compareWith.push(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to output array to make sure they still come out in the right order.
|
||||||
|
out[layout.indexOf(l)] = l
|
||||||
|
|
||||||
|
// Clear moved flag, if it exists.
|
||||||
|
l.moved = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compact an item in the layout.
|
||||||
|
*/
|
||||||
|
export function compactItem (compareWith, l, verticalCompact) {
|
||||||
|
if (verticalCompact) {
|
||||||
|
// Move the element up as far as it can go without colliding.
|
||||||
|
while (l.y > 0 && !getFirstCollision(compareWith, l)) {
|
||||||
|
l.y--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move it down, and keep moving it down if it's colliding.
|
||||||
|
let collides
|
||||||
|
while ((collides = getFirstCollision(compareWith, l))) {
|
||||||
|
l.y = collides.y + collides.h
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a layout, make sure all elements fit within its bounds.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Layout array.
|
||||||
|
* @param {Number} bounds Number of columns.
|
||||||
|
*/
|
||||||
|
export function correctBounds (layout, bounds) {
|
||||||
|
const collidesWith = getStatics(layout)
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
const l = layout[i]
|
||||||
|
// Overflows right
|
||||||
|
if (l.x + l.w > bounds.cols) l.x = bounds.cols - l.w
|
||||||
|
// Overflows left
|
||||||
|
if (l.x < 0) {
|
||||||
|
l.x = 0
|
||||||
|
l.w = bounds.cols
|
||||||
|
}
|
||||||
|
if (!l.static) collidesWith.push(l)
|
||||||
|
else {
|
||||||
|
// If this is static and collides with other statics, we must move it down.
|
||||||
|
// We have to do something nicer than just letting them overlap.
|
||||||
|
while (getFirstCollision(collidesWith, l)) {
|
||||||
|
l.y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a layout item by ID. Used so we can override later on if necessary.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Layout array.
|
||||||
|
* @param {String} id ID
|
||||||
|
* @return {LayoutItem} Item at ID.
|
||||||
|
*/
|
||||||
|
export function getLayoutItem (layout, id) {
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
if (layout[i].i === id) return layout[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first item this layout collides with.
|
||||||
|
* It doesn't appear to matter which order we approach this from, although
|
||||||
|
* perhaps that is the wrong thing to do.
|
||||||
|
*
|
||||||
|
* @param {Object} layoutItem Layout item.
|
||||||
|
* @return {Object|undefined} A colliding layout item, or undefined.
|
||||||
|
*/
|
||||||
|
export function getFirstCollision (layout, layoutItem) {
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
if (collides(layout[i], layoutItem)) return layout[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllCollisions (layout, layoutItem) {
|
||||||
|
return layout.filter((l) => collides(l, layoutItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all static elements.
|
||||||
|
* @param {Array} layout Array of layout objects.
|
||||||
|
* @return {Array} Array of static layout items..
|
||||||
|
*/
|
||||||
|
export function getStatics (layout) {
|
||||||
|
// return [];
|
||||||
|
return layout.filter((l) => l.static)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move an element. Responsible for doing cascading movements of other elements.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Full layout to modify.
|
||||||
|
* @param {LayoutItem} l element to move.
|
||||||
|
* @param {Number} [x] X position in grid units.
|
||||||
|
* @param {Number} [y] Y position in grid units.
|
||||||
|
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is
|
||||||
|
* being dragged/resized by th euser.
|
||||||
|
*/
|
||||||
|
export function moveElement (layout, l, x, y, isUserAction, preventCollision) {
|
||||||
|
if (l.static) return layout
|
||||||
|
|
||||||
|
// Short-circuit if nothing to do.
|
||||||
|
// if (l.y === y && l.x === x) return layout;
|
||||||
|
|
||||||
|
const oldX = l.x
|
||||||
|
const oldY = l.y
|
||||||
|
|
||||||
|
const movingUp = y && l.y > y
|
||||||
|
// This is quite a bit faster than extending the object
|
||||||
|
if (typeof x === 'number') l.x = x
|
||||||
|
if (typeof y === 'number') l.y = y
|
||||||
|
l.moved = true
|
||||||
|
|
||||||
|
// If this collides with anything, move it.
|
||||||
|
// When doing this comparison, we have to sort the items we compare with
|
||||||
|
// to ensure, in the case of multiple collisions, that we're getting the
|
||||||
|
// nearest collision.
|
||||||
|
let sorted = sortLayoutItemsByRowCol(layout)
|
||||||
|
if (movingUp) sorted = sorted.reverse()
|
||||||
|
const collisions = getAllCollisions(sorted, l)
|
||||||
|
|
||||||
|
if (preventCollision && collisions.length) {
|
||||||
|
l.x = oldX
|
||||||
|
l.y = oldY
|
||||||
|
l.moved = false
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move each item that collides away from this element.
|
||||||
|
for (let i = 0, len = collisions.length; i < len; i++) {
|
||||||
|
const collision = collisions[i]
|
||||||
|
// console.log('resolving collision between', l.i, 'at', l.y, 'and', collision.i, 'at', collision.y);
|
||||||
|
|
||||||
|
// Short circuit so we can't infinite loop
|
||||||
|
if (collision.moved) continue
|
||||||
|
|
||||||
|
// This makes it feel a bit more precise by waiting to swap for just a bit when moving up.
|
||||||
|
if (l.y > collision.y && l.y - collision.y > collision.h / 4) continue
|
||||||
|
|
||||||
|
// Don't move static items - we have to move *this* element away
|
||||||
|
if (collision.static) {
|
||||||
|
layout = moveElementAwayFromCollision(layout, collision, l, isUserAction)
|
||||||
|
} else {
|
||||||
|
layout = moveElementAwayFromCollision(layout, l, collision, isUserAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where the magic needs to happen - given a collision, move an element away from the collision.
|
||||||
|
* We attempt to move it up if there's room, otherwise it goes below.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Full layout to modify.
|
||||||
|
* @param {LayoutItem} collidesWith Layout item we're colliding with.
|
||||||
|
* @param {LayoutItem} itemToMove Layout item we're moving.
|
||||||
|
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is being dragged/resized
|
||||||
|
* by the user.
|
||||||
|
*/
|
||||||
|
export function moveElementAwayFromCollision (layout, collidesWith,
|
||||||
|
itemToMove, isUserAction) {
|
||||||
|
const preventCollision = false // we're already colliding
|
||||||
|
// If there is enough space above the collision to put this element, move it there.
|
||||||
|
// We only do this on the main collision as this can get funky in cascades and cause
|
||||||
|
// unwanted swapping behavior.
|
||||||
|
if (isUserAction) {
|
||||||
|
// Make a mock item so we don't modify the item here, only modify in moveElement.
|
||||||
|
const fakeItem = {
|
||||||
|
x: itemToMove.x,
|
||||||
|
y: itemToMove.y,
|
||||||
|
w: itemToMove.w,
|
||||||
|
h: itemToMove.h,
|
||||||
|
i: '-1'
|
||||||
|
}
|
||||||
|
fakeItem.y = Math.max(collidesWith.y - itemToMove.h, 0)
|
||||||
|
if (!getFirstCollision(layout, fakeItem)) {
|
||||||
|
return moveElement(layout, itemToMove, undefined, fakeItem.y, preventCollision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previously this was optimized to move below the collision directly, but this can cause problems
|
||||||
|
// with cascading moves, as an item may actually leapflog a collision and cause a reversal in order.
|
||||||
|
return moveElement(layout, itemToMove, undefined, itemToMove.y + 1, preventCollision)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to convert a number to a percentage string.
|
||||||
|
*
|
||||||
|
* @param {Number} num Any number
|
||||||
|
* @return {String} That number as a percentage.
|
||||||
|
*/
|
||||||
|
export function perc (num) {
|
||||||
|
return num * 100 + '%'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTransform (top, left, width, height) {
|
||||||
|
// Replace unitless items with px
|
||||||
|
const translate = 'translate3d(' + left + 'px,' + top + 'px, 0)'
|
||||||
|
return {
|
||||||
|
transform: translate,
|
||||||
|
WebkitTransform: translate,
|
||||||
|
MozTransform: translate,
|
||||||
|
msTransform: translate,
|
||||||
|
OTransform: translate,
|
||||||
|
width: width + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Just like the setTransform method, but instead it will return a negative value of right.
|
||||||
|
*
|
||||||
|
* @param top
|
||||||
|
* @param right
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
* @returns {{transform: string, WebkitTransform: string, MozTransform: string, msTransform: string, OTransform: string, width: string, height: string, position: string}}
|
||||||
|
*/
|
||||||
|
export function setTransformRtl (top, right, width, height) {
|
||||||
|
// Replace unitless items with px
|
||||||
|
const translate = 'translate3d(' + right * -1 + 'px,' + top + 'px, 0)'
|
||||||
|
return {
|
||||||
|
transform: translate,
|
||||||
|
WebkitTransform: translate,
|
||||||
|
MozTransform: translate,
|
||||||
|
msTransform: translate,
|
||||||
|
OTransform: translate,
|
||||||
|
width: width + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTopLeft (top, left, width, height) {
|
||||||
|
return {
|
||||||
|
top: top + 'px',
|
||||||
|
left: left + 'px',
|
||||||
|
width: width + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Just like the setTopLeft method, but instead, it will return a right property instead of left.
|
||||||
|
*
|
||||||
|
* @param top
|
||||||
|
* @param right
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
* @returns {{top: string, right: string, width: string, height: string, position: string}}
|
||||||
|
*/
|
||||||
|
export function setTopRight (top, right, width, height) {
|
||||||
|
return {
|
||||||
|
top: top + 'px',
|
||||||
|
right: right + 'px',
|
||||||
|
width: width + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get layout items sorted from top left to right and down.
|
||||||
|
*
|
||||||
|
* @return {Array} Array of layout objects.
|
||||||
|
* @return {Array} Layout, sorted static items first.
|
||||||
|
*/
|
||||||
|
export function sortLayoutItemsByRowCol (layout) {
|
||||||
|
return [].concat(layout).sort(function (a, b) {
|
||||||
|
if (a.y === b.y && a.x === b.x) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a layout using the initialLayout and children as a template.
|
||||||
|
* Missing entries will be added, extraneous ones will be truncated.
|
||||||
|
*
|
||||||
|
* @param {Array} initialLayout Layout passed in through props.
|
||||||
|
* @param {String} breakpoint Current responsive breakpoint.
|
||||||
|
* @param {Boolean} verticalCompact Whether or not to compact the layout vertically.
|
||||||
|
* @return {Array} Working layout.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
export function synchronizeLayoutWithChildren(initialLayout: Layout, children: Array<React.Element>|React.Element,
|
||||||
|
cols: number, verticalCompact: boolean): Layout {
|
||||||
|
// ensure 'children' is always an array
|
||||||
|
if (!Array.isArray(children)) {
|
||||||
|
children = [children];
|
||||||
|
}
|
||||||
|
initialLayout = initialLayout || [];
|
||||||
|
|
||||||
|
// Generate one layout item per child.
|
||||||
|
let layout: Layout = [];
|
||||||
|
for (let i = 0, len = children.length; i < len; i++) {
|
||||||
|
let newItem;
|
||||||
|
const child = children[i];
|
||||||
|
|
||||||
|
// Don't overwrite if it already exists.
|
||||||
|
const exists = getLayoutItem(initialLayout, child.key || "1" /!* FIXME satisfies Flow *!/);
|
||||||
|
if (exists) {
|
||||||
|
newItem = exists;
|
||||||
|
} else {
|
||||||
|
const g = child.props._grid;
|
||||||
|
|
||||||
|
// Hey, this item has a _grid property, use it.
|
||||||
|
if (g) {
|
||||||
|
if (!isProduction) {
|
||||||
|
validateLayout([g], 'ReactGridLayout.children');
|
||||||
|
}
|
||||||
|
// Validated; add it to the layout. Bottom 'y' possible is the bottom of the layout.
|
||||||
|
// This allows you to do nice stuff like specify {y: Infinity}
|
||||||
|
if (verticalCompact) {
|
||||||
|
newItem = cloneLayoutItem({...g, y: Math.min(bottom(layout), g.y), i: child.key});
|
||||||
|
} else {
|
||||||
|
newItem = cloneLayoutItem({...g, y: g.y, i: child.key});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Nothing provided: ensure this is added to the bottom
|
||||||
|
else {
|
||||||
|
newItem = cloneLayoutItem({w: 1, h: 1, x: 0, y: bottom(layout), i: child.key || "1"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout[i] = newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct the layout.
|
||||||
|
layout = correctBounds(layout, {cols: cols});
|
||||||
|
layout = compact(layout, verticalCompact);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a layout. Throws errors.
|
||||||
|
*
|
||||||
|
* @param {Array} layout Array of layout items.
|
||||||
|
* @param {String} [contextName] Context name for errors.
|
||||||
|
* @throw {Error} Validation error.
|
||||||
|
*/
|
||||||
|
export function validateLayout (layout, contextName) {
|
||||||
|
contextName = contextName || 'Layout'
|
||||||
|
const subProps = ['x', 'y', 'w', 'h']
|
||||||
|
if (!Array.isArray(layout)) throw new Error(contextName + ' must be an array!')
|
||||||
|
for (let i = 0, len = layout.length; i < len; i++) {
|
||||||
|
const item = layout[i]
|
||||||
|
for (let j = 0; j < subProps.length; j++) {
|
||||||
|
if (typeof item[subProps[j]] !== 'number') {
|
||||||
|
throw new Error('VueGridLayout: ' + contextName + '[' + i + '].' + subProps[j] + ' must be a number!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.i && typeof item.i !== 'string') {
|
||||||
|
// number is also ok, so comment the error
|
||||||
|
// TODO confirm if commenting the line below doesn't cause unexpected problems
|
||||||
|
// throw new Error('VueGridLayout: ' + contextName + '[' + i + '].i must be a string!');
|
||||||
|
}
|
||||||
|
if (item.static !== undefined && typeof item.static !== 'boolean') {
|
||||||
|
throw new Error('VueGridLayout: ' + contextName + '[' + i + '].static must be a boolean!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flow can't really figure this out, so we just use Object
|
||||||
|
export function autoBindHandlers (el, fns) {
|
||||||
|
fns.forEach((key) => el[key] = el[key].bind(el))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a JS object to CSS string. Similar to React's output of CSS.
|
||||||
|
* @param obj
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function createMarkup (obj) {
|
||||||
|
const keys = Object.keys(obj)
|
||||||
|
if (!keys.length) return ''
|
||||||
|
let i; const len = keys.length
|
||||||
|
let result = ''
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
const key = keys[i]
|
||||||
|
const val = obj[key]
|
||||||
|
result += hyphenate(key) + ':' + addPx(key, val) + ';'
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following list is defined in React's core */
|
||||||
|
export var IS_UNITLESS = {
|
||||||
|
animationIterationCount: true,
|
||||||
|
boxFlex: true,
|
||||||
|
boxFlexGroup: true,
|
||||||
|
boxOrdinalGroup: true,
|
||||||
|
columnCount: true,
|
||||||
|
flex: true,
|
||||||
|
flexGrow: true,
|
||||||
|
flexPositive: true,
|
||||||
|
flexShrink: true,
|
||||||
|
flexNegative: true,
|
||||||
|
flexOrder: true,
|
||||||
|
gridRow: true,
|
||||||
|
gridColumn: true,
|
||||||
|
fontWeight: true,
|
||||||
|
lineClamp: true,
|
||||||
|
lineHeight: true,
|
||||||
|
opacity: true,
|
||||||
|
order: true,
|
||||||
|
orphans: true,
|
||||||
|
tabSize: true,
|
||||||
|
widows: true,
|
||||||
|
zIndex: true,
|
||||||
|
zoom: true,
|
||||||
|
|
||||||
|
// SVG-related properties
|
||||||
|
fillOpacity: true,
|
||||||
|
stopOpacity: true,
|
||||||
|
strokeDashoffset: true,
|
||||||
|
strokeOpacity: true,
|
||||||
|
strokeWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will add px to the end of style values which are Numbers.
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export function addPx (name, value) {
|
||||||
|
if (typeof value === 'number' && !IS_UNITLESS[name]) {
|
||||||
|
return value + 'px'
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hyphenate a camelCase string.
|
||||||
|
*
|
||||||
|
* @param {String} str
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export var hyphenateRE = /([a-z\d])([A-Z])/g
|
||||||
|
|
||||||
|
export function hyphenate (str) {
|
||||||
|
return str.replace(hyphenateRE, '$1-$2').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findItemInArray (array, property, value) {
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
if (array[i][property] == value) { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findAndRemove (array, property, value) {
|
||||||
|
array.forEach(function (result, index) {
|
||||||
|
if (result[property] === value) {
|
||||||
|
// Remove from array
|
||||||
|
array.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,14 +5,13 @@
|
|||||||
v-if="gridLayoutShow"
|
v-if="gridLayoutShow"
|
||||||
:class="firstInit ? 'no-animation' : ''"
|
:class="firstInit ? 'no-animation' : ''"
|
||||||
:col-num="12"
|
:col-num="12"
|
||||||
:maxRows="12"
|
|
||||||
:is-draggable="!panelLock"
|
:is-draggable="!panelLock"
|
||||||
:is-resizable="!panelLock"
|
:is-resizable="!panelLock"
|
||||||
:layout.sync="copyDataList"
|
:layout.sync="copyDataList"
|
||||||
:margin="[10, 10]"
|
:margin="[10, 10]"
|
||||||
:row-height="stepWidth"
|
:row-height="stepWidth"
|
||||||
:use-css-transforms="true"
|
|
||||||
:vertical-compact="true"
|
:vertical-compact="true"
|
||||||
|
:use-css-transforms="true"
|
||||||
:style="{
|
:style="{
|
||||||
'margin-top': layoutMargintop,
|
'margin-top': layoutMargintop,
|
||||||
'padding-bottom': isGroup ? '0' : (200 + 'px')
|
'padding-bottom': isGroup ? '0' : (200 + 'px')
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
:x="item.x"
|
:x="item.x"
|
||||||
:y="item.y"
|
:y="item.y"
|
||||||
:min-h="headerH"
|
:min-h="headerH"
|
||||||
|
:max-h="12"
|
||||||
:static="item.static"
|
:static="item.static"
|
||||||
:class="{
|
:class="{
|
||||||
'group-hide-header':item.type === 'group' && item.param.collapse,
|
'group-hide-header':item.type === 'group' && item.param.collapse,
|
||||||
@@ -84,7 +84,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueGridLayout from 'vue-grid-layout'
|
import GridLayout from './chart/grid/GridLayout'
|
||||||
|
import GridItem from './chart/grid/GridItem'
|
||||||
import { fromRoute } from '@/components/common/js/constants'
|
import { fromRoute } from '@/components/common/js/constants'
|
||||||
import { getGroupHeight, getLayoutPosition, isGroup } from './chart/tools'
|
import { getGroupHeight, getLayoutPosition, isGroup } from './chart/tools'
|
||||||
import panelChart from '@/components/chart/panelChart'
|
import panelChart from '@/components/chart/panelChart'
|
||||||
@@ -105,8 +106,8 @@ export default {
|
|||||||
dataList: Array // 看板中所有图表信息
|
dataList: Array // 看板中所有图表信息
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GridLayout: VueGridLayout.GridLayout,
|
GridLayout: GridLayout,
|
||||||
GridItem: VueGridLayout.GridItem,
|
GridItem: GridItem,
|
||||||
panelChart
|
panelChart
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -267,10 +268,10 @@ export default {
|
|||||||
charts: charts
|
charts: charts
|
||||||
}
|
}
|
||||||
// console.log(this.copyDataList)
|
// console.log(this.copyDataList)
|
||||||
this.$put('/visual/panel/chart/weights', params).then(() => {
|
// this.$put('/visual/panel/chart/weights', params).then(() => {
|
||||||
const position = getLayoutPosition(this.copyDataList)
|
// const position = getLayoutPosition(this.copyDataList)
|
||||||
this.$store.commit('setChartLastPosition', position)
|
// this.$store.commit('setChartLastPosition', position)
|
||||||
})
|
// })
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default {
|
|||||||
active: '#53a3cb',
|
active: '#53a3cb',
|
||||||
inactive: '#7e7e7e'
|
inactive: '#7e7e7e'
|
||||||
},
|
},
|
||||||
chartId: ''
|
chartId: '',
|
||||||
|
isNoData: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -36,8 +37,10 @@ export default {
|
|||||||
handleTimeSeries (chartInfo, seriesTemplate, originalDatas) {
|
handleTimeSeries (chartInfo, seriesTemplate, originalDatas) {
|
||||||
const series = []
|
const series = []
|
||||||
let colorIndex = 0
|
let colorIndex = 0
|
||||||
|
console.log(this.chartData)
|
||||||
originalDatas.forEach((originalData, expressionIndex) => {
|
originalDatas.forEach((originalData, expressionIndex) => {
|
||||||
originalData.forEach((data, dataIndex) => {
|
originalData.forEach((data, dataIndex) => {
|
||||||
|
this.isNoData = false
|
||||||
const s = lodash.cloneDeep(seriesTemplate)
|
const s = lodash.cloneDeep(seriesTemplate)
|
||||||
if (s) {
|
if (s) {
|
||||||
s.data = data.values
|
s.data = data.values
|
||||||
@@ -45,6 +48,7 @@ export default {
|
|||||||
if (chartInfo.param.stack) { // 堆叠
|
if (chartInfo.param.stack) { // 堆叠
|
||||||
s.stack = 'Total'
|
s.stack = 'Total'
|
||||||
}
|
}
|
||||||
|
console.log(chartInfo.param.enable && chartInfo.param.enable.thresholds && !lodash.isEmpty(chartInfo.param.thresholds) && chartInfo.param.thresholds.length)
|
||||||
if (chartInfo.param.enable && chartInfo.param.enable.thresholds && !lodash.isEmpty(chartInfo.param.thresholds) && chartInfo.param.thresholds.length) { // 阈值
|
if (chartInfo.param.enable && chartInfo.param.enable.thresholds && !lodash.isEmpty(chartInfo.param.thresholds) && chartInfo.param.thresholds.length) { // 阈值
|
||||||
s.markLine = {
|
s.markLine = {
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
@@ -52,7 +56,7 @@ export default {
|
|||||||
}
|
}
|
||||||
s.markLine.data = chartInfo.param.thresholds.map(threshold => {
|
s.markLine.data = chartInfo.param.thresholds.map(threshold => {
|
||||||
return {
|
return {
|
||||||
yAxis: threshold.value,
|
yAxis: threshold.value || 0,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: threshold.color,
|
color: threshold.color,
|
||||||
width: 2,
|
width: 2,
|
||||||
@@ -66,6 +70,8 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
console.log(this.isNoData)
|
||||||
|
this.$emit('chartIsNoData', this.isNoData)
|
||||||
return series
|
return series
|
||||||
},
|
},
|
||||||
// 单个legend
|
// 单个legend
|
||||||
|
|||||||
@@ -508,9 +508,9 @@
|
|||||||
></el-switch>
|
></el-switch>
|
||||||
</div>
|
</div>
|
||||||
<transition name="el-zoom-in-top">
|
<transition name="el-zoom-in-top">
|
||||||
<el-row v-if="chartConfig.param.enable.valueMapping">
|
<el-row v-if="chartConfig.param.enable.thresholds">
|
||||||
<div
|
<div
|
||||||
v-for="(item,index) in chartConfig.param.valueMapping"
|
v-for="(item,index) in chartConfig.param.thresholds"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<div class="chart-title chart-title-config">
|
<div class="chart-title chart-title-config">
|
||||||
|
|||||||
Reference in New Issue
Block a user