c80_map_floors 0.1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);