spacedeck-open/public/javascripts/spacedeck_whiteboard.js
2020-04-07 20:37:41 +02:00

1024 lines
29 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();
}
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 (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,
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
};
}
});
}