c80_map_floors 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +3 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +67 -0
  8. data/Rakefile +1 -0
  9. data/app/admin/c80_map_floors/floors.rb +57 -0
  10. data/app/admin/c80_map_floors/map_buildings.rb +49 -0
  11. data/app/admin/c80_map_floors/settings.rb +32 -0
  12. data/app/assets/javascripts/buttons/admin_buttons/button_area_link.js +91 -0
  13. data/app/assets/javascripts/buttons/admin_buttons/button_building_link.js +91 -0
  14. data/app/assets/javascripts/buttons/admin_buttons/button_cancel_create.js +21 -0
  15. data/app/assets/javascripts/buttons/admin_buttons/button_cancel_remove.js +23 -0
  16. data/app/assets/javascripts/buttons/admin_buttons/button_complete_create.js +22 -0
  17. data/app/assets/javascripts/buttons/admin_buttons/button_edit.js +96 -0
  18. data/app/assets/javascripts/buttons/admin_buttons/button_new.js +46 -0
  19. data/app/assets/javascripts/buttons/admin_buttons/button_remove.js +23 -0
  20. data/app/assets/javascripts/buttons/admin_buttons/button_save.js +111 -0
  21. data/app/assets/javascripts/buttons/button_back_to_map.js +84 -0
  22. data/app/assets/javascripts/buttons/zoom_buttons.js +78 -0
  23. data/app/assets/javascripts/c80_map_floors.js.coffee +23 -0
  24. data/app/assets/javascripts/events/app_event.js +15 -0
  25. data/app/assets/javascripts/map_objects/area.js +251 -0
  26. data/app/assets/javascripts/map_objects/building.js +294 -0
  27. data/app/assets/javascripts/map_objects/dot.js +14 -0
  28. data/app/assets/javascripts/map_objects/floor.js +10 -0
  29. data/app/assets/javascripts/src/main.js +1421 -0
  30. data/app/assets/javascripts/src/state_controller.js +322 -0
  31. data/app/assets/javascripts/src/utils/map_utils.js +23 -0
  32. data/app/assets/javascripts/src/utils/opacity_buttons_utils.js +15 -0
  33. data/app/assets/javascripts/src/utils/utils.js +140 -0
  34. data/app/assets/javascripts/svg_elements/area_label.js +25 -0
  35. data/app/assets/javascripts/svg_elements/building_label.js +65 -0
  36. data/app/assets/javascripts/svg_elements/helper.js +36 -0
  37. data/app/assets/javascripts/svg_elements/polygon.js +194 -0
  38. data/app/assets/javascripts/view/save_preloader.js +30 -0
  39. data/app/assets/stylesheets/c80_map_floors.scss +4 -0
  40. data/app/assets/stylesheets/map.scss +1464 -0
  41. data/app/assets/stylesheets/view/buttons/area_order_button.scss +16 -0
  42. data/app/assets/stylesheets/view/buttons/back_to_map_button.scss +28 -0
  43. data/app/assets/stylesheets/view/elems/building_info.scss +54 -0
  44. data/app/assets/stylesheets/view/elems/free_areas_label.scss +116 -0
  45. data/app/assets/stylesheets/view/elems/map_objects/map_object_image_bg.scss +10 -0
  46. data/app/assets/stylesheets/view/modal_window.scss +13 -0
  47. data/app/assets/stylesheets/view/save_preloader.scss +206 -0
  48. data/app/controllers/c80_map_floors/ajax_controller.rb +65 -0
  49. data/app/controllers/c80_map_floors/application_controller.rb +6 -0
  50. data/app/controllers/c80_map_floors/map_ajax_controller.rb +70 -0
  51. data/app/helpers/c80_map_floors/application_helper.rb +28 -0
  52. data/app/models/c80_map_floors/area.rb +20 -0
  53. data/app/models/c80_map_floors/area_representator.rb +75 -0
  54. data/app/models/c80_map_floors/base_map_object.rb +41 -0
  55. data/app/models/c80_map_floors/building_representator.rb +72 -0
  56. data/app/models/c80_map_floors/floor.rb +37 -0
  57. data/app/models/c80_map_floors/map_building.rb +45 -0
  58. data/app/models/c80_map_floors/map_json.rb +35 -0
  59. data/app/models/c80_map_floors/setting.rb +32 -0
  60. data/app/uploaders/c80_map_floors/building_image_uploader.rb +31 -0
  61. data/app/uploaders/c80_map_floors/floor_image_uploader.rb +33 -0
  62. data/app/uploaders/c80_map_floors/map_image_uploader.rb +31 -0
  63. data/app/views/c80_map_floors/_map_row_index.html.erb +40 -0
  64. data/app/views/c80_map_floors/ajax/fetch_unlinked_areas.js.erb +7 -0
  65. data/app/views/c80_map_floors/ajax/fetch_unlinked_buildings.js.erb +7 -0
  66. data/app/views/c80_map_floors/ajax/map_edit_buttons.js.erb +36 -0
  67. data/app/views/c80_map_floors/ajax/shared/_map_creating.html.erb +6 -0
  68. data/app/views/c80_map_floors/ajax/shared/_map_editing.html.erb +4 -0
  69. data/app/views/c80_map_floors/ajax/shared/_map_removing.html.erb +5 -0
  70. data/app/views/c80_map_floors/ajax/shared/_select_list_unlinked_areas.html.erb +6 -0
  71. data/app/views/c80_map_floors/ajax/shared/_select_list_unlinked_buildings.html.erb +10 -0
  72. data/app/views/c80_map_floors/shared/_modal_window.html.erb +28 -0
  73. data/app/views/c80_map_floors/shared/_save_preloader.html.erb +3 -0
  74. data/app/views/c80_map_floors/shared/map_row/_area_order_button.html.erb +7 -0
  75. data/app/views/c80_map_floors/shared/map_row/_building_info.html.erb +17 -0
  76. data/bin/console +14 -0
  77. data/bin/setup +7 -0
  78. data/c80_map_floors.gemspec +28 -0
  79. data/config/routes.rb +4 -0
  80. data/db/migrate/20161015190000_create_c80_map_floors_settings.rb +8 -0
  81. data/db/migrate/20161015190001_create_c80_map_floors_map_buildings.rb +10 -0
  82. data/db/migrate/20161015190002_create_c80_map_floors_areas.rb +12 -0
  83. data/db/migrate/20161015190003_add_building_representator_to_c80_map_floors_buildings.rb +6 -0
  84. data/db/migrate/20161015190004_create_c80_map_floors_floors.rb +16 -0
  85. data/db/migrate/20161019111010_add_title_to_c80_map_floors_buildings.rb +5 -0
  86. data/db/migrate/20161020184040_add_coords_img_to_c80_map_floors_buildings.rb +5 -0
  87. data/db/seeds/c80_map_floors_01_fill_map_settings.rb +6 -0
  88. data/db/seeds/c80_map_floors_02_create_test_area.rb +7 -0
  89. data/lib/c80_map_floors.rb +8 -0
  90. data/lib/c80_map_floors/engine.rb +23 -0
  91. data/lib/c80_map_floors/version.rb +3 -0
  92. metadata +218 -0
@@ -0,0 +1,14 @@
1
+
2
+ function Dot() {
3
+
4
+ var _this = this;
5
+ var _options = null;
6
+ var _polygon = null;
7
+
8
+ _this.init = function (options,pself) {
9
+ console.log('<Dot.init>');
10
+ _options = options;
11
+ _polygon = Polygon.createFromSaved(options, false, pself);
12
+ }
13
+
14
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ function Floor() {
4
+
5
+ var _map = null;
6
+ var _options = null; // тот узел с данными, который приходит в JSON с сервера, C80MapFloors::Floor.as_json
7
+ var _this = this;
8
+ _this.id = null;
9
+
10
+ }
@@ -0,0 +1,1421 @@
1
+ "use strict";
2
+
3
+ var IS_ADMIN = false;
4
+ var map_on_index_page = null;
5
+
6
+ var InitMap = function (params) {
7
+
8
+ var dnd_enable = true;
9
+ if (params != undefined && params["dnd_enable"] != undefined) {
10
+ dnd_enable = params["dnd_enable"];
11
+ }
12
+
13
+ // - to delete start -----------------------------------------------------------------------------------------------------------------------
14
+ var scale = 0.599999;
15
+
16
+ var window_height = $(window).height() - 200;
17
+ if (window_height < 400) window_height = 400;
18
+
19
+ var window_width = $(window).width();
20
+ var image_width = MAP_WIDTH * scale;
21
+ var image_height = MAP_HEIGHT * scale;
22
+
23
+ var x = (window_width - image_width)/2;
24
+ var y = (window_height - image_height)/2;
25
+ // - to delete end -----------------------------------------------------------------------------------------------------------------------
26
+
27
+ map_on_index_page = $('#map_wrapper').beMap(
28
+ {
29
+ source:LOCS_HASH,
30
+ scale: scale,
31
+ x: x,
32
+ y: y,
33
+ mapwidth: MAP_WIDTH,
34
+ mapheight: MAP_HEIGHT,
35
+ height: window_height,
36
+ dnd_enable: dnd_enable
37
+ }
38
+ );
39
+
40
+ };
41
+
42
+ var clog = function () {
43
+ console.log(arguments[0]);
44
+ };
45
+
46
+ (function () {
47
+
48
+ var Map = function () {
49
+ var self = this;
50
+
51
+ self.debug = true;
52
+ self.o = {
53
+ source: 'locations.json', // data
54
+ height: 400, // viewbox height, pixels
55
+ mapwidth: 100, // actual image size, in pixels
56
+ mapheight: 100,
57
+ mapfill: true,
58
+ zoom: true,
59
+ zoombuttons: true,
60
+ maxscale: 1,
61
+ fitscale: 0.51,
62
+ skin: '', // css class name
63
+ scale: 1,
64
+ x: 0,
65
+ y: 0,
66
+ dnd_enable: true
67
+ };
68
+ self.svg = null;
69
+ self.svg_overlay = null;
70
+ self.container = null;
71
+ self.mode = 'viewing';
72
+ self.prev_mode = null;
73
+ self.setMode = null;
74
+ self.selected_area = null; // ссылка на полигон из #svg_overlay
75
+ self.drawing_poligon = null;
76
+ self.events = [];
77
+ self.edit_type = null;
78
+ self.remove_button_klass = null;
79
+ self.new_button_klass = null;
80
+ self.edit_button_klass = null;
81
+ self.complete_creating_button_klass = null;
82
+ self.back_to_map_button_klass = null;
83
+ self.current_building = null;
84
+ self.current_area = null;
85
+ self.is_draw = false;
86
+ self.save_button_klass = null;
87
+ self.area_link_button_klass = null;
88
+ self.drawn_areas = []; // если имеются нарисованные но несохранённые Площади - они хранятся тут
89
+ self.drawn_buildings = []; // если имеются нарисованные но несохранённые Здания - они хранятся тут
90
+ self.save_preloader_klass = null;
91
+ self.last_clicked_g = null; // начали просматривать area\building (запустили сессию), и здесь храним ссылку на последний кликнутый полигон из svg_overlay в течение сессии
92
+ self.dnd_enable = null; // если да, то можно карту dnd мышкой
93
+
94
+ // во время анимации каждый шаг рассчитывается мгновенный scale
95
+ self.scale_during_animation = null;
96
+
97
+ // true, если:
98
+ //- юзер еще не кликал по кнопкам zoom
99
+ //- юзер еще не делал drag-n-drop
100
+ //- юзер ещё не вошёл ни в здание ни в площадь
101
+ // Т.Е., другими словами, true до момента, пока пользователь не начал взаимодействовать с картой
102
+ self.mark_virgin = true;
103
+
104
+ // здесь сохранятся параметры для метода moveTo
105
+ // чтобы вернуть карту в исходное состояние после нажатия кнопки "назад на карту"
106
+ self.initial_map_position = null;
107
+
108
+ self.init = function (el, params) {
109
+ // extend options
110
+ self.o = $.extend(self.o, params);
111
+
112
+ self.x = self.o.x;
113
+ self.y = self.o.y;
114
+ self.scale = self.o.scale; /* NOTE:: инициализация: начальные значения */
115
+
116
+ self.el = el.addClass('melem mloading').addClass(self.o.skin).height(self.o.height);
117
+
118
+ // Disable modules when landmark mode is active
119
+ /*if (self.o.landmark) {
120
+ self.o.sidebar = false;
121
+ self.o.zoombuttons = false;
122
+ self.o.deeplinking = false;
123
+ }*/
124
+
125
+ if (typeof self.o.source === 'string') {
126
+ // Loading .json file with AJAX
127
+ $.getJSON(self.o.source, function (data) { // Success
128
+ initProcessData(data);
129
+ self.el.removeClass('mloading');
130
+ //setTimeout(invalidateZoom,1000);
131
+
132
+ }).fail(function () { // Failure: couldn't load JSON file, or it is invalid.
133
+ console.error('Couldn\'t load map data. (Make sure you are running the script through a server and not just opening the html file with your browser)');
134
+ self.el.removeClass('mloading').addClass('merror');
135
+ alert('Data file missing or invalid!');
136
+ });
137
+ }
138
+ else {
139
+ // Inline json object
140
+ initProcessData(self.o.source);
141
+ self.el.removeClass('mloading');
142
+ }
143
+
144
+
145
+ return self;
146
+
147
+
148
+ };
149
+
150
+ var initProcessData = function (data) {
151
+
152
+ self.data = data;
153
+
154
+ //var nrlevels = 0;
155
+ //var shownLevel;
156
+
157
+ self.container = $('.mcontainer'); //$('<div></div>').addClass('mcontainer').appendTo(self.el);
158
+ self.map = self.container.find('.mmap'); //$('<div></div>').addClass('mmap').appendTo(self.container);
159
+ if (self.o.zoom) self.map.addClass('mapplic-zoomable');
160
+ self.map_layers = self.map.find('.layers');
161
+ self.map_overlay_layers = self.map.find('.overlay_layers');
162
+
163
+ self.svg = $("#svg");
164
+ self.svg_overlay = $('#svg_overlay');
165
+ //$('<svg></svg>')
166
+ //.attr('xmlns','http://www.w3.org/2000/svg')
167
+ //.attr('version','1.2')
168
+ //.attr('baseProfile','tiny')
169
+ //.attr('id','svg')
170
+ //.appendTo(self.map);
171
+
172
+ self.levelselect = $('<select></select>').addClass('mlevels_select');
173
+
174
+ self.container.css('width', '100%'); // if (!self.o.sidebar)
175
+
176
+ self.contentWidth = parseInt(data.mapwidth);
177
+ self.contentHeight = parseInt(data.mapheight);
178
+
179
+ self.hw_ratio = data.mapheight / data.mapwidth;
180
+
181
+ self.map.css({
182
+ 'width': data.mapwidth,
183
+ 'height': data.mapheight
184
+ });
185
+
186
+ /* NOTE:: важная строка: определяет css и js поведение кое-где [qwwqq]:
187
+ 2016-10-19
188
+
189
+ 1. Всё начинается в JSON - в самом начале разработки проектировался JSON:
190
+ 2. там для root объекта указан был
191
+ руками
192
+ набор характеристик. Среди которых был и object_type[переименован в class_name].
193
+ 3. на тот момент object_type был равен строке "main_map".
194
+ 4. Затем эта строка (в процессе разработки) стала использоваться, как
195
+ имя css класса, и были накиданы стили для оформления.
196
+ 5. Затем, уже css класс был использован внутри StateController.js, который
197
+ работает с состоянием приложения и видимостью детей '.main_map'.
198
+
199
+ По хорошему - надо убрать из JSON этот параметр,
200
+ а здесь рукми напишем строку 'main_map', что и сделаем.
201
+ */
202
+ // Create new map layer
203
+ var layer = $('<div></div>')
204
+ .addClass('mlayer')
205
+ //.addClass(data["class_name"])/* [qwwqq] */
206
+ .addClass("main_map")/* [qwwqq] */
207
+ .appendTo(self.map_layers); // .hide()
208
+ $('<img>').attr('src', data["img"]).addClass('mmap-image').appendTo(layer);
209
+
210
+ // Zoom buttons
211
+ if (self.o.zoombuttons) {
212
+ self.zoombuttons = new ZoomButtons();
213
+ self.zoombuttons.init({height: self.o.height}, self);
214
+ //if (!self.o.clearbutton) self.zoombuttons.el.css('bottom', '0');
215
+ }
216
+
217
+ var sc = new StateController();
218
+ sc.init(self);
219
+ self.setMode = sc.setMode;
220
+
221
+ // Admin buttons
222
+ $.ajax({
223
+ url: '/ajax/map_edit_buttons',
224
+ type: 'POST',
225
+ dataType: 'script',
226
+ data: {
227
+ div_css_selector: '#container_buttons .mzoom_buttons'
228
+ }
229
+ }).done(function () {
230
+ clog('<ajax.done>');
231
+
232
+ self.edit_button_klass = new EditButton();
233
+ self.edit_button_klass.init('.mapplic-edit-button', self);
234
+
235
+ var e = new NewButton();
236
+ e.init('.mapplic-new-button', self);
237
+ self.new_button_klass = e;
238
+
239
+ e = new RemoveButton();
240
+ e.init('.mapplic-remove-button', self);
241
+ self.remove_button_klass = e;
242
+
243
+ e = new CancelRemoveButton();
244
+ e.init('#cancelRemoving', self);
245
+
246
+ e = new CancelCreatingButton();
247
+ e.init("#cancelCreating", self);
248
+
249
+ e = new CompleteCreatingButton();
250
+ e.init("#completeCreating", self);
251
+
252
+ self.save_button_klass = new SaveChangesButton();
253
+ self.save_button_klass.init('.mapplic-save-button', self);
254
+
255
+ // при клике на эту кнопку произойдет показ модального окна
256
+ self.area_link_button_klass = new AreaLinkButton();
257
+ self.area_link_button_klass.init('.mapplic-area-link-button', self);
258
+
259
+ // при клике на эту кнопку произойдет показ модального окна, в котором можно будет указать здание, соответствующее полигону
260
+ self.building_link_button_klass = new BuildingLinkButton();
261
+ self.building_link_button_klass.init('.mapplic-building-link-button', self);
262
+
263
+ $('[data-toggle="tooltip"]').tooltip();
264
+
265
+ });
266
+
267
+ // Controls
268
+ initAddControls();
269
+
270
+ self.draw_childs(data["buildings"]);
271
+
272
+ self.ivalidateViewArea();
273
+
274
+ // Browser resize
275
+ $(window).resize(function () {
276
+
277
+ // Mobile
278
+ //if ($(window).width() < 668) {
279
+ // self.container.height($(window).height() - 66);
280
+ //}
281
+ //else self.container.height('100%');
282
+
283
+ // Контейнер с картой должен слушать изменения габаритов окна и подгоняться по высоте
284
+ var window_height = $(window).height() - 200;
285
+ if (window_height < 400) window_height = 400;
286
+ self.el.height(window_height + "px");
287
+
288
+ // 20161003: заодно после редизайна необходимо позиционировать текстовый блок с адресом
289
+ var hh = _$address_p.outerHeight(true);
290
+ _$address_p.css('margin-top',(window_height - hh - 200)+"px");
291
+
292
+ // ------------------------------------------------------------------------------------------------------------------------
293
+
294
+ // если пользователь еще не взаимодействовал с картой или вне здания\площади
295
+ // вписываем картинку карты в главный прямоугольник карты
296
+ // т.е. меняем масштаб
297
+ if (self.mark_virgin) {
298
+ // рассчитаем масштаб, при котором можно вписать главный прямоугольник карты в прямоугольник рабочей области
299
+ var scaleX = self.calcScale(self.o.mapwidth*0.05, self.o.mapwidth *.95, self.X10, self.X20);
300
+ var scaleY = self.calcScale(self.o.mapheight*0.05, self.o.mapheight *.95, self.Y10, self.Y20);
301
+ var scale = (scaleX < scaleY) ? scaleX : scaleY;
302
+ self.scale = scale; /* NOTE:: вызывается во время window resize */
303
+ }
304
+
305
+ // совмещаем точку на экране, в которую надо центрировать карту,
306
+ // с центром карты (или с центром здания\площади, в котором находится юзер),
307
+ // с учётом рассчитанного масштаба
308
+
309
+ // если пользователь еще не взаимодействовал с картой или вне здания\площади,
310
+ // то фокусируемся на центр карты,
311
+ // иначе - фокусируемся на центр здания\площади, в котором находится пользователь и
312
+ // фокус сдвигаем, с учётом того, что сбоку открыта панель с информацией о здании
313
+
314
+ // NOTE-25::хардкод
315
+ // логические координаты - геометрический центр картинки
316
+ var cx = self.o.mapwidth/2;
317
+ var cy = self.o.mapheight/2;
318
+ var mark_do_moving = false;
319
+
320
+ if (self.current_building) {
321
+ cx = self.current_building.cx();
322
+ cy = self.current_building.cy();
323
+ mark_do_moving = true;
324
+ } else if (self.current_area) {
325
+ cx = self.current_area.cx();
326
+ cy = self.current_area.cy();
327
+ mark_do_moving = true;
328
+ } else if (self.mark_virgin) {
329
+ mark_do_moving = true;
330
+ }
331
+
332
+ if (mark_do_moving) {
333
+ self.x = self.normalizeX({
334
+ x: self.CX - self.scale * cx - self.container.offset().left,
335
+ scale: self.scale
336
+ });
337
+ self.y = self.normalizeY({
338
+ y: self.CY - self.scale * cy - self.container.offset().top,
339
+ scale: self.scale
340
+ });
341
+ clog("<$(window).resize> call moveTo");
342
+ self.moveTo(self.x, self.y, self.scale, 100);
343
+
344
+ // если пользователь ещё не взаимодействовал с картой (т.е. она только загрузилась и готова к использованию)
345
+ // запомним позицию, чтобы при нажатии на кнопку "назад на карту" происходил возврат с исходному
346
+ // состоянию
347
+ if (self.mark_virgin) {
348
+ self.initial_map_position = {
349
+ x: self.x,
350
+ y: self.y,
351
+ scale: self.scale
352
+ }
353
+ }
354
+
355
+ }
356
+
357
+ // ------------------------------------------------------------------------------------------------------------------------
358
+
359
+
360
+ self.ivalidateViewArea();
361
+
362
+ }).resize();
363
+
364
+ };
365
+
366
+ var initAddControls = function () {
367
+ var map = self.map,
368
+ mapbody = $('.mmap-image', self.map);
369
+
370
+ document.ondragstart = function () {
371
+ return false;
372
+ }; // IE drag fix
373
+
374
+ function onSvgMousedown(e) {
375
+ clog("<onSvgMousedown> self.mode = " + self.mode);
376
+
377
+ if (self.mode === 'editing' || self.mode === "edit_building" || self.mode === 'edit_area') {
378
+ if (e.target.parentNode.tagName === 'g') {
379
+ clog("<onSvgMousedown> e = ");
380
+ //clog(e.pageX);
381
+ //clog("<mouseDown> e.target.parentNode.tagName = " + e.target.parentNode.tagName);
382
+ //clog(e.target);
383
+ //info.unload();
384
+
385
+ // запомним ссылку на "выбранную" область
386
+ self.selected_area = e.target.parentNode.obj;
387
+
388
+ //app.deselectAll();
389
+
390
+ // поменяем внешний вид полигона - добавим класс .selected
391
+ self.selected_area.select();
392
+
393
+ // запомним начальные координаты кликаы
394
+ self.selected_area.delta = {
395
+ 'x': e.pageX,
396
+ 'y': e.pageY
397
+ };
398
+
399
+ // если взаимодействуем с вершиной
400
+ if (utils.hasClass(e.target, 'helper')) {
401
+ var helper = e.target;
402
+ //clog("<mouseDown> helper.action = ");
403
+ //clog(helper.action);
404
+ self.edit_type = helper.action; // pointMove
405
+
406
+ if (helper.n >= 0) { // if typeof selected_area == polygon
407
+ self.selected_area.selected_point = helper.n;
408
+ }
409
+
410
+ self.addEvent(self.el[0], 'mousemove', self.onEdit)
411
+ //self.addEvent(self.el[0], 'mousemove', self.selected_area.onEdit)
412
+ .addEvent(self.el[0], 'mouseup', self.onEditStop);
413
+ }
414
+
415
+ else if (e.target.tagName === 'rect' || e.target.tagName === 'circle' || e.target.tagName === 'polygon') {
416
+ self.edit_type = 'move';
417
+ self.addEvent(self.el[0], 'mousemove', self.onEdit)
418
+ .addEvent(self.el[0], 'mouseup', self.onEditStop);
419
+ }
420
+ } else {
421
+ //app.deselectAll();
422
+ //info.unload();
423
+ }
424
+ }
425
+ }
426
+
427
+ self.svg.on('mousedown', onSvgMousedown);
428
+ //self.el[0].addEventListener('mousedown', onSvgMousedown, false);
429
+
430
+
431
+ // Drag & drop
432
+ function onDragNdrop(event) {
433
+ //clog("<mousedown> edit_type = " + self.edit_type);
434
+ clog("<mousedown> mode = " + self.mode);
435
+ //clog(event);
436
+
437
+ // если в данный момент не редактируем фигуру (т.е. не двигаем вершину фигуры)
438
+ if (self.edit_type == null) {
439
+ self.dragging = false;
440
+ map.stop();
441
+
442
+ map.data('mouseX', event.pageX);
443
+ map.data('mouseY', event.pageY);
444
+ map.data('lastX', self.x);
445
+ map.data('lastY', self.y);
446
+ map.data('startX', self.x);
447
+ map.data('startY', self.y);
448
+
449
+ map.addClass('mdragging');
450
+
451
+ self.map.on('mousemove', function (event) {
452
+ self.dragging = true;
453
+
454
+ if (self.dnd_enable) {
455
+ var x = event.pageX - map.data('mouseX') + self.x;
456
+ var y = event.pageY - map.data('mouseY') + self.y;
457
+
458
+ x = self.normalizeX({
459
+ x:x,
460
+ scale: self.scale
461
+ });
462
+ y = self.normalizeY({
463
+ y:y,
464
+ scale: self.scale
465
+ });
466
+
467
+ //clog("<Map.mousemove> x = " + x + "; y = " + y);
468
+ //clog("<Map.mousemove> Call moveTo.");
469
+ self.moveTo(x, y); /* NOTE:: вызывается во время dnd */
470
+ map.data('lastX', x);
471
+ map.data('lastY', y);
472
+ }
473
+ });
474
+
475
+ $(document).on('mouseup', function (event) {
476
+ //clog("<mouseup> dragging = " + self.dragging + ", mode = " + self.mode + "; is_draw = " + self.is_draw + "; scale = " + self.scale);
477
+ //clog("<mouseup> event = ");
478
+ //clog(event);
479
+ //clog("<mouseup> event.target = ");
480
+ //clog($(event.target).parent()[0].obj);
481
+
482
+ //clog("<mouseup> [qq] screen: " + event.pageX + ", " + event.pageY +
483
+ //"; logic: " + self.rightX(event.pageX) + ", " + self.rightY(event.pageY));
484
+
485
+ clog("<mouseup> self.mode = " + self.mode);
486
+
487
+ // исключаем случайный dnd дрожащей рукой
488
+ var dx = map.data('startX') - map.data('lastX');
489
+ var dy = map.data('startY') - map.data('lastY');
490
+ var delta = Math.sqrt(dx*dx + dy*dy);
491
+ var is_real_dragging = delta > 10;
492
+
493
+ // если это в самом деле был drag\n\drop
494
+ if (self.dragging && is_real_dragging) {
495
+
496
+ self.x = map.data('lastX');
497
+ self.y = map.data('lastY');
498
+ }
499
+
500
+ // иначе - пытаемся выяснить, в каком режиме находимся
501
+ else {
502
+
503
+ var p;
504
+
505
+ /* если находимся в режиме просмотра всей карты - входим в здание */
506
+ if (self.mode == 'viewing') {
507
+ //clog($(event.target).parent()[0].obj.building);
508
+
509
+ // добираемся до объекта класса Здание, который обслуживает полигон
510
+ p = $(event.target).parent()[0];
511
+ if (p.obj && p.obj.building) {
512
+ var building = p.obj.building;
513
+ clog("<mouseup> Входим в здание.");
514
+ building.enter();
515
+ }
516
+
517
+ }
518
+
519
+ /* если находимся в режиме рисования - рисуем */
520
+ else if (self.mode == 'creating') {
521
+
522
+ // и если ещё пока не начали рисовать (т.е. если это первый клик)
523
+ if (!self.is_draw) {
524
+
525
+ var xx = self.rightX(event.pageX);
526
+ var yy = self.rightY(event.pageY);
527
+ //clog("<mouseup> " + xx + "; " + yy);
528
+
529
+ self.drawing_poligon = new Polygon(xx, yy, false, self);
530
+
531
+ //self.addEvent(self.el[0], 'mousemove', self.drawing_poligon.onDraw)
532
+ self.addEvent(self.el[0], 'mousemove', function (e) {
533
+ var _n_f = self.drawing_poligon;
534
+ var right_angle = !!e.shiftKey; //e.shiftKey ? true : false;
535
+
536
+ _n_f.dynamicDraw(self.rightX(e.pageX), self.rightY(e.pageY), right_angle);
537
+ })
538
+ //.addEvent(self.drawing_poligon.helpers[0].helper, 'click', self.drawing_poligon.onDrawStop)
539
+ //.addEvent(self.el[0], 'click', self.drawing_poligon.onDrawAddPoint);
540
+ .addEvent(self.el[0], 'click', function (e) {
541
+
542
+ // если кликнули в первую точку фигуры - заканчиваем рисование
543
+ var $et = $(e.target);
544
+ var $h = $(self.drawing_poligon.helpers[0].helper);
545
+ if ($et.attr('x') == $h.attr('x') && $et.attr('y') == $h.attr('y')) {
546
+ //self.drawing_poligon.onDrawStop();
547
+ self.onDrawStop();
548
+ return;
549
+ }
550
+
551
+ var x = self.rightX(e.pageX),
552
+ y = self.rightY(e.pageY),
553
+ _n_f = self.drawing_poligon;
554
+
555
+ if (e.shiftKey) {
556
+ var right_coords = _n_f.right_angle(x, y);
557
+ x = right_coords.x;
558
+ y = right_coords.y;
559
+ }
560
+ _n_f.addPoint(x, y);
561
+ })
562
+ .addEvent(document, 'keydown', self.onDrawStop);
563
+ }
564
+ }
565
+
566
+ /* если находимся в режиме просмотра здания - входим в площадь */
567
+ /* если находится в режиме просмотра площади - переключаемся на другую площадь */
568
+ else if (self.mode == 'view_building' || self.mode == 'view_area') {
569
+
570
+ //console.log($(event.target).parent());
571
+ // => g, который живёт в #svg_overlay, или, другими словами,
572
+ // тот g, по которому кликнули последний раз,
573
+ // просматривая либо здание, либо площадь
574
+ var $viewing_g_from_svg_overlay = $(event.target).parent();
575
+
576
+ // добираемся до объекта класса Area, который обслуживает полигон
577
+ p = $viewing_g_from_svg_overlay[0];
578
+ //clog($(event.target).parent()[0].obj.area_hash);
579
+
580
+ if (p.obj && p.obj.area) {
581
+
582
+ // запомним последний кликнутый полигон
583
+ self.last_clicked_g = $viewing_g_from_svg_overlay;
584
+
585
+ var area = p.obj.area;
586
+ clog("<mouseup> Входим в площадь. self.last_clicked_g = ");
587
+ clog(self.last_clicked_g);
588
+ area.enter();
589
+ }
590
+
591
+ }
592
+ }
593
+
594
+ self.map.off('mousemove');
595
+ $(document).off('mouseup');
596
+
597
+ map.removeClass('mdragging');
598
+ });
599
+ }
600
+ }
601
+
602
+ self.svg.on('mousedown', onDragNdrop);
603
+ self.svg_overlay.on('mousedown', onDragNdrop);
604
+
605
+ self.el[0].addEventListener('mousemove', function (e) {
606
+ //coords_info.innerHTML = 'x: ' + rightX(e.pageX) + ', ' + 'y: ' + rightY(e.pageY);
607
+ }, false);
608
+
609
+ self.el[0].addEventListener('mouseleave', function () {
610
+ //coords_info.innerHTML = '';
611
+ }, false);
612
+
613
+ /* Disable selection */
614
+ //self.el[0].addEventListener('mousedown', function(e) { e.preventDefault(); }, false);
615
+
616
+ /* Disable image dragging */
617
+ self.el[0].addEventListener('dragstart', function (e) {
618
+ e.preventDefault();
619
+ }, false);
620
+
621
+ self.back_to_map_button_klass = new BackToMapButton();
622
+ self.back_to_map_button_klass.init("#ui", self);
623
+
624
+ self.save_preloader_klass = new SavePreloader();
625
+ self.save_preloader_klass.init();
626
+
627
+ };
628
+
629
+ // какой должен быть минимальный масштаб, чтобы вписать отрезок [min,max] в отрезок [p1,p2]
630
+ self.calcScale = function (min, max, p1, p2) {
631
+ //clog("<calcScale> [" + min + "," + max + '] to [' + p1 + "," + p2 + "]");
632
+ return (p2 - p1) / (max - min);
633
+ };
634
+
635
+ self.calcCoord = function (scale, pageC, logicC) {
636
+ return pageC - scale * logicC;
637
+ };
638
+
639
+ /* --- ivalidateViewArea BEGIN --------------------------------------------------------------------------------- */
640
+
641
+ var _$m = $("#map_wrapper");
642
+ var _$b = $('.container');//$('footer .container');
643
+ var $building_info = $('.building_info'); // "layouts/shared/map_row/building_info"
644
+ var $area_order_button = $('.area_order_button');
645
+ var $container_buttons = $('#container_buttons');
646
+ var _is_debug_drawn = true;
647
+ var _$address_p = $('#paddress'); // 20161003: после редизайна надо дополнительно позиционировать блок с адресом
648
+
649
+ self.ivalidateViewArea = function () {
650
+ //clog('<init> _$b.offset().left = ' + _$b.offset().left);
651
+
652
+ // рассчитаем "константы" - прямоугольник, в который надо вписывать картинки зданий при входе в них
653
+ self.X1 = _$b.offset().left + 100;
654
+ self.X1S = _$b.offset().left + 200;
655
+ self.Y1 = 73;
656
+ self.Y1S = 140;
657
+ self.X2 = self.X1 + _$b.width() * .5;
658
+ self.X2S = self.X1 + _$b.width() * .4;
659
+ self.X3 = self.X1 + _$b.width() - 100;
660
+ self.Y2 = _$m.height() - 20;
661
+ self.Y2S = _$m.height() - 80;
662
+ self.CX = (self.X2 - self.X1) / 2 - 2 + self.X1;
663
+ self.CY = (self.Y2 - self.Y1) / 2 - 2 + self.Y1;
664
+
665
+ self.X10 = _$b.offset().left + 15;
666
+ self.X20 = self.X10 + _$b.width();
667
+ self.Y10 = 73;
668
+ self.Y20 = _$m.height();
669
+
670
+ // позиционируем элементы
671
+ $building_info.css("left", self.X2 + "px");
672
+ $area_order_button.css("left", self.X2 + "px");
673
+ if (self.container) $container_buttons.css("margin-top", (self.container.height() -10) + "px");
674
+
675
+ // DEBUG
676
+ if (self.debug) {
677
+
678
+ if (!_is_debug_drawn) {
679
+ _is_debug_drawn = true;
680
+
681
+ var style = "display:block;position:absolute;background-color:#00ff00;opacity:0.4;";
682
+ var style_x = style + "width:1px;height:800px;top:0;left:{X}px;";
683
+ var style_y = style + "width:3000px;height:1px;left:0;top:{Y}px;";
684
+ var style_dot = style + 'width:4px;height:4px;left:{X}px;top:{Y}px;';
685
+
686
+ var to_draw = [
687
+ {x: self.X10},
688
+ {x: self.X20},
689
+ {y: self.Y10},
690
+ {y: self.Y20},
691
+ {x: self.CX},
692
+ {y: self.CY},
693
+ ];
694
+
695
+
696
+ var i, istyle, xx, yy, ip;
697
+ for (i = 0; i < to_draw.length; i++) {
698
+ ip = to_draw[i];
699
+
700
+ if (ip.x != undefined) {
701
+ istyle = style_x.split("{X}").join(ip.x);
702
+ } else if (ip.y != undefined) {
703
+ istyle = style_y.split("{Y}").join(ip.y);
704
+ }
705
+
706
+ _$m.append($("<div style=" + istyle + "></div>"));
707
+ }
708
+
709
+ }
710
+
711
+ }
712
+
713
+ };
714
+
715
+ /* --- ivalidateViewArea END ----------------------------------------------------------------------------------- */
716
+
717
+ self.addEvent = function (target, eventType, func) {
718
+ self.events.push(new AppEvent(target, eventType, func));
719
+ return self;
720
+ };
721
+
722
+ self.removeAllEvents = function () {
723
+ utils.foreach(self.events, function (x) {
724
+ x.remove();
725
+ });
726
+ self.events.length = 0;
727
+ self.edit_type = null;
728
+ return this;
729
+ };
730
+
731
+ self.addNodeToSvg = function (node, is_overlay) {
732
+ if (is_overlay) {
733
+ self.svg_overlay[0].appendChild(node);
734
+ } else {
735
+ self.svg[0].appendChild(node);
736
+ }
737
+ return self;
738
+ };
739
+
740
+ self.removeNodeFromSvg = function(node, is_overlay) {
741
+ if (is_overlay) {
742
+ self.svg_overlay[0].removeChild(node);
743
+ } else {
744
+ self.svg[0].removeChild(node);
745
+ }
746
+ return this;
747
+ };
748
+
749
+ self.svgRemoveAllNodes = function () {
750
+ self.svg.empty();
751
+ self.svg_overlay.empty();
752
+ };
753
+
754
+ /** Нарисовать на карте объекты из массива childs.
755
+ *
756
+ * Массив childs содержит наборы однотипных объектов.
757
+ * Т.е. объекты только одного типа приходят в фукнцию.
758
+ * Типы могут быть: C80MapFloors::MapBuilding,C80MapFloors::Area
759
+ *
760
+ * Если мы рисуем набор Площадей C80MapFloors::Area, то:
761
+ * - это означает, что мы вошли в Здание C80MapFloors::Building.
762
+ * - parent_hash - это as_json объекта класса C80Rent:Building,
763
+ * который привязан к родителю отрисовываемого C80MapFloors::Area,
764
+ * (т.е. родитель - это C80MapFloors::Building).
765
+ * - И подаётся он для того, чтобы в окне с информацией о C80Rent:Area
766
+ * можно было отобразить характеристики Здания родителя C80Rent:Building.
767
+ */
768
+ self.draw_childs = function (childs, parent_hash) {
769
+ //clog("<Map.draw_childs>");
770
+
771
+ //var ip;
772
+ var iobj;
773
+ var ib, id, ia;
774
+ for (var i = 0; i < childs.length; i++) {
775
+ iobj = childs[i];
776
+
777
+ switch (iobj["class_name"]) { /* NOTE:: сопоставление Ruby класса и JS класса */
778
+ case 'C80MapFloors::MapBuilding':
779
+ ib = new Building();
780
+ ib.init(iobj,self);
781
+ break;
782
+ case 'dot':
783
+ id = new Dot();
784
+ id.init(iobj,self);
785
+ break;
786
+ case 'C80MapFloors::Area':
787
+ ia = new Area();
788
+ ia.init(iobj, parent_hash, self);
789
+ break;
790
+ case 'C80MapFloors::Floor':
791
+ ia = new Floor();
792
+ ia.init(iobj, parent_hash, self);
793
+ break;
794
+ }
795
+ //ip = Polygon.createFromSaved(iobj);
796
+ //utils.id('svg').appendChild(ip.g);
797
+ }
798
+ };
799
+
800
+ /**
801
+ * создаёт DOM элемент:
802
+ * <div class='mlayer #{obj_type}'>
803
+ * <img src='#{img_src}' class='mmap-image' />
804
+ * </div>
805
+ * и помещает его либо в map_overlay_layers, либо в map_layers (~ от параметра is_overlay)
806
+ */
807
+ self.draw_child_bg_image = function (img_src, obj_type, is_overlay) {
808
+ var t;
809
+ if (is_overlay == true) {
810
+ t = self.map_overlay_layers;
811
+ } else {
812
+ t = self.map_layers;
813
+ }
814
+ // Create new map layer
815
+ var layer = $('<div></div>').addClass('mlayer').addClass(obj_type).appendTo(t); // .hide()
816
+ $('<img>').attr('src', img_src).addClass('mmap-image').appendTo(layer);
817
+
818
+ return layer;
819
+ };
820
+
821
+ /**
822
+ * создаёт DOM элемент:
823
+ * <div class='map_object_image_bg'> // style='background-image:url(#{img_src});'
824
+ * <img src=#{img_src} />
825
+ * </div>
826
+ * и помещает его map_layers
827
+ *
828
+ * left и top - координаты bound box верхнего левого угла здания
829
+ *
830
+ */
831
+ self.draw_map_object_image_bg = function (img_src, params) {
832
+ console.log('<draw_map_object_image_bg>');
833
+
834
+ // породим DOM
835
+ var $div_map_object_image_bg = $('<div></div>')
836
+ .addClass('mlayer')
837
+ //.attr('style','background-image:url("'+img_src+'")')
838
+ .appendTo(self.map_layers); // .hide()
839
+
840
+ // сохраним начальные параметры в data
841
+ var left = params["x"];
842
+ var top = params["y"];
843
+ var width = params["width"];
844
+ var height = params["height"];
845
+
846
+ var $img = $('<img>')
847
+ .data('top', top)
848
+ .data('left', left)
849
+ .data('width', width)
850
+ .data('height', height)
851
+ .attr('src', img_src)
852
+ .addClass('map_object_image_bg') /* этот класс используем при [zoomove]*/
853
+ .appendTo($div_map_object_image_bg);
854
+
855
+ // рассчитаем позиционирующий стиль и применим его к созданной оверлейной картинке
856
+ self.__compose_css_style_for_map_object_image($img);
857
+
858
+ return $div_map_object_image_bg;
859
+
860
+ };
861
+
862
+ /**
863
+ * Задача этой служебной функции:
864
+ * - рассчёт актуальных (для данного масштаба) размеров и координат местонах указанного объекта (вместо объекта подаётся хэш описывающий его, с x,y,width,height)
865
+ * - составление css стиля для картинки с css-классом map_object_image_bg
866
+ * - присвоении этого стиля картинке
867
+ *
868
+ * Вызывается каждый шаг анимации и при входе в здание на первый этаж.
869
+ *
870
+ * Пользуется map.scale_during_animation при рассчётах
871
+ *
872
+ * @private
873
+ */
874
+ self.__compose_css_style_for_map_object_image = function ($img_with_class_map_object_image_bg) {
875
+
876
+ var $i = $img_with_class_map_object_image_bg;
877
+
878
+ // проведём калькуляцию [zoomove-calc]
879
+ var left = $i.data("left")*self.scale_during_animation;
880
+ var top = $i.data("top")*self.scale_during_animation;
881
+ var width = $i.data("width")*self.scale_during_animation;
882
+ var height = $i.data("height")*self.scale_during_animation;
883
+
884
+ // впишем в DOM стили
885
+ var style = 'top:';
886
+ style += top + 'px;';
887
+ style += "left:";
888
+ style += left + 'px;';
889
+ style += "width:";
890
+ style += width + 'px;';
891
+ style += "height:";
892
+ style += height + 'px;';
893
+
894
+ console.log("> scale: " + self.scale + "; style: " + style);
895
+ $i.attr('style',style);
896
+
897
+ };
898
+
899
+ self.onEdit = function (e) {
900
+
901
+ //clog("<Polygon.prototype.onEdit> _s_f = " + _s_f);
902
+ //clog("<Polygon.prototype.onEdit> e = ");
903
+ //clog(_s_f);
904
+ //clog(e.pageX);
905
+
906
+ var selected_area = self.selected_area;
907
+ var edit_type = self.edit_type;
908
+ //clog("<Polygon.prototype.onEdit> edit_type = " + edit_type);
909
+
910
+ selected_area.dynamicEdit(selected_area[edit_type](e.pageX - selected_area.delta.x, e.pageY - selected_area.delta.y));
911
+ selected_area.delta.x = e.pageX;
912
+ selected_area.delta.y = e.pageY;
913
+ };
914
+
915
+ self.onDrawStop = function (e) {
916
+ clog("<Map.onDrawStop>");
917
+
918
+ if (e != undefined) {
919
+ if (e.type == 'keydown' && e.keyCode == 13) {
920
+ // its ok, continue execution..
921
+ } else {
922
+ return
923
+ }
924
+ }
925
+
926
+ var _n_f = self.drawing_poligon;
927
+ if (_n_f.params.length >= 6) { //>= 3 points for polygon
928
+ _n_f.polyline = _n_f.polygon;
929
+ _n_f.polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
930
+ _n_f.g.replaceChild(_n_f.polygon, _n_f.polyline);
931
+ _n_f.setCoords(_n_f.params).deselect();
932
+ delete(_n_f.polyline);
933
+
934
+ // в зависимости от предыдущего состояния, создадим либо Здание, либо Площадь
935
+ if (self.prev_mode == "edit_building") {
936
+ var bo = self.current_building.options;
937
+ var a = new Area();
938
+ a.init({ coords:_n_f.params }, bo, self);
939
+ //a.is_new = true;
940
+ _n_f.remove(); // удаляем нарисованный полигон, т.к. его уже заменили полигоном Area
941
+ self.registerJustDrownArea(a);
942
+ }
943
+ else if (self.prev_mode == 'editing') {
944
+ var b = new Building();
945
+ b.init({ coords:_n_f.params }, self);
946
+ //b.is_new = true;
947
+ _n_f.remove(); // удаляем нарисованный полигон, т.к. его уже заменили полигоном Building
948
+ self.registerJustDrownBuilding(b);
949
+ }
950
+
951
+ self.removeAllEvents();
952
+ self.drawing_poligon = null;
953
+ self.is_draw = false;
954
+ }
955
+
956
+ self.setMode('editing');
957
+ };
958
+
959
+ self.onEditStop = function (e) {
960
+ //clog("<Polygon.prototype.onEditStop>");
961
+ var _s_f = self.selected_area,
962
+ edit_type = self.edit_type;
963
+
964
+ _s_f.setParams(_s_f.dynamicEdit(_s_f[edit_type](e.pageX - _s_f.delta.x, e.pageY - _s_f.delta.y)));
965
+
966
+ self.removeAllEvents();
967
+ };
968
+
969
+ self.registerJustDrownArea = function (area) {
970
+ self.drawn_areas.push(area);
971
+ };
972
+
973
+ self.registerJustDrownBuilding = function (building) {
974
+ self.drawn_buildings.push(building);
975
+ };
976
+
977
+ /**
978
+ * Зная ширину контейнера и контента,
979
+ * используя указанные параметры,
980
+ * рассчитать нормальный X
981
+ * @param x
982
+ * @param scale
983
+ * @returns {*}
984
+ */
985
+ self.normalizeX = function (params) {
986
+
987
+ var x = params['x'];
988
+ var scale = params['scale'];
989
+
990
+ var minX = self.container.width() - self.contentWidth * scale;
991
+
992
+ if (minX < 0) {
993
+ if (x > 0) x = 0;
994
+ else if (x < minX) x = minX;
995
+ }
996
+ else x = minX / 2;
997
+
998
+ return x;
999
+ };
1000
+
1001
+ /**
1002
+ * Зная высоту контейнера и контента,
1003
+ * используя указанные параметры,
1004
+ * рассчитать нормальный Y
1005
+ * @param y
1006
+ * @param scale
1007
+ * @returns {*}
1008
+ */
1009
+ self.normalizeY = function (params) {
1010
+
1011
+ var y = params['y'];
1012
+ var scale = params['scale'];
1013
+
1014
+ var minY = self.container.height() - self.contentHeight * scale;
1015
+
1016
+ if (minY < 0) {
1017
+ if (y >= 0) y = 0;
1018
+ else if (y < minY) y = minY;
1019
+ }
1020
+ else y = minY / 2;
1021
+
1022
+ return y;
1023
+ };
1024
+
1025
+ /**
1026
+ * Используя ограничения по масштабу нормализовать scale
1027
+ * @param scale
1028
+ * @returns {*}
1029
+ */
1030
+ self.normalizeScale = function (scale) {
1031
+ clog('<self.normalizeScale>' + self.o.fitscale);
1032
+ if (scale < self.o.fitscale) scale = self.o.fitscale;
1033
+ else if (scale > self.o.maxscale) scale = self.o.maxscale;
1034
+
1035
+ if (self.zoombuttons) self.zoombuttons.update(scale);
1036
+
1037
+ return scale;
1038
+ };
1039
+
1040
+ /*self.zoomTo = function (x, y, s, duration, easing, ry) {
1041
+ duration = typeof duration !== 'undefined' ? duration : 400;
1042
+ ry = typeof ry !== 'undefined' ? ry : 0.5;
1043
+
1044
+ // это значение нужно присвоить только после анимации
1045
+ self.scale = self.normalizeScale(self.o.fitscale * s);
1046
+
1047
+ self.x = self.normalizeX(self.container.width() * 0.5 - self.scale * self.contentWidth * x);
1048
+ self.y = self.normalizeY(self.container.height() * ry - self.scale * self.contentHeight * y);
1049
+
1050
+ clog("<Map.zoomTo> Call moveTo.");
1051
+ self.moveTo(self.x, self.y, self.scale, duration, easing);
1052
+ };*/
1053
+
1054
+ /**
1055
+ * задачи этой функции:
1056
+ * - быть контейнером кода
1057
+ * - считывать css атрибут self.map
1058
+ * - по регулярке извлекать left и top
1059
+ * - трансформировать эти значения
1060
+ * - изменить атрибут viewBox обоих svg слоёв
1061
+ *
1062
+ * Изначально была задумка каждый шаг анимации вызывать эту функцию.
1063
+ * Но затем во время оптимизации слои с svg стали видны только тогда,
1064
+ * когда анимация не проходит. По-этому этот код был поставлен на setTimeout
1065
+ *
1066
+ * Отличие этого кода от [qq1] лишь в механике вычисления (извлечения) нужных значений.
1067
+ * Скорее всего, это код-дубликат, который появился во время rush разработки.
1068
+ * Т.е. желателен рефакторинг и упрощение логики, но не сейчас.
1069
+ * */
1070
+ var __afterMovingCorrectSvgLayersPositions = function () {
1071
+ //clog(self.map.attr('style'));
1072
+ // left: -69.9985px; top: -299.999px;
1073
+ // left: [-]{0,1}(\d+\.\d+px);
1074
+
1075
+ var str = self.map.attr('style');
1076
+ var rx_left = /left: [-]{0,1}(\d+\.\d+)px;/;
1077
+ var rx_top = /top: ([-]{0,1}\d+\.\d+)px;/;
1078
+ var match_left = str.match(rx_left);
1079
+ var match_right = str.match(rx_top);
1080
+
1081
+ if (match_left != null && match_right != null) {
1082
+ var x = -1 * Number(match_left[1]); // ["left: -69.9985px;", "69.9985"]
1083
+ var y = -1 * Number(match_right[1]); // ["left: -69.9985px;", "69.9985"]
1084
+ var att = x + " " + y + " " + self.contentWidth + " " + self.contentHeight;
1085
+ //clog(x + "; y = " + y);
1086
+ self.svg.attr('viewBox', att);
1087
+ self.svg_overlay.attr('viewBox', att);
1088
+ }
1089
+
1090
+ };
1091
+
1092
+ /**
1093
+ * Вызывается после анимации moveTo
1094
+ * Задачей этого метода является :
1095
+ * - сохранение x,y,scale в данных карты,
1096
+ * - корректировка местонах и размера оверлейного слоя после анимации
1097
+ *
1098
+ * @param y // эти параметры есть конечная точка анимации moveTo,
1099
+ * @param x // т.е. к этим параметрам позиции стремится картинка карты и это анимируется
1100
+ * @param scale // как только картинка карты дойдёт до цели - их надо сохранить в map
1101
+ *
1102
+ * @private
1103
+ */
1104
+ var __moveToComplete = function (x,y,scale) {
1105
+ console.log("<__moveToComplete> x = " + x + "; y = " + y + "; scale = " + scale);
1106
+
1107
+ /* NOTE:: CORE */
1108
+
1109
+ if (scale !== undefined) self.scale = scale;
1110
+ self.x = x;
1111
+ self.y = y;
1112
+
1113
+ if (self.tooltip) self.tooltip.position();
1114
+
1115
+ __afterMovingCorrectSvgLayersPositions();
1116
+
1117
+ };
1118
+
1119
+ var __moveToTimeout = function () {
1120
+ if (self.mode === 'edit_area'|| self.mode === 'view_area') {
1121
+ $("#masked").removeClass('hiddn');
1122
+ }
1123
+ };
1124
+ /*var __moveToAnimate = function () {
1125
+
1126
+ };*/
1127
+
1128
+ /**
1129
+ * Рассчитывает scale_during_animation и корректирует местонах. оверлейных картинок.
1130
+ * Вызывается каждый шаг анимации moveto.
1131
+ * @private
1132
+ */
1133
+ var __moveToStep = function () {
1134
+
1135
+ //var x = self.map.css('left').split('px').join('');
1136
+ //var y = self.map.css('top').split('px').join('');
1137
+ var w = self.map.css('width').split('px').join('');
1138
+ //var h = self.map.css('height').split('px').join('');
1139
+
1140
+ //var image_width = MAP_WIDTH * scale;
1141
+
1142
+ // рассчитаем мгновенное значение scale
1143
+ self.scale_during_animation = w / MAP_WIDTH;
1144
+
1145
+ // [zoomove] пробежимся по всем оверлейным картинкам и позиционируем их
1146
+ $('.map_object_image_bg').each(function () {
1147
+ // рассчитаем и применим стиль
1148
+ self.__compose_css_style_for_map_object_image($( this ));
1149
+ });
1150
+
1151
+ //console.log("<__moveToStep> x = " + x + "; y = " + y + "; w = " + w + "; h = " + h + "; scale = " + scale_during_animation);
1152
+ };
1153
+
1154
+ // x,y - экранные координаты
1155
+ // сюда подаётся scale, который нужно присвоить map после анимации
1156
+ self.moveTo = function (x, y, scale, d, easing) {
1157
+ clog("<self.moveTo> x = " + x + "; y = " + y + "; scale = " + scale + "; delay = " + d);
1158
+ //clog('<self.moveTo>');
1159
+
1160
+ // если подан аргумент scale(масштаб)
1161
+ // перемещаемся анимированно
1162
+ if (scale !== undefined) {
1163
+
1164
+ // на время движения скрываем слой с полосатой анимацией
1165
+ if (self.current_area != null) {
1166
+ $("#masked").addClass('hiddn');
1167
+ setTimeout(__moveToTimeout, d);
1168
+ }
1169
+
1170
+ //setTimeout(__afterMovingCorrectSvgLayersPositions, d);
1171
+
1172
+ self.map.stop().animate(
1173
+ {
1174
+ 'left': x,
1175
+ 'top': y,
1176
+ 'width': self.contentWidth * scale,
1177
+ 'height': self.contentHeight * scale
1178
+ },
1179
+ {
1180
+ 'step': __moveToStep,
1181
+ 'complete': function () {
1182
+ __moveToComplete(x,y,scale);
1183
+ }
1184
+ },
1185
+ d,
1186
+ easing //,
1187
+ //__moveToAnimate
1188
+ );
1189
+
1190
+ }
1191
+
1192
+ // если не подан аргумент scale(масштаб)
1193
+ // перемещаемся без анимации
1194
+ else {
1195
+
1196
+ self.map.css({
1197
+ 'left': x,
1198
+ 'top': y
1199
+ });
1200
+
1201
+ __moveToComplete(x,y);
1202
+
1203
+ // отличие этого кода [qq1] от кода в [__afterMovingCorrectSvgLayersPositions] лишь в том,
1204
+ // что строка для атрибута viewbox формируется иным образом
1205
+ // Скорее всего, это код-дубликат, который появился во время rush разработки.
1206
+ //var t = (-x) + " " + (-y) + " " + self.contentWidth * self.scale + " " + self.contentHeight * self.scale;
1207
+ //self.svg.attr('viewBox',t);
1208
+ //self.svg_overlay.attr('viewBox', t);
1209
+ }
1210
+
1211
+ if (self.current_area != null) {
1212
+ self.current_area.invalidateAnimationMask();
1213
+ }
1214
+
1215
+ //if (self.tooltip) self.tooltip.position();
1216
+ //if (self.minimap) self.minimap.update(x, y);
1217
+ };
1218
+
1219
+ // показать инфо о здании
1220
+ self.showBuildingInfo = function (rent_building_hash) {
1221
+ console.log("<main.showBuildingInfo> Показать информацию о Rent-здании с id = " + rent_building_hash.id);
1222
+
1223
+ //"rent_building_hash": {
1224
+ // "id": 2,
1225
+ // "title": "Здание 2",
1226
+ // "props": {
1227
+ // "square": "1234 кв.м.",
1228
+ // "square_free": "124 кв. м",
1229
+ // "floor_height": "124 кв. м",
1230
+ // "column_step": "2 м",
1231
+ // "gate_type": "распашные",
1232
+ // "communications": "Интернет, электричество, водоснабжение",
1233
+ // "price": "от 155 руб/кв.м в месяц"
1234
+ // }
1235
+
1236
+ if (rent_building_hash.id == undefined) {
1237
+ $building_info.css('display','none');
1238
+ } else {
1239
+ $building_info.css('display','block');
1240
+ $building_info.find("h2").text(rent_building_hash["title"]);
1241
+
1242
+ var v, $ili, p;
1243
+ for (p in rent_building_hash["props"]) {
1244
+
1245
+ v = rent_building_hash["props"][p];
1246
+ $ili = $building_info.find("#" + p);
1247
+ $ili.find('span').text(v);
1248
+ //console.log("."+v+".");
1249
+
1250
+ // Не показывать пользователю карты незаполненные поля
1251
+ var li_css_display = 'block';
1252
+ if (v == '' || v == '-1' || v === 'null' || v == null) {
1253
+ li_css_display = 'none';
1254
+ }
1255
+ $ili.css('display',li_css_display);
1256
+
1257
+
1258
+ }
1259
+
1260
+ $building_info.find("#square_free").css('height', 'auto');
1261
+ }
1262
+ };
1263
+
1264
+ // показать инфо о просматриваемой площади
1265
+ self.showAreaInfo = function (area_hash, parent_building_hash) {
1266
+ //clog(area_hash);
1267
+
1268
+ //"area_hash": {
1269
+ // "id": 2,
1270
+ // "title": "Площадь 2.12",
1271
+ // "is_free": false,
1272
+ // "props": {
1273
+ // "square": "100 кв.м.",
1274
+ // "floor_height": "6 кв. м",
1275
+ // "column_step": "2 м",
1276
+ // "gate_type": "распашные",
1277
+ // "communications": "Интернет, электричество, водоснабжение",
1278
+ // "price": "от 155 руб/кв.м в месяц"
1279
+ // }
1280
+
1281
+ //"rent_building_hash": {
1282
+ // "id": 2,
1283
+ // "title": "Здание 2",
1284
+ // "props": {
1285
+ // "square": "1234 кв.м.",
1286
+ // "square_free": "124 кв. м",
1287
+ // "floor_height": "6 кв. м",
1288
+ // "column_step": "2 м",
1289
+ // "gate_type": "распашные",
1290
+ // "communications": "Интернет, электричество, водоснабжение",
1291
+ // "price": "от 155 руб/кв.м в месяц"
1292
+ // }
1293
+
1294
+ $building_info.find("h2").html("</span>" + area_hash["title"] + "<span style='color:#D0B2B2;'> / " + parent_building_hash["title"]);
1295
+
1296
+ var v;
1297
+ for (var p in area_hash["props"]) {
1298
+ v = area_hash["props"][p];
1299
+ $building_info.find("#" + p).find('span').text(v);
1300
+ }
1301
+
1302
+ $building_info.find("#square_free").css('height', '0');
1303
+
1304
+ // заполняем данными ссылку 'Оставить заявку'
1305
+ var $a_make_order = $building_info.find('.c80_order_invoking_btn');
1306
+ $a_make_order.data('comment-text', 'Здравствуйте, оставляю заявку на площадь: ' + area_hash["title"]);
1307
+ $a_make_order.data('subj-id', area_hash["id"]);
1308
+
1309
+ };
1310
+
1311
+ // перевод экранных координат в логические
1312
+ self.rightX = function(x) {
1313
+ return (x - self.x - self.container.offset().left) / self.scale;
1314
+ };
1315
+
1316
+ self.rightY = function(y) {
1317
+ return (y - self.y - self.container.offset().top) / self.scale
1318
+ };
1319
+
1320
+ // взять C80MapFloors::current_area и назначить ей Rent::area.id,
1321
+ // выбранный в окне _modal_window.html.erb
1322
+ self.link_area = function () {
1323
+
1324
+ // фиксируем компоненты модального окна
1325
+ var $m = $('#modal_window');
1326
+ var $b = $m.find('.modal-footer').find('.btn');
1327
+ var $s = $m.find('select');
1328
+
1329
+ // извлекаем значения
1330
+ var rent_area_id = $s.val();
1331
+ var map_area_id = self.current_area.id;
1332
+ console.log("<Map.link_area> rent_area_id = " + rent_area_id + "; map_area_id = " + map_area_id);
1333
+
1334
+ // нажимаем кнопку "закрыть"
1335
+ $b.click();
1336
+
1337
+ // показываем прелоадер
1338
+ self.save_preloader_klass.show();
1339
+
1340
+ // отправляем запрос на сервер
1341
+ // TODO_MY:: реализовать обработчик ошибок
1342
+ $.ajax({
1343
+ url:'/ajax/link_area',
1344
+ type:'POST',
1345
+ data: {
1346
+ rent_area_id: rent_area_id,
1347
+ map_area_id: map_area_id
1348
+ },
1349
+ dataType:"json"
1350
+ }).done(function (data, result) {
1351
+ self.save_preloader_klass.hide();
1352
+ self.data = data["updated_locations_json"];
1353
+ });
1354
+
1355
+ };
1356
+
1357
+ // взять C80MapFloors::current_building и назначить ему Rent::building.id,
1358
+ // выбранный в окне _modal_window.html.erb
1359
+ self.link_building = function () {
1360
+ console.log('<Map.link_building> ');
1361
+
1362
+ // фиксируем компоненты модального окна
1363
+ var $m = $('#modal_window');
1364
+ var $b = $m.find('.modal-footer').find('.btn');
1365
+ var $s = $m.find('select');
1366
+
1367
+ // извлекаем значения
1368
+ var rent_building_id = $s.val();
1369
+ var map_building_id = self.current_building.id;
1370
+ console.log("<Map.link_area> rent_building_id = " + rent_building_id + "; map_building_id = " + map_building_id);
1371
+
1372
+ // нажимаем кнопку "закрыть"
1373
+ $b.click();
1374
+
1375
+ // показываем прелоадер
1376
+ self.save_preloader_klass.show();
1377
+
1378
+ // отправляем запрос на сервер
1379
+ // TODO_MY:: реализовать обработчик ошибок
1380
+ $.ajax({
1381
+ url:'/ajax/link_building',
1382
+ type:'POST',
1383
+ data: {
1384
+ rent_building_id: rent_building_id,
1385
+ map_building_id: map_building_id
1386
+ },
1387
+ dataType:"json"
1388
+ }).done(function (data, result) {
1389
+ self.save_preloader_klass.hide();
1390
+ self.data = data["updated_locations_json"];
1391
+ });
1392
+
1393
+ };
1394
+
1395
+ self.show_free_areas_hint = function () {
1396
+ // рисуем в слое #svg_overlay,
1397
+ // а т.к. находимся в режиме просмотра карты,
1398
+ // этот слой пуст, можно им пока воспользоваться
1399
+
1400
+
1401
+
1402
+ };
1403
+ self.hide_free_areas_hint = function () {
1404
+
1405
+ };
1406
+
1407
+ };
1408
+
1409
+ // Create a jQuery plugin
1410
+ $.fn.beMap = function (params) {
1411
+ var len = this.length;
1412
+
1413
+ return this.each(function (index) {
1414
+ var me = $(this),
1415
+ key = 'beMap' + (len > 1 ? '-' + ++index : ''),
1416
+ instance = (new Map).init(me, params);
1417
+ });
1418
+ };
1419
+
1420
+ })
1421
+ (jQuery);