diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..19f7cc8 --- /dev/null +++ b/css/main.css @@ -0,0 +1,380 @@ +html, +body +{ + height: 100%; +} + +body +{ + margin: 0; + + font: 14px/1.2 sans-serif; +} + + +#content +{ + height: 100%; + margin: 50px 40px -50px; + + text-align: center; +} + + +.menu +{ + position: absolute; + top: 15px; + left: 0; + + display: none; + + margin: 0 40px; + padding: 0; + + list-style: none; +} + +.menu__item +{ + display: inline; + + margin: 0 15px 0 0; +} + + +.pictures__title +{ + margin: 0 0 20px; + + text-align: left; + + font-size: 40px; + font-weight: 400; +} + +.pictures__title:first-letter +{ + color: red; +} + +.pictures__items +{ + margin: -10px -10px 0; + padding: 0; + + list-style: none; + + text-align: justify; + + font: 0/0 a; +} + +.pictures__items:after +{ + display: inline-block; + + width: 100%; + height: 0; + + content: ""; +} + + +.picture +{ + position: relative; + + display: inline-block; + + margin: 10px; + + vertical-align: top; +} + +.picture__link +{ + position: relative; + z-index: 1; + + display: block; + overflow: hidden; + + height: 100px; + + cursor: pointer; +} + +.picture_active_yes .picture__link +{ + cursor: default; +} + +.picture__preview +{ + display: block; +} + +.picture__info +{ + position: absolute; + top: -10px; + left: -10px; + + display: none; + + width: 100%; + padding: 120px 10px 10px; + + text-align: left; + + color: #fff; + background: #2b2b2b; + box-shadow: 0 2px 9px rgba(0,0,0, .6); + + font: 12px/16px sans-serif; +} + +.picture__title +{ + overflow: hidden; + + margin: 0 0 5px; + + text-overflow: ellipsis; +} + +.picture_active_yes, +.picture_hovered_yes +{ + position: relative; + z-index: 100; +} + +.picture_active_yes .picture__info, +.picture_hovered_yes .picture__info +{ + display: block; +} + + +.preview +{ + position: relative; + + display: inline-block; + overflow: hidden; + + width: 100%; + + background: #2b2b2b; + + font: 14px/1.2 sans-serif; +} + +.preview__close +{ + position: absolute; + top: 10px; + right: 10px; + + overflow: hidden; + + height: 28px; + + cursor: pointer; + + color: #888; + + font-size: 48px; + font-weight: 700; + line-height: 28px; +} + +.preview__close:hover +{ + color: #ddd; +} + +.preview__slider +{ + margin: 10px 330px 10px 10px; + + text-align: center; +} + +.preview__info +{ + position: absolute; + top: 50px; + right: 10px; + + width: 300px; + + text-align: left; + + color: #eee; +} + +.preview__props +{ + margin: 0; + padding: 0; + + list-style: none; +} + +.preview__prop +{ + margin: 0 0 10px; +} + +.preview__sizes +{ + width: 170px; + margin: 5px 0 0; +} + +.preview__new-window +{ + display: inline-block; + + width: 16px; + height: 16px; + + vertical-align: text-top; + + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABbmlDQ1BpY2MAAHjalZC/SwJhHMYftUjSMqqhoeEGaQiFsKWxbBBCRMwgq+XuvNPgTo+7k4jGhlYHl4qWLPoPaov+gSAIqqWWmhsKIgi5nvMEJWroe7z3/fC87/P+eAB/VpN1q28G0Cu2mUslhdXCmjDwiBBGMIwgRkXZMhay2TT+rI87+Nx+G3f3wv8qVFQsGfAFyXOyYdrkeXJmyzZcrpPH5bJYJJ+QYyYvSL5xdcnjF5dLHn+6bOZzi4A/TBZKPSz1sFw2dXKMHNW1mty5j/uSsFJZWWafbA8LOaSQhAAJNWxCg404e4WZ/e5LtH0ZVOmR+TewDZOOEsr0xqjWuKvCrlJX+GlcwXKz/5mppc4mvBPCS0D/s+O8TwMDh0Brz3G+jh2n1QQCD8BVo+uvNhjnK/V6V4seAZFd4Pyyq0mnwAUznngyRFNsSwEOv6oCb2fAUAEYY9aD615enXk074H8DpC+BvYPgCmuj2x8A9UQaDp7qyzEAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHpUWHRhdXRob3IAAHjac8wryc9TiMpILUvMBgAc2ASK5VXOBwAAAVJJREFUOMutU01Lw0AQTdq6Fj/ILY03D6Jg9ZBzejDaY0CKRugv9JD+C8kPCISSgEjIRTxIE3tITGjjm9LoUpJG0IHHDJmZl5l9u4LwRxObCoqiuIXbq0k/tpoIBoPBSxRFOcIU+OCwskYC27Y9wzD8NE1ZVb6DEclfA72K/CvwFMdxq9vt5lUE5QTUnPGJ6XQ6VxQlzLLsXJKkk3V+F5McwLOqFZgoim6Jfr/vhmG4zxg7Re6TmieTSazruoczafOnTBivIXC4AO4Ag7xlWTeyLCuO4/Q0TbuczWb3q/VrCFiSJCP4UdlMay6XyyvgAfEOcEa1lSpghcVwOHymUTF2ZJqmi+I3fD8C2ohzwF+pUHN5Fij0VFVdBEHwTs11Mne23MAcJD6RbbsnPAHJNN4g2azPeAm/ZSTNNxM1xta1P+dFf8Gox4gPf/kA5+gJhP+yL2Tiz6Ca0DikAAAAAElFTkSuQmCC); +} + +.preview__title +{ + margin: 0 0 15px; + + color: #fff; +} + +.preview__author +{ + color: #24a323; +} + +.preview__author:hover +{ + color: red; +} + +.preview__slider +{ + position: relative; +} + +.preview__next, +.preview__prev +{ + position: absolute; + top: 50%; + + margin: -30px 0 0; + + color: #666; + + font-size: 60px; + line-height: 1; +} + +.preview__next:hover, +.preview__prev:hover +{ + cursor: pointer; + + color: #ddd; +} + +.preview__next +{ + right: 0; +} + +.preview__prev +{ + left: 0; +} + + +.loader-helper +{ + display: inline-block; + + height: 100%; + + content: ""; + vertical-align: middle; +} + + +.loader +{ + display: inline-block; + + margin: -100px 0 0; + + vertical-align: middle; +} + +.loader__circle +{ + display: block; + + width: 100px; + height: 100px; + margin: 20px auto; + + -webkit-animation: spin 1s linear infinite; + -moz-animation: spin 1s linear infinite; + -ms-animation: spin 1s linear infinite; + -o-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + + border: 20px solid #aaa; + border-right-color: transparent; + border-radius: 50%; + box-shadow: 0 0 25px 2px #eee; +} + +.loader__text +{ + text-align: center; + + color: #333; +} + +@-webkit-keyframes spin +{ + from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } +} + +@-moz-keyframes spin +{ + from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(360deg); } +} + +@-ms-keyframes spin +{ + from { -ms-transform: rotate(0deg); } + to { -ms-transform: rotate(360deg); } +} + +@-o-keyframes spin +{ + from { -o-transform: rotate(0deg); } + to { -o-transform: rotate(360deg); } +} + +@keyframes spin +{ + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..ba5781a --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + Домашнее задание №6 + + + + + +
+
+
+
+
Подождите, идёт загрузка...
+
+
+ + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..bc4f1ad --- /dev/null +++ b/js/app.js @@ -0,0 +1,23 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'router' +], function($, _, Backbone, Router) { + + return { + + initialize : function() { + this.router = new Router(); + this.vent = _.extend({}, Backbone.Events); + + var self = this; + $('a[href]').on('click', function(event) { + self.router.navigate($(this).attr('href'), { trigger : true }); + return false; + }); + } + + }; + +}); diff --git a/js/collections/picture.js b/js/collections/picture.js new file mode 100644 index 0000000..c8b60e6 --- /dev/null +++ b/js/collections/picture.js @@ -0,0 +1,24 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'models/picture' +], function($, _, Backbone, Picture) { + + return Backbone.Collection.extend({ + + model : Picture, + + url : 'http://api-fotki.yandex.ru/api/{{type}}/?format=json', + + initialize : function(options) { + this.url = this.url.replace('{{type}}', options.type); + }, + + parse : function(response) { + return response.entries; + } + + }); + +}); diff --git a/js/helpers/helper.js b/js/helpers/helper.js new file mode 100644 index 0000000..ab5040d --- /dev/null +++ b/js/helpers/helper.js @@ -0,0 +1,35 @@ +define(function() { + + var months = [ + 'января', + 'февраля', + 'марта', + 'апреля', + 'мая', + 'июня', + 'июля', + 'августа', + 'сентября', + 'октября', + 'ноября', + 'декабря' + ]; + + return { + + leadZero : function(num) { + return num < 10 ? '0' + num : num; + }, + + formatDate : function(date) { + date = new Date(date); + return date.getDate() + ' ' + + months[date.getMonth()] + ' ' + + date.getFullYear() + ' в ' + + this.leadZero(date.getHours()) + ':' + + this.leadZero(date.getMinutes()); + } + + }; + +}); diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..57bb42b --- /dev/null +++ b/js/main.js @@ -0,0 +1,25 @@ +requirejs.config({ + paths : { + underscore : '//yandex.st/underscore/1.5.2/underscore-min', + backbone : '//yandex.st/backbone/1.1.0/backbone-min', + jquery : '//yandex.st/jquery/1.10.2/jquery.min', + text : '//cdnjs.cloudflare.com/ajax/libs/require-text/2.0.10/text.min' + }, + shim : { + underscore : { + exports : '_' + }, + backbone : { + deps : ['jquery', 'underscore'], + exports : 'Backbone' + } + } +}); + +require([ + 'app' +], function(App) { + + App.initialize(); + +}); diff --git a/js/models/picture.js b/js/models/picture.js new file mode 100644 index 0000000..ba95c76 --- /dev/null +++ b/js/models/picture.js @@ -0,0 +1,42 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'helpers/helper', + 'views/picture/detail' +], function($, _, Backbone, Helper, PictureDetailView) { + + return Backbone.Model.extend({ + + initialize: function(attributes, options) { + this.collection = options.collection; + }, + + parse : function(response, options) { + return { + id : response.id, + title : _.escape(response.title), + author : { + name : response.author, + url : 'http://fotki.yandex.ru/users/' + response.author + '/photos/' + }, + pubDate : Helper.formatDate(response.published), + img : response.img + }; + }, + + index : function() { + return this.collection.indexOf(this); + }, + + next : function() { + return this.collection.at(this.index() + 1) || null; + }, + + prev : function() { + return this.collection.at(this.index() - 1) || null; + } + + }); + +}); diff --git a/js/router.js b/js/router.js new file mode 100644 index 0000000..19bac10 --- /dev/null +++ b/js/router.js @@ -0,0 +1,48 @@ +define([ + 'jquery', + 'underscore', + 'backbone', +], function($, _, Backbone) { + + return Backbone.Router.extend({ + + routes : { + '*type' : 'index' + }, + + initialize : function() { + Backbone.history.start(); + }, + + index : function(type) { + var types = { + 'top' : 'Популярные фотографии', + 'recent' : 'Новые интересные фотографии', + 'podhistory' : 'Фото дня' + }; + + types[type] === undefined && (type = 'top'); + + require([ + 'collections/picture', + 'views/picture/list' + ], function(PictureCollection, PictureListView) { + var collection = new PictureCollection({ + type : type + }); + collection.fetch({ + dataType : 'jsonp', + success : function(collection) { + $('.menu').show(); + new PictureListView({ + collection : collection, + title : types[type] + }); + } + }); + }); + } + + }); + +}); diff --git a/js/templates/picture/detail.html b/js/templates/picture/detail.html new file mode 100644 index 0000000..e8ceb81 --- /dev/null +++ b/js/templates/picture/detail.html @@ -0,0 +1,34 @@ +
+ + + <%= title %> +
+
+

<%= title %>

+ +
+× diff --git a/js/templates/picture/item.html b/js/templates/picture/item.html new file mode 100644 index 0000000..9789675 --- /dev/null +++ b/js/templates/picture/item.html @@ -0,0 +1,8 @@ + + <%= title %> + +
+

<%= title %>

+
<%= author.name %>
+
<%= pubDate %>
+
diff --git a/js/templates/picture/list.html b/js/templates/picture/list.html new file mode 100644 index 0000000..445c1e8 --- /dev/null +++ b/js/templates/picture/list.html @@ -0,0 +1,2 @@ +

<%= title %>

+ diff --git a/js/views/picture/detail.js b/js/views/picture/detail.js new file mode 100644 index 0000000..93ad3fa --- /dev/null +++ b/js/views/picture/detail.js @@ -0,0 +1,103 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'app', + 'text!templates/picture/detail.html' +], function($, _, Backbone, App, tpl) { + + return Backbone.View.extend({ + + tagName : 'li', + + className : 'preview', + + template : _.template(tpl), + + initialize: function(options) { + _.bindAll(this, 'keyPress'); + App.vent.on('picture:select', this.close, this); + $(document).on('keydown', this.keyPress); + this.on('picture:prev', this.showPrev, this); + this.on('picture:next', this.showNext, this); + this.render(options.addBefore); + }, + + events : { + 'click .preview__close' : 'close', + 'click .preview__next' : 'showNext', + 'click .preview__prev' : 'showPrev', + 'change .preview__sizes' : 'otherImageSize' + }, + + render : function(element) { + var $el = this.$el; + $el.html(this.template(this.model.toJSON())).css('display', 'none'); + + // Вставляем превью перед началом строки + $(element).before($el); + + // Разворачиваем превью и прокручиваем + $el.slideDown('fast', function() { + $(document.body).animate({ scrollTop: $el.offset().top - 10 }, 'fast'); + }); + + return this; + }, + + close : function() { + this.model.trigger('preview:close'); + + // Удаляем обработчики + $(document).off('keydown', this.keyPress); + App.vent.off('picture:select', this.close); + + // Сворачиваем и удаляем превью + var self = this; + this.$el.slideUp('fast', function() { + self.remove(); + }); + }, + + keyPress : function(e) { + switch(e.which) { + case 27: // esc + this.close(); + break; + case 37: // left + this.trigger('picture:prev'); + break; + case 39: // right + this.trigger('picture:next'); + break; + } + }, + + showNext : function() { + var model = this.model.next(); + if (model === null) + return; + App.vent.trigger('picture:select'); + model.trigger('picture:select', this); + }, + + showPrev : function() { + var model = this.model.prev(); + if (model === null) + return; + App.vent.trigger('picture:select'); + model.trigger('picture:select', this); + }, + + otherImageSize : function(event) { + // Получаем данные о выбранном размере + var size = $(event.target).val(), + img = this.model.get('img')[size]; + + // Открываем в новой вкладке + window.open(img.href, '_blank'); + } + + }); + +}); diff --git a/js/views/picture/item.js b/js/views/picture/item.js new file mode 100644 index 0000000..8d52872 --- /dev/null +++ b/js/views/picture/item.js @@ -0,0 +1,87 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'app', + 'views/picture/detail', + 'text!templates/picture/item.html' +], function($, _, Backbone, App, PictureDetailView, tpl) { + + return Backbone.View.extend({ + + tagName : 'li', + + className : 'picture', + + template : _.template(tpl), + + initialize: function() { + App.vent.on('picture:select', this.disactivate, this); + this.model.on('picture:select', this.showPreview, this); + this.model.on('preview:close', this.disactivate, this); + this.render(); + }, + + events : { + 'mouseout .picture__preview' : 'hover', + 'mouseover .picture__preview' : 'hover', + 'click .picture__link' : 'selectPicture' + }, + + render : function() { + this.$el.html(this.template(this.model.toJSON())); + return this; + }, + + selectPicture : function() { + if (this.$el.hasClass('picture_active_yes')) { + return; + } + App.vent.trigger('picture:select'); + this.model.trigger('picture:select', this); + }, + + showPreview : function() { + var $el = this.$el, + $prevEl; + + // Ищем начало строки для вставки превью + while (true) { + $prevEl = $el.prev(); + if ($prevEl.length > 0) { + if ($prevEl.position().left > $el.position().left) { + break; + } else { + $el = $prevEl; + } + } else { + break; + } + } + + new PictureDetailView({ + model : this.model, + addBefore : $el + }); + + this.activate(); + }, + + activate : function() { + this.$el.addClass('picture_active_yes'); + return this; + }, + + disactivate : function() { + this.$el.removeClass('picture_active_yes'); + return this; + }, + + hover : function() { + this.$el.toggleClass('picture_hovered_yes'); + return this; + } + + }); + +}); diff --git a/js/views/picture/list.js b/js/views/picture/list.js new file mode 100644 index 0000000..37643a5 --- /dev/null +++ b/js/views/picture/list.js @@ -0,0 +1,33 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'views/picture/item', + 'text!templates/picture/list.html' +], function($, _, Backbone, PictureItemView, tpl) { + + return Backbone.View.extend({ + + className : 'pictures', + + template : _.template(tpl), + + initialize: function(options) { + this.render(options.title); + }, + + render : function(title) { + this.$el.html(this.template({ title : title })); + this.collection.each(this.addOne, this); + $('#content').html(this.el); + return this; + }, + + addOne : function(picture) { + var view = new PictureItemView({ model : picture }); + this.$('.pictures__items').append('\n').append(view.render().el); + } + + }); + +});