Files
spacedeck-open/public/javascripts/spacedeck_whiteboard.js
togir c491af64d8 Keep the colors between drawing multiple scribbles and arrows (#61)
* Keep the colors between drawing multiple scribbles and arrows

The color is changed back to black on every new drawing. Now the color is kept. If there is no color is set, then there is a fallback to the black stroke as before.

* requested changes

Co-authored-by: togir <gitcatch@weiler.rocks>
2020-07-22 21:53:51 +02:00

1038 lines
30 KiB
JavaScript

/*
Spacedeck Whiteboard Directive
This module registers a custom Vue directive that handles Whiteboard sections.
*/
function setup_whiteboard_directives() {
var mode_touch = false;
if ('ontouchstart' in window) {
mode_touch = true;
var edown = "touchstart";
var emove = "touchmove";
var eup = "touchend";
} else {
var edown = "mousedown";
var emove = "mousemove";
var eup = "mouseup";
}
// detect first touch event
window.addEventListener('touchstart', function on_first_touch() {
mode_touch = true;
window.removeEventListener('touchstart', on_first_touch, false);
}, false);
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("touchstart", this.handle_mouse_down_space.bind(this));
$(el).bind("touchmove", this.handle_mouse_move.bind(this));
$(el).bind("touchend", this.handle_mouse_up_space.bind(this));
$(el).bind("mousedown", this.handle_mouse_down_space.bind(this));
$(el).bind("mousemove", this.handle_mouse_move.bind(this));
$(el).bind("mouseup", 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();
}
if ($scope.active_tool == "zoom") return;
if (evt.which == 2) {
// middle mouse button
this.handle_mouse_down_space(evt);
return;
}
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
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.which != 2) {
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 ((mode_touch && $scope.active_tool=="pointer") || 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_drawing_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 {
// FIXME snap rulers are broken
//$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_note: 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: "text/html",
description: "<p>Text</p>",
x: point.x,
y: point.y,
z: z,
w: 64,
h: 64,
align: "center",
valign: "middle",
stroke_color: "#000000",
fill_color: "rgb(241, 196, 15)",
stroke: 0
};
$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.transform_ox = 0;
$scope.transform_oy = 0;
$scope.begin_transaction();
}.bind(this));
},
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,
// Set the color to the last selected color, fallback to black if no color was selected
stroke_color: "#" + $scope.color_picker_rgb || "#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,
// Set the color to the last selected color, fallback to black if no color was selected
stroke_color: "#" + $scope.color_picker_rgb || "#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", "scribble"], $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
};
}
});
}