diff --git a/caravel/assets/javascripts/dashboard.js b/caravel/assets/javascripts/dashboard.jsx
similarity index 58%
rename from caravel/assets/javascripts/dashboard.js
rename to caravel/assets/javascripts/dashboard.jsx
index 89b96f324d1d5..b28d31c591c7c 100644
--- a/caravel/assets/javascripts/dashboard.js
+++ b/caravel/assets/javascripts/dashboard.jsx
@@ -4,18 +4,193 @@ var px = require('./modules/caravel.js');
var d3 = require('d3');
var showModal = require('./modules/utils.js').showModal;
require('bootstrap');
+import React from 'react';
+import { render } from 'react-dom';
var ace = require('brace');
require('brace/mode/css');
require('brace/theme/crimson_editor');
require('./caravel-select2.js');
-require('../node_modules/gridster/dist/jquery.gridster.min.css');
-require('../node_modules/gridster/dist/jquery.gridster.min.js');
+require('../node_modules/react-grid-layout/css/styles.css');
+require('../node_modules/react-resizable/css/styles.css');
require('../stylesheets/dashboard.css');
+import { Responsive, WidthProvider } from "react-grid-layout";
+const ResponsiveReactGridLayout = WidthProvider(Responsive);
+
+class SliceCell extends React.Component {
+ render() {
+ const slice = this.props.slice,
+ createMarkup = function () {
+ return { __html: slice.description_markeddown };
+ };
+
+ return (
+
+
+
+
+ {slice.slice_name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+class GridLayout extends React.Component {
+ removeSlice(sliceId) {
+ $('[data-toggle="tooltip"]').tooltip("hide");
+ this.setState({
+ layout: this.state.layout.filter(function (reactPos) {
+ return reactPos.i !== String(sliceId);
+ }),
+ slices: this.state.slices.filter(function (slice) {
+ return slice.slice_id !== sliceId;
+ }),
+ sliceElements: this.state.sliceElements.filter(function (sliceElement) {
+ return sliceElement.key !== String(sliceId);
+ })
+ });
+ }
+
+ onResizeStop(layout, oldItem, newItem) {
+ if (oldItem.w != newItem.w || oldItem.h != newItem.h) {
+ this.setState({
+ layout: layout
+ }, function () {
+ this.props.dashboard.getSlice(newItem.i).resize();
+ });
+ }
+ }
+
+ onDragStop(layout) {
+ this.setState({
+ layout: layout
+ });
+ }
+
+ serialize() {
+ return this.state.layout.map(function (reactPos) {
+ return {
+ slice_id: reactPos.i,
+ col: reactPos.x + 1,
+ row: reactPos.y,
+ size_x: reactPos.w,
+ size_y: reactPos.h
+ };
+ });
+ }
+
+ componentWillMount() {
+ var layout = [],
+ sliceElements = [];
+
+ this.props.slices.forEach(function (slice, index) {
+ var pos = this.props.posDict[slice.slice_id];
+ if (!pos) {
+ pos = {
+ col: (index * 4 + 1) % 12,
+ row: Math.floor((index) / 3) * 4,
+ size_x: 4,
+ size_y: 4
+ };
+ }
+
+ sliceElements.push(
+
+
+
+ );
+
+ layout.push({
+ i: String(slice.slice_id),
+ x: pos.col - 1,
+ y: pos.row,
+ w: pos.size_x,
+ minW: 2,
+ h: pos.size_y
+ });
+ }, this);
+
+ this.setState({
+ layout: layout,
+ sliceElements: sliceElements,
+ slices: this.props.slices
+ });
+ }
+
+ render() {
+ return (
+
+ {this.state.sliceElements}
+
+ );
+ }
+}
+
var Dashboard = function (dashboardData) {
+ var reactGridLayout;
+
var dashboard = $.extend(dashboardData, {
filters: {},
init: function () {
@@ -128,30 +303,17 @@ var Dashboard = function (dashboardData) {
}
},
initDashboardView: function () {
+ var posDict = {}
+ this.position_json.forEach(function (position) {
+ posDict[position.slice_id] = position;
+ });
+
+ reactGridLayout = render(
+ ,
+ document.getElementById("grid-container")
+ );
+
dashboard = this;
- var gridster = $(".gridster ul").gridster({
- autogrow_cols: true,
- widget_margins: [10, 10],
- widget_base_dimensions: [95, 95],
- draggable: {
- handle: '.drag'
- },
- resize: {
- enabled: true,
- stop: function (e, ui, element) {
- dashboard.getSlice($(element).attr('slice_id')).resize();
- }
- },
- serialize_params: function (_w, wgd) {
- return {
- slice_id: $(_w).attr('slice_id'),
- col: wgd.col,
- row: wgd.row,
- size_x: wgd.size_x,
- size_y: wgd.size_y
- };
- }
- }).data('gridster');
// Displaying widget controls on hover
$('.chart-header').hover(
@@ -162,18 +324,18 @@ var Dashboard = function (dashboardData) {
$(this).find('.chart-controls').fadeOut(300);
}
);
- $("div.gridster").css('visibility', 'visible');
+ $("div.grid-container").css('visibility', 'visible');
$("#savedash").click(function () {
var expanded_slices = {};
$.each($(".slice_info"), function (i, d) {
var widget = $(this).parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
- expanded_slices[$(d).attr('slice_id')] = true;
+ expanded_slices[$(widget).attr('data-slice-id')] = true;
}
});
var data = {
- positions: gridster.serialize(),
+ positions: reactGridLayout.serialize(),
css: editor.getValue(),
expanded_slices: expanded_slices
};
@@ -236,12 +398,8 @@ var Dashboard = function (dashboardData) {
slice.render(true);
});
});
- $("a.remove-chart").click(function () {
- var li = $(this).parents("li");
- gridster.remove_widget(li);
- });
- $("li.widget").click(function (e) {
+ $("div.widget").click(function (e) {
var $this = $(this);
var $target = $(e.target);
diff --git a/caravel/assets/package.json b/caravel/assets/package.json
index e6f5e0188cfd6..07a41fa154708 100644
--- a/caravel/assets/package.json
+++ b/caravel/assets/package.json
@@ -65,6 +65,8 @@
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.7",
+ "react-grid-layout": "^0.12.3",
+ "react-resizable": "^1.3.3",
"select2": "3.5",
"select2-bootstrap-css": "^1.4.6",
"style-loader": "^0.13.0",
diff --git a/caravel/assets/stylesheets/caravel.css b/caravel/assets/stylesheets/caravel.css
index 05614ecd6365d..b30d868f1a757 100644
--- a/caravel/assets/stylesheets/caravel.css
+++ b/caravel/assets/stylesheets/caravel.css
@@ -170,12 +170,12 @@ li.widget:hover {
z-index: 1000;
}
-li.widget .chart-header {
+div.widget .chart-header {
padding: 5px;
background-color: #f1f1f1;
}
-li.widget .chart-header a {
+div.widget .chart-header a {
margin-left: 5px;
}
@@ -183,16 +183,18 @@ li.widget .chart-header a {
display: none;
}
-li.widget .chart-controls {
+div.widget .chart-controls {
+ background-clip: content-box;
background-color: #f1f1f1;
position: absolute;
right: 0;
left: 0;
- padding: 0px 5px;
+ top: 5px;
+ padding: 5px 5px;
opacity: 0.75;
display: none;
}
-li.widget .slice_container {
+div.widget .slice_container {
overflow: auto;
}
diff --git a/caravel/assets/stylesheets/dashboard.css b/caravel/assets/stylesheets/dashboard.css
index e9376487fd547..3a77cf0c71c96 100644
--- a/caravel/assets/stylesheets/dashboard.css
+++ b/caravel/assets/stylesheets/dashboard.css
@@ -4,23 +4,22 @@
.dashboard i.drag {
cursor: move !important;
}
-.dashboard .gridster .preview-holder {
+.dashboard .slice-grid .preview-holder {
z-index: 1;
position: absolute;
background-color: #AAA;
border-color: #AAA;
opacity: 0.3;
}
-.gridster li.widget{
- list-style-type: none;
+
+.slice-grid div.widget{
border-radius: 0;
- margin: 5px;
border: 1px solid #ccc;
box-shadow: 2px 1px 5px -2px #aaa;
background-color: #fff;
}
-.dashboard .gridster .dragging,
-.dashboard .gridster .resizing {
+.dashboard .slice-grid .dragging,
+.dashboard .slice-grid .resizing {
opacity: 0.5;
}
.dashboard img.loading {
diff --git a/caravel/assets/visualizations/pivot_table.css b/caravel/assets/visualizations/pivot_table.css
index 8b8b136d3c453..7d94f85344af4 100644
--- a/caravel/assets/visualizations/pivot_table.css
+++ b/caravel/assets/visualizations/pivot_table.css
@@ -1,4 +1,4 @@
-.gridster .widget.pivot_table .slice_container {
+.slice-grid .widget.pivot_table .slice_container {
overflow: auto !important;
}
diff --git a/caravel/assets/visualizations/table.css b/caravel/assets/visualizations/table.css
index 3d11841bb4da5..655a360c14c40 100644
--- a/caravel/assets/visualizations/table.css
+++ b/caravel/assets/visualizations/table.css
@@ -1,4 +1,4 @@
-.gridster .widget.table .slice_container {
+.slice-grid .widget.table .slice_container {
overflow: auto !important;
}
diff --git a/caravel/assets/webpack.config.js b/caravel/assets/webpack.config.js
index d9253f3d5c413..cabb8bc12ceb2 100644
--- a/caravel/assets/webpack.config.js
+++ b/caravel/assets/webpack.config.js
@@ -6,7 +6,7 @@ var config = {
// for now generate one compiled js file per entry point / html page
entry: {
'css-theme': APP_DIR + '/javascripts/css-theme.js',
- dashboard: APP_DIR + '/javascripts/dashboard.js',
+ dashboard: APP_DIR + '/javascripts/dashboard.jsx',
explore: APP_DIR + '/javascripts/explore.js',
welcome: APP_DIR + '/javascripts/welcome.js',
sql: APP_DIR + '/javascripts/sql.js',
diff --git a/caravel/models.py b/caravel/models.py
index 0a91ee8f78d49..bdfb0e80bca3d 100644
--- a/caravel/models.py
+++ b/caravel/models.py
@@ -197,6 +197,7 @@ def datasource_id(self):
@property
def data(self):
+ """Data used to render slice in templates"""
d = {}
self.token = ''
try:
@@ -205,6 +206,11 @@ def data(self):
except Exception as e:
d['error'] = str(e)
d['slice_id'] = self.id
+ d['slice_name'] = self.slice_name
+ d['description'] = self.description
+ d['slice_url'] = self.slice_url
+ d['edit_url'] = self.edit_url
+ d['description_markeddown'] = self.description_markeddown
return d
@property
@@ -309,6 +315,7 @@ def json_data(self):
'dashboard_title': self.dashboard_title,
'slug': self.slug,
'slices': [slc.data for slc in self.slices],
+ 'position_json': json.loads(self.position_json),
}
return json.dumps(d)
diff --git a/caravel/templates/caravel/dashboard.html b/caravel/templates/caravel/dashboard.html
index 5c8cbed67fb5b..bde25e2031add 100644
--- a/caravel/templates/caravel/dashboard.html
+++ b/caravel/templates/caravel/dashboard.html
@@ -6,6 +6,7 @@
{% endblock %}
{% block title %}[dashboard] {{ dashboard.dashboard_title }}{% endblock %}
{% block body %}
+
@@ -97,71 +98,9 @@
-
{% endblock %}
diff --git a/caravel/views.py b/caravel/views.py
index b3bb3b4ec76ae..dd351d066fe02 100644
--- a/caravel/views.py
+++ b/caravel/views.py
@@ -883,15 +883,9 @@ def dashboard(**kwargs): # noqa
pass
dashboard(dashboard_id=dash.id)
- pos_dict = {}
- if dash.position_json:
- pos_dict = {
- int(o['slice_id']): o
- for o in json.loads(dash.position_json)}
return self.render_template(
"caravel/dashboard.html", dashboard=dash,
templates=templates,
- pos_dict=pos_dict,
dash_save_perm=appbuilder.sm.has_access('can_save_dash', 'Caravel'),
dash_edit_perm=appbuilder.sm.has_access('can_edit', 'DashboardModelView'))