ebac854da8
* The MongoDB/Mongoose data storage is removed in favor of Sequelize. This abstracts over SQLite or RDBMs like PostgreSQL and MSSQL. The default is SQLite, which significantly simplifies deployments in end-user environments. * As Spacedeck now has no more mandatory server dependencies, we can wrap it in Electron and ship it as a desktop application. * Removes docker-compose.yml * First version of import UI
980 lines
28 KiB
JavaScript
980 lines
28 KiB
JavaScript
|
|
/*
|
|
Spacedeck Whiteboard Directive
|
|
This module registers a custom Vue directive that handles Whiteboard sections.
|
|
*/
|
|
|
|
function setup_whiteboard_directives() {
|
|
if ('ontouchstart' in window) {
|
|
var edown = "touchstart";
|
|
var emove = "touchmove";
|
|
var eup = "touchend";
|
|
} else {
|
|
var edown = "mousedown";
|
|
var emove = "mousemove";
|
|
var eup = "mouseup";
|
|
}
|
|
|
|
Vue.directive('sd-whiteboard', {
|
|
bind: function () {
|
|
var el = this.el;
|
|
|
|
$(el).on(edown, ".artifact", this.handle_mouse_down_artifact.bind(this));
|
|
$(el).on("dblclick", ".artifact", this.handle_double_click_artifact.bind(this));
|
|
$(el).on("keyup", ".artifact", this.handle_key_up_artifact.bind(this));
|
|
$(el).on("keydown", ".artifact", this.handle_key_down_artifact.bind(this));
|
|
$(el).bind(edown, this.handle_mouse_down_space.bind(this));
|
|
$(el).bind(emove, this.handle_mouse_move.bind(this));
|
|
$(el).bind(eup, this.handle_mouse_up_space.bind(this));
|
|
|
|
$(el).bind("wheel", this.handle_wheel_space.bind(this));
|
|
|
|
$(document.body).bind("mouseleave", this.handle_mouse_leave.bind(this));
|
|
|
|
$(el).find(".handle.resize-nw").bind(edown, function(e){this.handle_transform_mouse_down(e,1,1)}.bind(this));
|
|
$(el).find(".handle.resize-n").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,1)}.bind(this));
|
|
$(el).find(".handle.resize-ne").bind(edown, function(e){this.handle_transform_mouse_down(e,0,1)}.bind(this));
|
|
$(el).find(".handle.resize-e").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0.5)}.bind(this));
|
|
$(el).find(".handle.resize-se").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0)}.bind(this));
|
|
$(el).find(".handle.resize-s").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,0)}.bind(this));
|
|
$(el).find(".handle.resize-sw").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0)}.bind(this));
|
|
$(el).find(".handle.resize-w").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0.5)}.bind(this));
|
|
|
|
$(el).find(".edge-handle.resize-n").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,1)}.bind(this));
|
|
$(el).find(".edge-handle.resize-s").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,0)}.bind(this));
|
|
$(el).find(".edge-handle.resize-e").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0.5)}.bind(this));
|
|
$(el).find(".edge-handle.resize-w").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0.5)}.bind(this));
|
|
|
|
$(el).on(edown, ".vector-handle", function(e){this.handle_vector_transform_mouse_down(e)}.bind(this));
|
|
|
|
var $scope = this.vm.$root;
|
|
|
|
this.space_zoom = 1;
|
|
this.artifacts_before_transaction = [];
|
|
$scope.active_tool = "pointer";
|
|
},
|
|
update: function () {
|
|
},
|
|
unbind: function () {
|
|
// do clean up work
|
|
// e.g. remove event listeners added in bind()
|
|
|
|
var el = this.el;
|
|
$(el).off(edown+" "+emove+" "+eup+" "+"keyup keydown mouseleave");
|
|
$(document.body).unbind("mouseleave");
|
|
},
|
|
|
|
handle_key_down_artifact: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
},
|
|
|
|
handle_key_up_artifact: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
},
|
|
|
|
handle_mouse_down_artifact: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
if (!$scope.editing_artifact_id) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
}
|
|
|
|
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
|
|
|
|
if ($scope.active_tool == "zoom") return;
|
|
|
|
if ($scope.active_tool == "eyedrop") {
|
|
var arts = $scope.selected_artifacts();
|
|
if (!$scope.is_selected(a) && arts.length > 0) {
|
|
// copy style from clicked artifact to selected artifacts
|
|
$scope.begin_transaction();
|
|
|
|
$scope.update_selected_artifacts(function(selected_artifact) {
|
|
selected_artifact.style = _.clone(a.style);
|
|
});
|
|
|
|
$scope.active_tool = "pointer";
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($scope.active_tool == "pan") {
|
|
this.start_pan(evt);
|
|
return;
|
|
}
|
|
|
|
if ($scope.active_tool == "pointer") {
|
|
if (!$scope.is_selected(a) || evt.shiftKey) {
|
|
this.select(evt,a);
|
|
}
|
|
|
|
// copy via alt+move
|
|
if (evt.altKey) {
|
|
a = $scope.clone_artifact(a);
|
|
this.select(evt,a);
|
|
}
|
|
}
|
|
|
|
$scope.begin_transaction();
|
|
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
$scope.mouse_ox = cursor.x;
|
|
$scope.mouse_oy = cursor.y;
|
|
$scope.mouse_moved = false;
|
|
this.mouse_state = "move";
|
|
evt.stopPropagation();
|
|
},
|
|
|
|
handle_double_click_artifact: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
|
|
if (!a) return;
|
|
|
|
if (a.payload_uri) {
|
|
$scope.download_selected_artifacts();
|
|
}
|
|
|
|
$scope.toggle_selected_artifact_editing(true);
|
|
},
|
|
|
|
handle_transform_mouse_down: function(evt,origin_x,origin_y) {
|
|
evt.stopPropagation();
|
|
evt.preventDefault();
|
|
|
|
var $scope = this.vm.$root;
|
|
|
|
$scope.begin_transaction();
|
|
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
this.mouse_state = "transform";
|
|
$scope.mouse_ox = cursor.x;
|
|
$scope.mouse_oy = cursor.y;
|
|
$scope.transform_ox = origin_x;
|
|
$scope.transform_oy = origin_y;
|
|
},
|
|
|
|
handle_vector_transform_mouse_down: function(evt) {
|
|
evt.stopPropagation();
|
|
evt.preventDefault();
|
|
|
|
var $scope = this.vm.$root;
|
|
|
|
var idx = parseInt($(evt.currentTarget).attr("data-idx"));
|
|
$scope.selected_control_point_idx = idx;
|
|
|
|
$scope.begin_transaction();
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
this.mouse_state = "vector_transform";
|
|
$scope.mouse_ox = cursor.x;
|
|
$scope.mouse_oy = cursor.y;
|
|
//$scope.transform_ox = origin_x;
|
|
//$scope.transform_oy = origin_y;
|
|
},
|
|
|
|
handle_wheel_space: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
if (!evt.ctrlKey && !evt.shiftKey) return;
|
|
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
var amount = 1;
|
|
var dy = evt.originalEvent.deltaY;
|
|
if (dy>0) {
|
|
amount=1.2;
|
|
if ($scope.viewport_zoom<=0.05) return false;
|
|
} else if (dy<0) {
|
|
amount=0.9;
|
|
if ($scope.viewport_zoom>=2) return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
$scope.zoom_to_cursor(evt,amount);
|
|
},
|
|
|
|
handle_mouse_down_space: function(evt) {
|
|
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
|
|
|
|
var $scope = this.vm.$root;
|
|
|
|
$scope.opened_dialog="none";
|
|
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
$scope.mouse_ox = cursor.x;
|
|
$scope.mouse_oy = cursor.y;
|
|
|
|
if (evt.which == 2 || evt.buttons == 4) {
|
|
$scope.active_tool = "pan";
|
|
}
|
|
|
|
if ($scope.active_tool=="note") {
|
|
this.deselect();
|
|
this.mouse_state = "transform";
|
|
$scope.mouse_state = this.mouse_state;
|
|
this.start_adding_note(evt);
|
|
return;
|
|
|
|
} else if ($scope.active_tool=="arrow") {
|
|
this.deselect();
|
|
this.mouse_state = "vector_transform";
|
|
$scope.mouse_state = this.mouse_state;
|
|
this.start_drawing_arrow(evt);
|
|
return;
|
|
|
|
} else if ($scope.active_tool=="line") {
|
|
this.deselect();
|
|
this.mouse_state = "vector_transform";
|
|
$scope.mouse_state = this.mouse_state;
|
|
this.start_drawing_line(evt);
|
|
return;
|
|
|
|
} else if ($scope.active_tool=="scribble") {
|
|
this.deselect();
|
|
this.mouse_state = "scribble";
|
|
$scope.mouse_state = this.mouse_state;
|
|
this.start_drawing_scribble(evt);
|
|
return;
|
|
|
|
} else if ($scope.active_tool=="zoom") {
|
|
if (evt.altKey) {
|
|
$scope.zoom_out();
|
|
} else {
|
|
$scope.zoom_in();
|
|
}
|
|
return;
|
|
|
|
} else if ($scope.active_tool=="pointer") {
|
|
this.mouse_state = "lasso";
|
|
this.start_lasso(evt);
|
|
} else if ($scope.active_tool=="zone") {
|
|
this.deselect();
|
|
this.mouse_state = "transform";
|
|
$scope.start_adding_zone(evt);
|
|
return;
|
|
} else if ($scope.active_tool=="image") {
|
|
this.deselect();
|
|
this.mouse_state = "transform";
|
|
$scope.start_adding_placeholder(evt);
|
|
return;
|
|
} else if ($scope.active_tool=="pan") {
|
|
this.start_pan(evt);
|
|
return;
|
|
}
|
|
|
|
if ($scope.selection_metrics.count>0) {
|
|
this._no_artifact_toolbar_this_round = true;
|
|
}
|
|
|
|
this.deselect();
|
|
},
|
|
|
|
start_pan: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
el = $("#space")[0];
|
|
if (el) {
|
|
this.mouse_state = "pan";
|
|
this.old_panx = el.scrollLeft;
|
|
this.old_pany = el.scrollTop;
|
|
}
|
|
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
$scope.mouse_ox = cursor.x;
|
|
$scope.mouse_oy = cursor.y;
|
|
$scope.mouse_moved = false;
|
|
},
|
|
|
|
deselect: function() {
|
|
var $scope = this.vm.$root;
|
|
|
|
$scope.deselect();
|
|
},
|
|
|
|
select: function(evt, a) {
|
|
var $scope = this.vm.$root;
|
|
|
|
$scope.select(evt, a);
|
|
},
|
|
|
|
multi_select: function(arts) {
|
|
var $scope = this.vm.$root;
|
|
|
|
$scope.multi_select(arts);
|
|
},
|
|
|
|
start_lasso: function(evt) {
|
|
var point = this.cursor_point_to_space(evt);
|
|
this.lasso = {
|
|
x: point.x,
|
|
y: point.y,
|
|
w: 0,
|
|
h: 0
|
|
}
|
|
},
|
|
|
|
rects_intersecting: function(r1,r2) {
|
|
if (!r1 || !r2) return false;
|
|
|
|
if ( (r1.x+r1.w < r2.x)
|
|
|| (r1.x > r2.x+r2.w)
|
|
|| (r1.y+r1.h < r2.y)
|
|
|| (r1.y > r2.y+r2.h) ) return false;
|
|
return true;
|
|
},
|
|
|
|
artifacts_in_rect: function(rect) {
|
|
if (!rect) return [];
|
|
|
|
var $scope = this.vm.$root;
|
|
|
|
return _.filter($scope.active_space_artifacts, function(a) {
|
|
return this.rects_intersecting(a, rect);
|
|
}.bind(this));
|
|
},
|
|
|
|
abs_rect: function(rect) {
|
|
var res = {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
w: Math.abs(rect.w),
|
|
h: Math.abs(rect.h)
|
|
}
|
|
if (rect.w<0) res.x+=rect.w;
|
|
if (rect.h<0) res.y+=rect.h;
|
|
return res;
|
|
},
|
|
|
|
lasso_style: function() {
|
|
var $scope = this.vm.$root;
|
|
|
|
if (!this.lasso) return "";
|
|
var lasso_scaled = {
|
|
x:this.lasso.x,
|
|
y:this.lasso.y,
|
|
w:this.lasso.w*$scope.viewport_zoom,
|
|
h:this.lasso.h*$scope.viewport_zoom
|
|
}
|
|
lasso_scaled = this.abs_rect(lasso_scaled);
|
|
lasso_scaled.x += $scope.bounds_margin_horiz;
|
|
lasso_scaled.y += $scope.bounds_margin_vert;
|
|
|
|
var s = "left:" +lasso_scaled.x+"px;";
|
|
s += "top:" +lasso_scaled.y+"px;";
|
|
s += "width:" +lasso_scaled.w+"px;";
|
|
s += "height:"+lasso_scaled.h+"px;";
|
|
s += "opacity: 1;";
|
|
return s;
|
|
},
|
|
|
|
render_lasso: function() {
|
|
if (!this.lasso) {
|
|
$("#lasso").hide();
|
|
return;
|
|
}
|
|
|
|
$("#lasso").attr("style", this.lasso_style());
|
|
$("#lasso").show();
|
|
},
|
|
|
|
cursor_point_to_space: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
var offset = {left: 0, top: 0};
|
|
|
|
evt = fixup_touches(evt);
|
|
|
|
return {
|
|
x: (parseInt(evt.pageX) - parseInt(offset.left) - $scope.bounds_margin_horiz) / this.space_zoom,
|
|
y: (parseInt(evt.pageY) - parseInt(offset.top) - $scope.bounds_margin_vert) / this.space_zoom
|
|
};
|
|
},
|
|
|
|
rect_to_points: function(rect) {
|
|
return [
|
|
{x:rect.x,y:rect.y},
|
|
{x:rect.x+rect.w,y:rect.y},
|
|
{x:rect.x,y:rect.y+rect.h},
|
|
{x:rect.x+rect.w,y:rect.y+rect.h}
|
|
];
|
|
},
|
|
|
|
old_selection_rect: function() {
|
|
var $scope = this.vm.$root;
|
|
|
|
var selected = $scope.selected_artifacts().map(function(a){
|
|
return $scope.find_artifact_before_transaction(a);
|
|
}.bind(this));
|
|
|
|
return $scope.enclosing_rect(selected);
|
|
},
|
|
|
|
snap_point: function(x,y,snap_middle) {
|
|
var $scope = this.vm.$root;
|
|
|
|
var TOL = 8;
|
|
var dists = [];
|
|
|
|
if (snap_middle) {
|
|
dists.push([[x-window.innerWidth/2,Math.abs(y-window.innerHeight/2)],[x-window.innerWidth/2,Math.abs(y-window.innerHeight/2)]]);
|
|
}
|
|
|
|
if ($scope.grid_active) {
|
|
// snap to grid
|
|
var gw = $scope.grid.spacing/$scope.grid.subdivisions;
|
|
var gh = $scope.grid.spacing/$scope.grid.subdivisions;
|
|
|
|
var sx1 = parseInt(x/gw)*gw;
|
|
var sy1 = parseInt(y/gh)*gh;
|
|
var sx2 = (parseInt(x/gw)+1)*gw;
|
|
var sy2 = (parseInt(y/gh)+1)*gh;
|
|
|
|
dists = [[[Math.abs(sx1-x),sx1], [Math.abs(sy1-y),sy1]],
|
|
[[Math.abs(sx2-x),sx2], [Math.abs(sy2-y),sy2]]];
|
|
|
|
} else {
|
|
|
|
// snap to other artifacts
|
|
|
|
dists = $scope.unselected_artifacts().map(function(a){
|
|
|
|
var r = this.rect_to_points(a);
|
|
|
|
var xd1 = Math.abs(r[0].x-x);
|
|
var xd2 = Math.abs(r[1].x-x);
|
|
var xd3 = Math.abs(r[0].x+a.w/2 - x);
|
|
|
|
var yd1 = Math.abs(r[0].y-y);
|
|
var yd2 = Math.abs(r[2].y-y);
|
|
var yd3 = Math.abs(r[0].y+a.h/2 - y);
|
|
|
|
if (!snap_middle) {
|
|
if (xd2<xd1) {
|
|
var xd = xd2;
|
|
var sx = r[1].x;
|
|
} else {
|
|
var xd = xd1;
|
|
var sx = r[0].x;
|
|
}
|
|
|
|
if (yd2<yd1) {
|
|
var yd = yd2;
|
|
var sy = r[2].y;
|
|
} else {
|
|
var yd = yd1;
|
|
var sy = r[0].y;
|
|
}
|
|
}
|
|
|
|
if (snap_middle) {
|
|
var xd = xd3;
|
|
var sx = r[0].x+a.w/2;
|
|
|
|
var yd = yd3;
|
|
var sy = r[0].y+a.h/2;
|
|
}
|
|
|
|
return [[xd,sx],[yd,sy]];
|
|
}.bind(this));
|
|
}
|
|
|
|
// snap to space edges
|
|
|
|
dists.push([[Math.abs(x),0],[Math.abs(y),0]]);
|
|
//dists.push([[Math.abs(dims.width-x),dims.width],[Math.abs(dims.height-y),dims.height]]);
|
|
|
|
var unzipped = _.unzip(dists);
|
|
var xdists = _.sortBy(unzipped[0], function(pair) {return pair[0];});
|
|
var ydists = _.sortBy(unzipped[1], function(pair) {return pair[0];});
|
|
|
|
var results = {snapx:xdists[0], snapy:ydists[0]};
|
|
if (!xdists[0] || xdists[0][0]>TOL) {
|
|
results.snapx = [0,x]; // distance, coordinate
|
|
} else {
|
|
//$scope.snap_ruler_x = xdists[0][1];
|
|
}
|
|
if (!ydists[0] || ydists[0][0]>TOL) {
|
|
results.snapy = [0,y];
|
|
} else {
|
|
//$scope.snap_ruler_y = ydists[0][1];
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
offset_point_in_wrapper: function(point) {
|
|
var $scope = this.vm.$root;
|
|
var section_el = $(this.el)[0];
|
|
var z = $scope.viewport_zoom;
|
|
|
|
var pt = parseInt($("#space").css("padding-top"));
|
|
|
|
point.y=(point.y+section_el.scrollTop-pt)/z;
|
|
point.x=(point.x+section_el.scrollLeft)/z;
|
|
|
|
return point;
|
|
},
|
|
|
|
start_drawing_scribble: function(evt) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
var $scope = this.vm.$root;
|
|
var point = this.offset_point_in_wrapper(this.cursor_point_to_space(evt));
|
|
var z = $scope.highest_z()+1;
|
|
|
|
$scope.deselect();
|
|
|
|
var a = {
|
|
space_id: $scope.active_space._id,
|
|
mime: "x-spacedeck/vector",
|
|
description: "",
|
|
control_points: [{dx:0,dy:0}],
|
|
x: point.x,
|
|
y: point.y,
|
|
z: z,
|
|
w: 64,
|
|
h: 64,
|
|
stroke_color: "#000000",
|
|
stroke: 2,
|
|
shape: "scribble"
|
|
};
|
|
|
|
$scope.save_artifact(a, function(saved_a) {
|
|
$scope.update_board_artifact_viewmodel(saved_a);
|
|
$scope.active_space_artifacts.push(saved_a);
|
|
|
|
this.select(evt,saved_a);
|
|
//$scope.tool_artifact = a;
|
|
$scope.transform_ox = 0;
|
|
$scope.transform_oy = 0;
|
|
$scope.begin_transaction();
|
|
}.bind(this));
|
|
},
|
|
|
|
start_drawing_arrow: function(evt) {
|
|
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
var $scope = this.vm.$root;
|
|
var point = this.cursor_point_to_space(evt);
|
|
this.offset_point_in_wrapper(point);
|
|
var z = $scope.highest_z()+1;
|
|
|
|
var a = {
|
|
space_id: $scope.active_space._id,
|
|
mime: "x-spacedeck/vector",
|
|
description: "",
|
|
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
|
|
x: point.x,
|
|
y: point.y,
|
|
z: z,
|
|
w: 64,
|
|
h: 64,
|
|
stroke_color: "#000000",
|
|
stroke: 2,
|
|
shape: "arrow"
|
|
};
|
|
|
|
$scope.save_artifact(a, function(saved_a) {
|
|
$scope.update_board_artifact_viewmodel(saved_a);
|
|
$scope.active_space_artifacts.push(saved_a);
|
|
$scope.select(evt,a);
|
|
$scope.selected_control_point_idx = 1;
|
|
$scope.transform_ox = 0;
|
|
$scope.transform_oy = 0;
|
|
$scope.begin_transaction();
|
|
}.bind(this));
|
|
},
|
|
|
|
// FIXME: consolidate with arrow drawing?
|
|
start_drawing_line: function(evt) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
var $scope = this.vm.$root;
|
|
var point = this.cursor_point_to_space(evt);
|
|
this.offset_point_in_wrapper(point);
|
|
var z = $scope.highest_z()+1;
|
|
|
|
var a = {
|
|
space_id: $scope.active_space._id,
|
|
mime: "x-spacedeck/vector",
|
|
description: "",
|
|
control_points: [{dx:0,dy:0},{dx:0,dy:0}],
|
|
x: point.x,
|
|
y: point.y,
|
|
z: z,
|
|
w: 64,
|
|
h: 64,
|
|
stroke_color: "#000000",
|
|
stroke: 2,
|
|
shape: "line"
|
|
};
|
|
|
|
$scope.save_artifact(a, function(saved_a) {
|
|
$scope.update_board_artifact_viewmodel(saved_a);
|
|
$scope.active_space_artifacts.push(saved_a);
|
|
$scope.select(evt,a);
|
|
$scope.selected_control_point_idx = 1;
|
|
$scope.transform_ox = 0;
|
|
$scope.transform_oy = 0;
|
|
$scope.begin_transaction();
|
|
}.bind(this));
|
|
},
|
|
|
|
snap_point_simple: function(point) {
|
|
var snapped = this.snap_point(point.x, point.y);
|
|
return {
|
|
x: snapped.snapx[1],
|
|
y: snapped.snapy[1]
|
|
}
|
|
},
|
|
|
|
handle_mouse_up_space: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
evt.preventDefault();
|
|
|
|
if (this.mouse_state == "lasso") {
|
|
var lasso_rect = this.abs_rect(this.offset_point_in_wrapper(this.lasso));
|
|
// convert to space coordinates
|
|
|
|
if (lasso_rect.w>0 && lasso_rect.h>0) {
|
|
var arts = this.artifacts_in_rect(lasso_rect);
|
|
this.multi_select(arts);
|
|
} else {
|
|
|
|
if (this._no_artifact_toolbar_this_round) {
|
|
this._no_artifact_toolbar_this_round = false;
|
|
} else {
|
|
$scope.start_adding_artifact(evt);
|
|
}
|
|
}
|
|
this.lasso = null;
|
|
this.render_lasso();
|
|
}
|
|
else if (_.include(["transform","move","vector_transform","scribble"],this.mouse_state)) {
|
|
var ars = $scope.selected_artifacts();
|
|
|
|
for (var i=0; i<ars.length; i++) {
|
|
|
|
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
|
|
// some types of artifact need a minimum size
|
|
if (ars[i].w<10) {
|
|
ars[i].w = 10;
|
|
}
|
|
if (ars[i].h<10) {
|
|
ars[i].h = 10;
|
|
}
|
|
}
|
|
|
|
//save_artifact(ars[i], null, $scope.display_saving_error);
|
|
}
|
|
}
|
|
|
|
if (this.mouse_state == "text_editor") {
|
|
return;
|
|
}
|
|
|
|
if (_.include(["zoom"], $scope.active_tool)) {
|
|
// tools that stay active after use
|
|
this.mouse_state = "idle";
|
|
$scope.mouse_state = this.mouse_state;
|
|
$scope.end_transaction();
|
|
$scope.deselect();
|
|
return;
|
|
}
|
|
|
|
this.mouse_state = "idle";
|
|
$scope.mouse_state = this.mouse_state;
|
|
this.lasso = null;
|
|
$scope.active_tool = "pointer";
|
|
$scope.end_transaction();
|
|
$scope.show_toolbar_props();
|
|
|
|
},
|
|
|
|
handle_mouse_leave: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
|
|
this.mouse_state = "idle";
|
|
this.lasso = null;
|
|
$scope.active_tool = "pointer";
|
|
$scope.end_transaction();
|
|
|
|
this.render_lasso();
|
|
},
|
|
|
|
handle_mouse_move: function(evt) {
|
|
var $scope = this.vm.$root;
|
|
if (!$scope.active_space) return;
|
|
|
|
if (!$scope.editing_artifact_id) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
}
|
|
|
|
$scope.handle_scroll();
|
|
|
|
var cursor = this.cursor_point_to_space(evt);
|
|
var dx = cursor.x - $scope.mouse_ox;
|
|
var dy = cursor.y - $scope.mouse_oy;
|
|
var dt = (new Date()).getTime() - this.last_mouse_move_time;
|
|
this.last_mouse_move_time = (new Date()).getTime();
|
|
|
|
var zoom = $scope.viewport_zoom||1;
|
|
if (zoom) {
|
|
dx/=zoom;
|
|
dy/=zoom;
|
|
}
|
|
|
|
// send cursor
|
|
if (dx>10 || dy>10 || dt>100) {
|
|
var name = "anonymous";
|
|
if ($scope.logged_in) {
|
|
name = $scope.user.nickname || $scope.user.email;
|
|
} else {
|
|
name = $scope.guest_nickname || "anonymous";
|
|
}
|
|
|
|
var cursor_msg = {
|
|
action: "cursor",
|
|
x: cursor.x/zoom,
|
|
y: cursor.y/zoom,
|
|
name: name,
|
|
id: $scope.user._id||name
|
|
};
|
|
|
|
$scope.websocket_send(cursor_msg);
|
|
}
|
|
|
|
// side effects ftw!
|
|
$scope.snap_ruler_x = -1000;
|
|
$scope.snap_ruler_y = -1000;
|
|
|
|
$scope.mouse_moved = true;
|
|
|
|
$scope.transform_lock = evt.shiftKey;
|
|
|
|
if ($scope.transform_lock) {
|
|
if (this.mouse_state == "transform") {
|
|
// lock aspect is done in transform
|
|
} else {
|
|
// lock axis
|
|
if (Math.abs(dy)>Math.abs(dx)) {
|
|
dx = 0;
|
|
} else {
|
|
dy = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.mouse_state == "move") {
|
|
$scope.hide_toolbar_props();
|
|
|
|
var snap_dx = 0;
|
|
var snap_dy = 0;
|
|
|
|
var selected = $scope.selected_artifacts();
|
|
var snap_edges = this.old_selection_rect();
|
|
|
|
if (selected.length && selected[0]._id==$scope.editing_artifact_id) {
|
|
// bail out of moving editable artifact
|
|
return;
|
|
}
|
|
|
|
if (snap_edges) {
|
|
var mx = snap_edges.x1 + (snap_edges.x2-snap_edges.x1)/2;
|
|
var my = snap_edges.y1 + (snap_edges.y2-snap_edges.y1)/2;
|
|
var snapped1 = this.snap_point(snap_edges.x1 + dx, snap_edges.y1 + dy, false);
|
|
var snapped2 = this.snap_point(snap_edges.x2 + dx, snap_edges.y2 + dy, false);
|
|
var snapped3 = this.snap_point(mx + dx, my + dy, true);
|
|
|
|
if (snapped3.snapx[0]>0) {
|
|
snap_dx = mx + dx - snapped3.snapx[1];
|
|
} else if (snapped2.snapx[0]>0) {
|
|
snap_dx = snap_edges.x2 + dx - snapped2.snapx[1];
|
|
} else {
|
|
snap_dx = snap_edges.x1 + dx - snapped1.snapx[1];
|
|
}
|
|
|
|
if (snapped3.snapy[0]>0) {
|
|
snap_dy = my + dy - snapped3.snapy[1];
|
|
} else if (snapped2.snapy[0]>0) {
|
|
snap_dy = snap_edges.y2 + dy - snapped2.snapy[1];
|
|
} else {
|
|
snap_dy = snap_edges.y1 + dy - snapped1.snapy[1];
|
|
}
|
|
}
|
|
|
|
$scope.update_selected_artifacts(function(a) {
|
|
var old_a = $scope.find_artifact_before_transaction(a);
|
|
|
|
if (old_a) {
|
|
return {
|
|
x: old_a.x + dx - snap_dx,
|
|
y: old_a.y + dy - snap_dy
|
|
};
|
|
} else {
|
|
// deleted?
|
|
return {};
|
|
}
|
|
}.bind(this));
|
|
|
|
} else if (this.mouse_state == "transform") {
|
|
var selected = $scope.selected_artifacts();
|
|
var edges = this.old_selection_rect();
|
|
|
|
if (!edges) {
|
|
this.mouse_state = "idle";
|
|
return;
|
|
}
|
|
|
|
$scope.hide_toolbar_props();
|
|
|
|
var ew = (edges.x2-edges.x1);
|
|
var eh = (edges.y2-edges.y1);
|
|
|
|
var origin_x = edges.x1 + ew * $scope.transform_ox;
|
|
var origin_y = edges.y1 + eh * $scope.transform_oy;
|
|
|
|
// "leading point"
|
|
var lead_x = edges.x1 + ew * (1-$scope.transform_ox) - origin_x;
|
|
var lead_y = edges.y1 + eh * (1-$scope.transform_oy) - origin_y;
|
|
|
|
var lead_snapped = this.snap_point(origin_x + lead_x + dx, origin_y + lead_y + dy);
|
|
var moved_x = (lead_snapped.snapx[1] - origin_x);
|
|
var moved_y = (lead_snapped.snapy[1] - origin_y);
|
|
|
|
var scale_x = lead_x ? (moved_x)/lead_x : 1;
|
|
var scale_y = lead_y ? (moved_y)/lead_y : 1;
|
|
if (!$scope.transform_lock) scale_y = scale_x;
|
|
|
|
$scope.update_selected_artifacts(function(a) {
|
|
var old_a = $scope.find_artifact_before_transaction(a);
|
|
|
|
var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
|
|
var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
|
|
var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
|
|
var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
|
|
|
|
if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
|
|
if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
|
|
|
|
return {
|
|
x: x1,
|
|
y: y1,
|
|
w: x2 - x1,
|
|
h: y2 - y1
|
|
};
|
|
}.bind(this));
|
|
|
|
} else if (this.mouse_state == "lasso") {
|
|
this.lasso.w = dx;
|
|
this.lasso.h = dy;
|
|
|
|
this.render_lasso();
|
|
|
|
} else if (this.mouse_state == "vector_transform") {
|
|
$scope.hide_toolbar_props();
|
|
|
|
var _this = this;
|
|
$scope.update_selected_artifacts(function(a) {
|
|
var old_a = $scope.find_artifact_before_transaction(a);
|
|
|
|
var control_points = _.cloneDeep(old_a.control_points);
|
|
var cp = control_points[$scope.selected_control_point_idx];
|
|
|
|
var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
|
|
dx = snapped.snapx[1]-(old_a.x+cp.dx);
|
|
dy = snapped.snapy[1]-(old_a.y+cp.dy);
|
|
|
|
cp.dx += dx;
|
|
cp.dy += dy;
|
|
|
|
// special case for arrow's 3rd point
|
|
if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
|
|
/*control_points[2].dx += dx/2;
|
|
control_points[2].dy += dy/2; */
|
|
|
|
control_points[2].dx = (control_points[0].dx+control_points[1].dx)/2;
|
|
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
|
|
}
|
|
|
|
return _this.normalize_control_points(control_points, old_a);
|
|
});
|
|
|
|
} else if (this.mouse_state == "scribble") {
|
|
|
|
$scope.update_selected_artifacts(function(a) {
|
|
var old_a = a;
|
|
|
|
var control_points = _.cloneDeep(old_a.control_points);
|
|
var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
|
|
|
|
control_points.push({
|
|
dx: offset.x-old_a.x,
|
|
dy: offset.y-old_a.y
|
|
});
|
|
|
|
return this.normalize_control_points(simplify_scribble_points(control_points), old_a);
|
|
}.bind(this));
|
|
|
|
var arts = $scope.selected_artifacts();
|
|
|
|
if (arts.length) {
|
|
$scope.update_board_artifact_viewmodel(arts[0]);
|
|
}
|
|
}
|
|
else if (this.mouse_state == "pan") {
|
|
if (!$("#space").length) return;
|
|
el = $("#space")[0];
|
|
|
|
el.scrollLeft = this.old_panx - dx*$scope.viewport_zoom;
|
|
el.scrollTop = this.old_pany - dy*$scope.viewport_zoom;
|
|
|
|
$scope.handle_scroll();
|
|
}
|
|
},
|
|
|
|
normalize_control_points: function(control_points, artifact) {
|
|
var x1 = _.min(control_points,"dx").dx;
|
|
var y1 = _.min(control_points,"dy").dy;
|
|
var x2 = _.max(control_points,"dx").dx;
|
|
var y2 = _.max(control_points,"dy").dy;
|
|
|
|
var shiftx = -x1;
|
|
var shifty = -y1;
|
|
|
|
var shifted_cps = control_points.map(function(cp) {
|
|
return {
|
|
dx: cp.dx + shiftx,
|
|
dy: cp.dy + shifty
|
|
};
|
|
});
|
|
|
|
var w = Math.abs(x2 - x1);
|
|
var h = Math.abs(y2 - y1);
|
|
|
|
var bshiftx = 0;
|
|
var bshifty = 0;
|
|
|
|
if (artifact.w < 0) bshiftx = -artifact.w;
|
|
if (artifact.h < 0) bshifty = -artifact.h;
|
|
|
|
return {
|
|
x: artifact.x + bshiftx - shiftx,
|
|
y: artifact.y + bshifty - shifty,
|
|
w: w,
|
|
h: h,
|
|
z: artifact.z,
|
|
control_points: shifted_cps
|
|
};
|
|
}
|
|
|
|
});
|
|
}
|