diff --git a/.eslintrc b/.eslintrc
index d30435505..0002e9777 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -5,6 +5,9 @@
"jest": true,
"es6": true
},
+ "parserOptions": {
+ "ecmaVersion": 2017
+ },
"rules": {
"complexity": [0, 11],
"consistent-return": 2,
diff --git a/src/library/css/article.css b/src/library/css/article.css
new file mode 100644
index 000000000..fada05a4c
--- /dev/null
+++ b/src/library/css/article.css
@@ -0,0 +1,118 @@
+.article_header,
+.article_filter {
+ color: #444c63;
+ font-size: 22px;
+ height: 70px;
+ background-color: #eef1f7;
+ border-bottom: 1px solid #dadfea;
+ border-top: 1px solid #f4f6fa;
+}
+
+.article_header-label {
+ margin: 23px;
+}
+
+.article_filter {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.article_filter ul {
+ display: flex;
+ width: 355px;
+}
+
+.article_filter li {
+ padding: 4px 11px;
+ margin: 0;
+ border-radius: 10px;
+}
+
+.article_filter li:hover {
+ background-color: #97b3ce;
+}
+
+.article_books {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.article_books img {
+ width: 200px;
+ height: 275px;
+}
+
+.article_books h3 {
+ color: #444c63;
+ font-size: 16px;
+ font-weight: normal;
+ margin: 0;
+}
+
+.article_books p {
+ color: #6f7d95;
+ font-size: 12px;
+ margin: 0;
+}
+
+.article_books_book {
+ margin: 30px 20px;
+ width: 200px;
+}
+
+.star_selected {
+ fill: #ffab00;
+ stroke: #ffab00;
+ stroke-width: 2px;
+}
+
+.star_unselected {
+ stroke: #ffab00;
+ fill: white;
+ stroke-width: 2px;
+}
+
+.star_tmpselected {
+ fill: #ffab00;
+ stroke: #ffab00;
+}
+
+.star_tmpunselected {
+ fill: white;
+ stroke: #facc6f;
+}
+
+.article_search {
+ width: 400px;
+ height: 32px;
+ border: 1px solid rgba(140, 151, 178, 0.4);
+ border-radius: 16px;
+ padding: 0 17px;
+ outline: none;
+}
+
+.article_search::-moz-placeholder {
+ color: #8c97b2;
+}
+
+.article_search::-webkit-input-placeholder {
+ color: #8c97b2;
+}
+
+.article_search-img {
+ position: absolute;
+ top: 9px;
+ right: 30px;
+ width: 13px;
+ height: 13px;
+}
+
+.article_search-label {
+ position: relative;
+ height: 32px;
+}
+
+.article_filter .selected-filter {
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/library/css/footer.css b/src/library/css/footer.css
new file mode 100644
index 000000000..6de2576a6
--- /dev/null
+++ b/src/library/css/footer.css
@@ -0,0 +1,20 @@
+.footer__item {
+ float: right;
+ background-color: #1f2637;
+ width: 55px;
+ height: 45px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.footer__item:hover {
+ background-color: #15a4fa;
+ ;
+}
+
+.footer__item img {
+ float: center;
+ width: 14px;
+ height: 14px;
+}
\ No newline at end of file
diff --git a/src/library/css/header.css b/src/library/css/header.css
new file mode 100644
index 000000000..c3d4e6c5a
--- /dev/null
+++ b/src/library/css/header.css
@@ -0,0 +1,34 @@
+.header__button {
+ color: #78829d;
+ font-size: 13px;
+ margin: 15px;
+}
+
+.header__marker {
+ border: 2px solid #19202e;
+ border-radius: 100%;
+}
+
+.header__user {
+ color: #ffffff;
+ font-size: 13px;
+ width: 158px;
+ background-color: #15a4fa;
+ height: 100%;
+ border-top-right-radius: 4px;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+}
+
+.header__user-icons {
+ width: 12px;
+ height: 12px;
+}
+
+.header__user-photo {
+ width: 31px;
+ height: 32px;
+ border: 2px solid #eef1f7;
+ border-radius: 100%;
+}
\ No newline at end of file
diff --git a/src/library/css/library.css b/src/library/css/library.css
new file mode 100644
index 000000000..feab89dfa
--- /dev/null
+++ b/src/library/css/library.css
@@ -0,0 +1,66 @@
+@font-face {
+ font-family: "Proxima Nova";
+ src: url(../fonts/12093.ttf);
+}
+
+.library {
+ display: grid;
+ margin: 0;
+ font-family: "Proxima Nova";
+ grid-template-areas: "header header" "navigation article" "footer footer";
+ grid-template-columns: 200px 1fr;
+}
+
+.header {
+ grid-area: header;
+ height: 45px;
+ background-color: #2f364a;
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: flex-end;
+ align-items: center;
+ border-top-right-radius: 4px;
+ position: fixed;
+ width: 100%;
+ z-index: 1;
+}
+
+.navigation {
+ height: 100%;
+ grid-area: navigation;
+ background-color: #181e2c;
+ margin: 44px 0;
+}
+
+.article {
+ grid-area: article;
+ margin: 45px 0;
+}
+
+.footer {
+ height: 45px;
+ grid-area: footer;
+ background-color: #181e2c;
+ position: fixed;
+ bottom: 0px;
+ width: 100%;
+}
+
+.library ul {
+ padding: 0;
+}
+
+.library li {
+ color: #78829d;
+ font-size: 13px;
+ text-transform: capitalize;
+ padding: 0 0 0 46px;
+ list-style-type: none;
+ position: relative;
+ cursor: pointer;
+}
+
+.library li:hover {
+ color: white;
+ background-color: #15a4fa;
+}
\ No newline at end of file
diff --git a/src/library/css/modalWindow.css b/src/library/css/modalWindow.css
new file mode 100644
index 000000000..fec962744
--- /dev/null
+++ b/src/library/css/modalWindow.css
@@ -0,0 +1,67 @@
+.modal-window {
+ display: none;
+ position: absolute;
+ position: fixed;
+ z-index: 2;
+ margin: auto;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(49, 48, 48, 0.5)
+}
+
+.modal-window label {
+ width: 180px;
+ display: inline-block;
+ margin: 5px 10px;
+}
+
+.modal-window form {
+ background-color: #1f2637;
+ color: #78829d;
+ padding: 30px;
+ border-radius: 10px;
+ border: 1px solid black;
+}
+
+.modal-window p {
+ margin: 20px 20px 5px 20px;
+}
+
+.modal-window input {
+ border-radius: 3px;
+}
+
+.modal-window__buttons {
+ text-align: center;
+}
+
+.modal-window__button {
+ border-radius: 3px;
+ width: 90px;
+ height: 20px;
+ background-color: #f2795a;
+ color: white;
+ border: none;
+ font-size: 14px;
+ margin: 10px;
+}
+
+.modal-window__button:hover {
+ background-color: #f05a34;
+}
+
+.modal-window i {
+ color: red;
+ margin: 5px;
+}
+
+.modal-window__hint {
+ color: red;
+ display: none;
+}
+
+.modal-window__container-hint {
+ height: 20px;
+}
\ No newline at end of file
diff --git a/src/library/css/navigation.css b/src/library/css/navigation.css
new file mode 100644
index 000000000..48c51c5a8
--- /dev/null
+++ b/src/library/css/navigation.css
@@ -0,0 +1,153 @@
+.navigation__module {
+ background-color: #1f2637;
+ margin: 4px 0;
+ border-bottom: 1px solid #141824;
+ border-top: 1px solid #78829d;
+}
+
+.navigation__item-history p {
+ font-size: 12px;
+ color: #6f7d95;
+}
+
+.navigation__item-history strong {
+ font-size: 12px;
+ font-weight: normal;
+ color: #97b3ce;
+}
+
+.navigation__item-history {
+ margin: 25px 25px 25px 46px;
+ position: relative;
+}
+
+.navigation__menu li::before {
+ content: "";
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ top: 11px;
+ left: 21px;
+}
+
+.navigation__menu li:nth-child(1)::before {
+ background-image: url(../img/book.svg);
+}
+
+.navigation__menu li:nth-child(1):hover::before {
+ background-image: url(../img/book_hover.svg);
+}
+
+.navigation__menu li:nth-child(2)::before {
+ background-image: url(../img/globe.svg);
+}
+
+.navigation__menu li:nth-child(2):hover::before {
+ background-image: url(../img/globe_hover.svg);
+}
+
+.navigation__menu li:nth-child(3)::before {
+ background-image: url(../img/shoppingtrolley.svg);
+}
+
+.navigation__menu li:nth-child(3):hover::before {
+ background-image: url(../img/shoppingtrolley_hover.svg);
+}
+
+.navigation__menu li:nth-child(4)::before {
+ background-image: url(../img/star.svg);
+}
+
+.navigation__menu li:nth-child(4):hover::before {
+ background-image: url(../img/star_hover.svg);
+}
+
+.navigation__menu li:nth-child(5)::before {
+ background-image: url(../img/list_icon.svg);
+}
+
+.navigation__menu li:nth-child(5):hover::before {
+ background-image: url(../img/list_icon_hover.svg);
+}
+
+.navigation__menu li:nth-child(6)::before {
+ background-image: url(../img/clock.svg);
+}
+
+.navigation__menu li:nth-child(6):hover::before {
+ background-image: url(../img/clock_hover.svg);
+}
+
+.navigation__menu-filter li::before {
+ content: "";
+ width: 6px;
+ height: 6px;
+ position: absolute;
+ top: 16px;
+ left: 25px;
+ border-radius: 100%;
+}
+
+.navigation__menu-filter li:nth-child(1)::before {
+ background-color: #e64c66;
+}
+
+.navigation__menu-filter li:nth-child(2)::before {
+ background-color: #ffab00;
+}
+
+.navigation__menu-filter li:nth-child(3)::before {
+ background-color: #00bfdd;
+}
+
+.navigation__menu-filter li:nth-child(4)::before {
+ background-color: #7874cf;
+}
+
+.navigation__clock::before {
+ content: "";
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ background-image: url(../img/clock.svg);
+ top: 10px;
+ left: -25px;
+}
+
+.navigation__menu-filter li {
+ height: 38px;
+ line-height: 38px;
+ display: inline-block;
+ width: 154px;
+}
+
+.navigation__menu a {
+ height: 38px;
+ line-height: 38px;
+ display: inline-block;
+}
+
+.navigation__module-button-add {
+ text-transform: uppercase;
+ width: 160px;
+ height: 44px;
+ background-color: #f2795a;
+ border-radius: 4px;
+ color: white;
+ border: none;
+ margin: 35px 20px;
+ font-size: 14px;
+}
+
+.navigation__module-button-add:hover {
+ background-color: #f05a34;
+}
+
+.navigation__module-button-add img {
+ margin: 0 10px;
+}
+
+.navigation__menu-filter .selected-filter {
+ background-color: #37415c;
+ box-shadow: inset 0 0 5px #181d2b;
+}
\ No newline at end of file
diff --git a/src/library/fonts/12093.ttf b/src/library/fonts/12093.ttf
new file mode 100644
index 000000000..7d0024750
Binary files /dev/null and b/src/library/fonts/12093.ttf differ
diff --git a/src/library/img/+.png b/src/library/img/+.png
new file mode 100644
index 000000000..ae2f10646
Binary files /dev/null and b/src/library/img/+.png differ
diff --git a/src/library/img/arrowDown.png b/src/library/img/arrowDown.png
new file mode 100644
index 000000000..8a96fdaf9
Binary files /dev/null and b/src/library/img/arrowDown.png differ
diff --git a/src/library/img/avatar-01.png b/src/library/img/avatar-01.png
new file mode 100644
index 000000000..00fb845db
Binary files /dev/null and b/src/library/img/avatar-01.png differ
diff --git a/src/library/img/book.svg b/src/library/img/book.svg
new file mode 100644
index 000000000..db5a3478e
--- /dev/null
+++ b/src/library/img/book.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/src/library/img/book_hover.svg b/src/library/img/book_hover.svg
new file mode 100644
index 000000000..e7831bd18
--- /dev/null
+++ b/src/library/img/book_hover.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/src/library/img/clock.svg b/src/library/img/clock.svg
new file mode 100644
index 000000000..d1a751331
--- /dev/null
+++ b/src/library/img/clock.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/library/img/clock_hover.svg b/src/library/img/clock_hover.svg
new file mode 100644
index 000000000..ed445aeb6
--- /dev/null
+++ b/src/library/img/clock_hover.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/library/img/footer_hint.png b/src/library/img/footer_hint.png
new file mode 100644
index 000000000..6eff1a81a
Binary files /dev/null and b/src/library/img/footer_hint.png differ
diff --git a/src/library/img/footer_settings.png b/src/library/img/footer_settings.png
new file mode 100644
index 000000000..b87249a5a
Binary files /dev/null and b/src/library/img/footer_settings.png differ
diff --git a/src/library/img/globe.svg b/src/library/img/globe.svg
new file mode 100644
index 000000000..3cb328b36
--- /dev/null
+++ b/src/library/img/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/library/img/globe_hover.svg b/src/library/img/globe_hover.svg
new file mode 100644
index 000000000..d21052b54
--- /dev/null
+++ b/src/library/img/globe_hover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/library/img/list_icon.svg b/src/library/img/list_icon.svg
new file mode 100644
index 000000000..8fe8f2760
--- /dev/null
+++ b/src/library/img/list_icon.svg
@@ -0,0 +1,61 @@
+
+
+
+
diff --git a/src/library/img/list_icon_hover.svg b/src/library/img/list_icon_hover.svg
new file mode 100644
index 000000000..3f64295e8
--- /dev/null
+++ b/src/library/img/list_icon_hover.svg
@@ -0,0 +1,61 @@
+
+
+
+
diff --git a/src/library/img/search.png b/src/library/img/search.png
new file mode 100644
index 000000000..0875f22f1
Binary files /dev/null and b/src/library/img/search.png differ
diff --git a/src/library/img/shoppingtrolley.svg b/src/library/img/shoppingtrolley.svg
new file mode 100644
index 000000000..a8a6c47b7
--- /dev/null
+++ b/src/library/img/shoppingtrolley.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/library/img/shoppingtrolley_hover.svg b/src/library/img/shoppingtrolley_hover.svg
new file mode 100644
index 000000000..921cc369a
--- /dev/null
+++ b/src/library/img/shoppingtrolley_hover.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/library/img/star.svg b/src/library/img/star.svg
new file mode 100644
index 000000000..83d5aad61
--- /dev/null
+++ b/src/library/img/star.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/library/img/star_hover.svg b/src/library/img/star_hover.svg
new file mode 100644
index 000000000..275badae4
--- /dev/null
+++ b/src/library/img/star_hover.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/library/img/star_rating.svg b/src/library/img/star_rating.svg
new file mode 100644
index 000000000..22909e959
--- /dev/null
+++ b/src/library/img/star_rating.svg
@@ -0,0 +1,45 @@
+
+
+
+
diff --git a/src/library/library.html b/src/library/library.html
new file mode 100644
index 000000000..f3243bcff
--- /dev/null
+++ b/src/library/library.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Library
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Browse Available Books
+
+
+
+ - All Books
+ - Most Recent
+ - Most Popular
+ - Free Books
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/library/scripts/app.js b/src/library/scripts/app.js
new file mode 100644
index 000000000..4a2aad743
--- /dev/null
+++ b/src/library/scripts/app.js
@@ -0,0 +1 @@
+new MainController().init();
\ No newline at end of file
diff --git a/src/library/scripts/app/mainController.js b/src/library/scripts/app/mainController.js
new file mode 100644
index 000000000..a7c88611a
--- /dev/null
+++ b/src/library/scripts/app/mainController.js
@@ -0,0 +1,13 @@
+function MainController() {
+ return Controller.call(this);
+}
+MainController.prototype = Object.create(MainController.prototype);
+MainController.prototype.constructor = Controller;
+MainController.prototype.init = function () {
+ this.bookController = new BookController();
+ this.bookController.init();
+ this.historyController = new HistoryController();
+ this.bookController.setOnHistoryEventHandler(this.historyController.onNewEvent.bind(this.historyController));
+ this.historyController.init();
+};
+
diff --git a/src/library/scripts/books/bookController.js b/src/library/scripts/books/bookController.js
new file mode 100644
index 000000000..02820d4bb
--- /dev/null
+++ b/src/library/scripts/books/bookController.js
@@ -0,0 +1,156 @@
+var urlBooks = "https://rsu-library-api.herokuapp.com/books";
+
+function BookController() {
+ Controller.call(this);
+ this.addBookController = new ModalWindowController();
+ this.addBookController.init();
+ this.addBookController.setonAddBookEventHandler((book) => {
+ this.addBook(book);
+ message = `You added a new book ${book.title} by ${book.author.firstName} ${book.author.lastName}`;
+ this.onHistoryEventHandler(message, new Date());
+ });
+ this.onHistoryEventHandler = function() {};
+ this.lastFilteredCategory = null;
+ this.view = new BookView();
+ return this;
+}
+BookController.prototype = Object.create(Controller.prototype);
+BookController.prototype.constructor = BookController;
+BookController.prototype.setOnHistoryEventHandler = function(cb) {
+ this.onHistoryEventHandler = cb;
+};
+BookController.prototype.onBtnAllBooksHandler = function() {
+ this.view.changeCategorySelection(event.target)
+ this.store.dropSort();
+};
+BookController.prototype.onBtnMostRecentHandler = function() {
+ this.view.changeCategorySelection(event.target)
+ this.store.sort(book => book.updatedAt, true);
+};
+BookController.prototype.onBtnMostPopular = function() {
+ this.view.changeCategorySelection(event.target)
+ this.store.sort(book => book.rating, true);
+};
+BookController.prototype.onBtnFreeBooksHandler = function() {
+ this.view.changeCategorySelection(event.target)
+ this.store.sort(book => book.cost, false);
+};
+BookController.prototype.onBtnCategoryFilterHandler = function(category, event) {
+ this.view.changeCategorySelection(event.target)
+ var store = this.store;
+ store.beginUpdate();
+ var message;
+ try {
+ store.removePropFilter("CategoryFilter");
+ if (this.lastFilteredCategory === category) {
+ message = `You have canceled filter by category ${category}`;
+ this.lastFilteredCategory = null;
+ } else {
+ this.lastFilteredCategory = category;
+ message = `You have applied filter by category ${category}`;
+ store.addPropFilter(function(item) { return item.categories.includes(category); }, "CategoryFilter");
+ }
+ store.filter();
+ } finally {
+ store.endUpdate();
+ this.onHistoryEventHandler(message, new Date());
+ }
+};
+BookController.prototype.onStarClickHandlerWrapper = function(bookId, rating) {
+ var store = this.store;
+ var index = store.findIndex(book => book.id === bookId);
+ if (index >= 0) {
+ var book = store.getRealAt(index);
+ book.rating = rating;
+ }
+ store.changed();
+};
+BookController.prototype.onSearchFieldChangeHandler = function() {
+ var text = this.items.inpSearch.value.toLowerCase();
+ const { store } = this;
+ store.beginUpdate();
+ try {
+ store.removePropFilter("SearchAuthorAndBook");
+ if (text && text !== "") {
+ store.addPropFilter(function(item) {
+ return item.author.firstName.toLowerCase().includes(text) ||
+ item.author.lastName.toLowerCase().includes(text) ||
+ item.title.toLowerCase().includes(text);
+ }, "SearchAuthorAndBook");
+ }
+ store.filter();
+ } finally {
+ this.onHistoryEventHandler(`You have applied search by word ${text}`, new Date());
+ store.endUpdate();
+ }
+};
+BookController.prototype.LoadBooks = async function() {
+ await this.getDataFromServer();
+};
+BookController.prototype.getDataFromServer = async function() {
+ const response = await request(urlBooks);
+ const { store } = this;
+ store.beginUpdate();
+ try {
+ if (response instanceof Array) {
+ for (const item of response) {
+ this.addBook(item);
+ }
+ }
+ } finally {
+ store.endUpdate();
+ }
+};
+BookController.prototype.addBook = function(bookSource) {
+ const book = new Book();
+ book.id = generateId();
+ const { author } = bookSource;
+ if (author) {
+ if (author.firstName) book.author.firstName = author.firstName;
+ if (author.lastName) book.author.lastName = author.lastName;
+ }
+ const { categories } = bookSource;
+ if (categories && (categories instanceof Array)) {
+ for (const item of categories) {
+ book.categories.push(item);
+ }
+ }
+ if (bookSource.cost) book.cost = bookSource.cost;
+ if (bookSource.createdAt) book.createdAt = bookSource.createdAt;
+ if (bookSource.image_url) book.image_url = bookSource.image_url;
+ if (bookSource.rating) book.rating = bookSource.rating;
+ if (bookSource.title) book.title = bookSource.title;
+ if (bookSource.updatedAt) book.updatedAt = bookSource.updatedAt;
+ this.store.add(book);
+};
+BookController.prototype.onBtnAddBookHandler = function() {
+ this.addBookController.setActive();
+};
+
+BookController.prototype.prepareListeners = function() {
+ Controller.prototype.prepareListeners.call(this);
+ var items = this.items;
+ items.btnAllBooks.addEventListener("click", this.onBtnAllBooksHandler.bind(this));
+ items.btnMostRecent.addEventListener("click", this.onBtnMostRecentHandler.bind(this));
+ items.btnMostPopular.addEventListener("click", this.onBtnMostPopular.bind(this));
+ items.btnFreeBooks.addEventListener("click", this.onBtnFreeBooksHandler.bind(this));
+ items.inpSearch.addEventListener("input", debounce(this.onSearchFieldChangeHandler.bind(this), 1000));
+ items.btnMustReadTitles.addEventListener("click", (event) => this.onBtnCategoryFilterHandler("must_read", event));
+ items.btnBestOfList.addEventListener("click", (event) => this.onBtnCategoryFilterHandler("best", event));
+ items.btnClassicNovels.addEventListener("click", (event) => this.onBtnCategoryFilterHandler("classic", event));
+ items.btnNonFiction.addEventListener("click", (event) => this.onBtnCategoryFilterHandler("non_fiction", event));
+
+ items.btnAddBook.addEventListener("click", this.onBtnAddBookHandler.bind(this));
+ this.view.setOnStarClickHandler(this.onStarClickHandlerWrapper.bind(this));
+};
+BookController.prototype.prepareStore = async function() {
+ Controller.prototype.prepareStore.call(this);
+ await this.LoadBooks();
+};
+BookController.prototype.validation = function() {
+ var items = this.items;
+ if (items.title) {
+ console.log(this.items);
+ }
+ return false;
+};
\ No newline at end of file
diff --git a/src/library/scripts/books/bookModel.js b/src/library/scripts/books/bookModel.js
new file mode 100644
index 000000000..a7f74774d
--- /dev/null
+++ b/src/library/scripts/books/bookModel.js
@@ -0,0 +1,10 @@
+function Book() {
+ this.title = undefined;
+ this.author = { firstName: undefined, lastName: undefined };
+ this.categories = [];
+ this.cost = undefined;
+ this.createdAt = new Date();
+ this.updatedAt = new Date();
+ this.image_url = undefined;
+ this.rating = undefined;
+}
diff --git a/src/library/scripts/books/bookView.js b/src/library/scripts/books/bookView.js
new file mode 100644
index 000000000..07b984723
--- /dev/null
+++ b/src/library/scripts/books/bookView.js
@@ -0,0 +1,178 @@
+var bookIdSymb = Symbol("bookID");
+var starParentSymb = Symbol("parentStar");
+var starRatingSymb = Symbol("ratingStar");
+var isSelectedSymb = Symbol("selected");
+var groupSymb = Symbol("group");
+
+function BookView() {
+ View.call(this);
+ this.onStarClickHandler = null;
+ this.containerName = "books";
+ this.prepareItems();
+ return this;
+}
+BookView.prototype = Object.create(View.prototype);
+BookView.prototype.constructor = BookView;
+BookView.prototype.setOnStarClickHandler = function(cb) {
+ this.onStarClickHandler = cb;
+};
+BookView.prototype.onStarClickHandlerWrapper = function(event) {
+ var id = event.target[bookIdSymb];
+ var rating = event.target[starRatingSymb];
+ if (this.onStarClickHandler) this.onStarClickHandler(id, rating);
+};
+BookView.prototype.updateContainer = function() {
+ View.prototype.updateContainer.call(this);
+ const { container } = this;
+ for (const book of this.store) {
+ container.appendChild(this.createBook(book));
+ }
+ var starCont = container.getElementsByTagName("path");
+ for (let i = 0; i < starCont.length; i++) {
+ starCont[i].addEventListener("mouseover", this.onMouseOverStar.bind(this));
+ starCont[i].addEventListener("mouseout", this.onMouseOutOfStar.bind(this));
+ starCont[i].addEventListener("click", this.onStarClickHandlerWrapper.bind(this));
+ }
+};
+BookView.prototype.fillstarsTmp = function(rating, id) {
+ var tmpsStar = document.getElementById(id).getElementsByTagName("svg");
+ for (var i = 0; i < 5; i++) {
+ var svgClass = (rating > i) ?
+ " star_tmpselected" :
+ " star_tmpunselected";
+ var tmpClass = tmpsStar[i].getAttribute("class") + svgClass;
+ tmpsStar[i].setAttribute("class", tmpClass);
+ }
+};
+BookView.prototype.dellfillstarsTmp = function(id) {
+ var star = document.getElementById(id).getElementsByTagName("svg");
+ for (var i = 0; i < 5; i++) {
+ var clazz = star[i].getAttribute("class").split(" ")[0];
+ star[i].setAttribute("class", clazz);
+ }
+};
+BookView.prototype.onMouseOverStar = function(event) {
+ if (event.target.id !== "") {
+ var rating = event.target[starRatingSymb];
+ var parent = event.target[starParentSymb];
+ this.fillstarsTmp(rating, parent);
+ }
+};
+BookView.prototype.onMouseOutOfStar = function(event) {
+ var parent = event.target[starParentSymb];
+ if (parent) this.dellfillstarsTmp(parent);
+};
+BookView.prototype.addStars = function(book, bookElement) {
+ var id = book.id;
+ var rating = document.createElement("div");
+ rating.className = "booksCont_stars";
+ rating.id = id.toString();
+ rating.innerHTML += star + star + star + star + star;
+ bookElement.appendChild(rating);
+ this.setStarsId(bookElement, id);
+ this.fillstars(book.rating, bookElement);
+};
+BookView.prototype.setStarsId = function(book, id) {
+ var stars = book.getElementsByTagName("path");
+ for (var i = 0; i < 5; i++) {
+ stars[i].setAttribute("id", "book_" + id + "star" + (i + 1));
+ stars[i][bookIdSymb] = id;
+ stars[i][starParentSymb] = "book_" + id;
+ stars[i][starRatingSymb] = i + 1;
+ }
+};
+BookView.prototype.fillstars = function(rating, bookElement) {
+ var stars = bookElement.getElementsByTagName("svg");
+ for (var i = 0; i < 5; i++) {
+ var svgClass = (rating > i) ?
+ "star_selected" :
+ "star_unselected";
+ stars[i].setAttribute("class", svgClass);
+ }
+};
+BookView.prototype.createBook = function(book) {
+ var bookElement = document.createElement("div");
+ var img = document.createElement("img");
+ var title = document.createElement("h3");
+ var author = document.createElement("p");
+ bookElement.className = "article_books_book";
+ bookElement.id = "book_" + book.id;
+ img.src = book.image_url;
+ title.innerHTML = book.title;
+ author.innerHTML = "by " + book.author.firstName + " " + book.author.lastName;
+ bookElement.appendChild(img);
+ bookElement.appendChild(title);
+ bookElement.appendChild(author);
+ this.addStars(book, bookElement);
+ return bookElement;
+};
+BookView.prototype.prepareItems = function() {
+ var items = this.items;
+ items.btnAllBooks = document.getElementById("allBooks");
+ items.btnMostRecent = document.getElementById("mostRecent");
+ items.btnMostPopular = document.getElementById("mostPopular");
+ items.btnFreeBooks = document.getElementById("freeBooks");
+ items.topFilter = [
+ items.btnAllBooks,
+ items.btnMostRecent,
+ items.btnMostPopular,
+ items.btnFreeBooks
+ ];
+ items.topFilter.forEach(btn => {
+ const item = btn;
+ item[isSelectedSymb] = false;
+ item[groupSymb] = "topFilter";
+ });
+ this.setSelectionItem(items.btnAllBooks);
+
+ items.inpSearch = document.getElementById("searchBox");
+ items.btnMustReadTitles = document.getElementById("MustReadTitles");
+ items.btnBestOfList = document.getElementById("BestOfList");
+ items.btnClassicNovels = document.getElementById("ClassicNovels");
+ items.btnNonFiction = document.getElementById("NonFiction");
+ items.leftFilter = [
+ items.btnMustReadTitles,
+ items.btnBestOfList,
+ items.btnClassicNovels,
+ items.btnNonFiction
+ ];
+ items.leftFilter.forEach(btn => {
+ const item = btn;
+ item[isSelectedSymb] = false;
+ item[groupSymb] = "leftFilter";
+ });
+ items.btnAddBook = document.getElementById("buttonAdd");
+};
+BookView.prototype.changeCategorySelection = function(target) {
+ const item = target;
+ if (item[groupSymb] === "topFilter") {
+ this.items.topFilter.forEach(btn => {
+ if (item !== btn) this.removeSelectionItem(btn);
+ })
+ this.setSelectionItem(item);
+ }
+ if (item[groupSymb] === "leftFilter") {
+ this.items.leftFilter.forEach(btn => {
+ if (item !== btn) this.removeSelectionItem(btn);
+ })
+ if (item[isSelectedSymb]) {
+ this.removeSelectionItem(item);
+ } else {
+ this.setSelectionItem(item);
+ }
+ }
+};
+BookView.prototype.setSelectionItem = function(elem) {
+ const item = elem;
+ if (!item[isSelectedSymb]) {
+ item[isSelectedSymb] = true
+ item.className = "selected-filter";
+ }
+};
+BookView.prototype.removeSelectionItem = function(elem) {
+ const item = elem;
+ if (item[isSelectedSymb]) {
+ item[isSelectedSymb] = false;
+ item.className = "";
+ }
+};
\ No newline at end of file
diff --git a/src/library/scripts/history/historyController.js b/src/library/scripts/history/historyController.js
new file mode 100644
index 000000000..131ae69f5
--- /dev/null
+++ b/src/library/scripts/history/historyController.js
@@ -0,0 +1,21 @@
+function HistoryController() {
+ Controller.call(this);
+ this.view = new HistoryView();
+ return this;
+}
+HistoryController.prototype = Object.create(Controller.prototype);
+HistoryController.prototype.constructor = HistoryController;
+HistoryController.prototype.onNewEvent = function (event, time) {
+ var record = new HistoryRecord();
+ record.event = event;
+ record.time = time;
+ var store = this.store;
+ store.beginUpdate();
+ try {
+ store.add(record);
+ store.sort((item) => item.time, true);
+ }
+ finally {
+ store.endUpdate();
+ }
+};
\ No newline at end of file
diff --git a/src/library/scripts/history/historyModel.js b/src/library/scripts/history/historyModel.js
new file mode 100644
index 000000000..a607b6d72
--- /dev/null
+++ b/src/library/scripts/history/historyModel.js
@@ -0,0 +1,4 @@
+function HistoryRecord() {
+ this.event = "";
+ this.cost = new Date();
+}
diff --git a/src/library/scripts/history/historyView.js b/src/library/scripts/history/historyView.js
new file mode 100644
index 000000000..e9420b736
--- /dev/null
+++ b/src/library/scripts/history/historyView.js
@@ -0,0 +1,27 @@
+function HistoryView() {
+ var _this = View.call(this);
+ _this.containerName = "history";
+ return _this;
+}
+HistoryView.prototype = Object.create(View.prototype);
+HistoryView.prototype.constructor = HistoryView;
+HistoryView.prototype.updateContainer = function() {
+ View.prototype.updateContainer.call(this);
+ const { container } = this;
+ for (const record of this.store) {
+ container.appendChild(this.createRecord(record));
+ }
+};
+HistoryView.prototype.createRecord = function(record) {
+ var recordElement = document.createElement("div");
+ recordElement.className = "navigation__item-history";
+ var message = document.createElement("p");
+ message.innerHTML = record.event;
+ var time = document.createElement("p");
+ time.className = "navigation__clock";
+ var currentTime = new Date();
+ time.innerHTML = Math.round((currentTime.getTime() - record.time.getTime()) / 1000 / 60) + " minutes ago";
+ recordElement.appendChild(message);
+ recordElement.appendChild(time);
+ return recordElement;
+};
\ No newline at end of file
diff --git a/src/library/scripts/lib/container.js b/src/library/scripts/lib/container.js
new file mode 100644
index 000000000..d582b38f9
--- /dev/null
+++ b/src/library/scripts/lib/container.js
@@ -0,0 +1,3 @@
+function ItemContainer() {
+ this.items = {};
+}
\ No newline at end of file
diff --git a/src/library/scripts/lib/controller.js b/src/library/scripts/lib/controller.js
new file mode 100644
index 000000000..29dfae6ae
--- /dev/null
+++ b/src/library/scripts/lib/controller.js
@@ -0,0 +1,18 @@
+function Controller() {
+ return ItemContainer.call(this);
+}
+Controller.prototype = Object.create(ItemContainer.prototype);
+Controller.prototype.constructor = Controller;
+Controller.prototype.init = function () {
+ this.store = new Store();
+ this.view.initView(this.store);
+ this.items = this.view.getItems();
+ this.prepareListeners();
+ this.prepareStore();
+};
+Controller.prototype.prepareListeners = function () {
+ var _this = this;
+ this.store.subscribe(() => _this.view.onUpdate());
+};
+Controller.prototype.prepareStore = function () {
+};
diff --git a/src/library/scripts/lib/star.js b/src/library/scripts/lib/star.js
new file mode 100644
index 000000000..29442256d
--- /dev/null
+++ b/src/library/scripts/lib/star.js
@@ -0,0 +1 @@
+var star = '';
diff --git a/src/library/scripts/lib/store.js b/src/library/scripts/lib/store.js
new file mode 100644
index 000000000..3de7a7251
--- /dev/null
+++ b/src/library/scripts/lib/store.js
@@ -0,0 +1,113 @@
+function Store() {
+ this.items = [];
+ this.sortedIndexes = [];
+ this.filteredIndexes = [];
+ this.filterRules = [];
+ this.delegateList = [];
+ this.suppressedChanges = 0;
+}
+Store.prototype.changed = function() {
+ if (this.suppressedChanges === 0) {
+ for (const delegate of this.delegateList) {
+ delegate();
+ }
+ }
+};
+Store.prototype.beginUpdate = function() {
+ this.suppressedChanges++;
+};
+Store.prototype.endUpdate = function() {
+ if (this.suppressedChanges === 0) {
+ throw new Error("Cant resume unsupressed events");
+ }
+ this.suppressedChanges--;
+ this.changed();
+};
+Store.prototype.add = function(item) {
+ this.items.push(item);
+ this.sortedIndexes.push(this.sortedIndexes.length);
+ this.filter();
+};
+Store.prototype.resetFilteredIndexes = function() {
+ this.filteredIndexes = [...this.sortedIndexes];
+};
+Store.prototype.filter = function() {
+ this.resetFilteredIndexes();
+ for (const rule of this.filterRules) {
+ this.applyFilterRule(rule.prop);
+ }
+ this.changed();
+};
+Store.prototype.applyFilterRule = function(rule) {
+ var result = [];
+ var filteredIndexes = this.filteredIndexes;
+ for (const index of filteredIndexes) {
+ if (rule(this.getRealAt(index))) {
+ result.push(index);
+ }
+ }
+ this.filteredIndexes = result;
+};
+Store.prototype.sort = function(propGetter, desc) {
+ var items = this.items;
+ var res = desc ? -1 : 1;
+ this.sortedIndexes.sort(function(a, b) {
+ if (propGetter(items[a]) > propGetter(items[b])) {
+ return res;
+ }
+ if (propGetter(items[a]) < propGetter(items[b])) {
+ return -res;
+ }
+ return 0;
+ });
+ this.filter();
+};
+Store.prototype.dropSort = function() {
+ var sortedIndexes = this.sortedIndexes;
+ for (var i = 0; i < sortedIndexes.length; i++) {
+ sortedIndexes[i] = i;
+ }
+ this.filter();
+};
+Store.prototype.addPropFilter = function(propFunc, keyValue) {
+ this.filterRules.push({ key: keyValue, prop: propFunc });
+};
+Store.prototype.removePropFilter = function(key) {
+ var index = this.filterRules.findIndex(function(obj) { return obj.key === key; });
+ if (index >= 0) {
+ this.filterRules.splice(index, 1);
+ }
+};
+Store.prototype.findIndex = function(cb) {
+ var items = this.items;
+ for (var i = 0; i < items.length; i++) {
+ var value = items[i];
+ if (cb(value)) {
+ return i;
+ }
+ }
+ return -1;
+};
+Store.prototype.getRealAt = function(index) {
+ return this.items[index];
+};
+Store.prototype.getFilteredAt = function(index) {
+ return this.getRealAt(this.filteredIndexes[index]);
+};
+Store.prototype.getAt = function(index) {
+ return this.getFilteredAt(index);
+};
+Store.prototype.subscribe = function(delegate) {
+ this.delegateList.push(delegate);
+};
+Store.prototype.unsubscribe = function(delegate) {
+ var index = this.delegateList.indexOf(delegate);
+ if (index >= 0) {
+ this.delegateList.splice(index, 1);
+ }
+};
+Store.prototype[Symbol.iterator] = function*() {
+ for (const item of this.filteredIndexes) {
+ yield this.getRealAt(item);
+ }
+};
\ No newline at end of file
diff --git a/src/library/scripts/lib/view.js b/src/library/scripts/lib/view.js
new file mode 100644
index 000000000..2cbc2330d
--- /dev/null
+++ b/src/library/scripts/lib/view.js
@@ -0,0 +1,27 @@
+
+function View() {
+ return ItemContainer.call(this) || this;
+}
+View.prototype = Object.create(ItemContainer.prototype);
+View.prototype.constructor = View;
+View.prototype.prepareContainer = function (containerName) {
+ this.container = document.getElementById(containerName);
+};
+View.prototype.clearContainer = function () {
+ this.container.innerHTML = "";
+};
+View.prototype.updateContainer = function () {
+ this.clearContainer();
+};
+
+View.prototype.initView = function (store) {
+ this.store = store;
+ this.prepareContainer(this.containerName);
+ this.updateContainer();
+};
+View.prototype.getItems = function () {
+ return this.items;
+};
+View.prototype.onUpdate = function () {
+ this.updateContainer();
+};
\ No newline at end of file
diff --git a/src/library/scripts/modalWindow/modalWindowController.js b/src/library/scripts/modalWindow/modalWindowController.js
new file mode 100644
index 000000000..d13671420
--- /dev/null
+++ b/src/library/scripts/modalWindow/modalWindowController.js
@@ -0,0 +1,73 @@
+function ModalWindowController() {
+ Controller.call(this);
+ this.view = new ModalWindowView();
+ this.onAddBookEventHandler = function () { };
+ return this;
+};
+ModalWindowController.prototype = Object.create(Controller.prototype);
+ModalWindowController.prototype.constructor = ModalWindowController;
+ModalWindowController.prototype.setonAddBookEventHandler = function (cb) {
+ this.onAddBookEventHandler = cb;
+};
+ModalWindowController.prototype.setActive = function () {
+ this.view.onShow();
+};
+ModalWindowController.prototype.hide = function () {
+ this.view.onHide();
+};
+
+ModalWindowController.prototype.addBook = function (e) {
+ if (this.validation()) {
+ const bookSource = this.createBookSource();
+ this.onAddBookEventHandler(bookSource);
+ this.hide();
+ this.view.updateContainer();
+ }
+};
+
+ModalWindowController.prototype.createBookSource = function () {
+ console.log("click");
+ const book = new Book();
+ const { items } = this;
+ book.author = {};
+ book.author.firstName = items.AuthorName.value;
+ book.author.lastName = items.AuthorNameLast.value;
+ book.categories = [];
+ if (items.rdbMustRead.checked) {
+ book.categories.push("must_read");
+ }
+ if (items.rdbBest.checked) {
+ book.categories.push("best");
+ }
+ if (items.rdbClassic.checked) {
+ book.categories.push("classic");
+ }
+ if (items.rdbNonFiction.checked) {
+ book.categories.push("non_fiction");
+ }
+ book.cost = items.Cost.value;
+ book.image_url = items.ImageUrl.value;
+ book.rating = 0;
+ book.title = items.Title.value;
+ book.createdAt = new Date();
+ book.updatedAt = new Date();
+ return book;
+}
+ModalWindowController.prototype.validation = function () {
+ const itemsModal = this.view.items;
+ let result;
+ if (itemsModal.Title.value.length !== 0 && itemsModal.AuthorName.value.length !== 0 && itemsModal.AuthorNameLast.value.length !== 0 && itemsModal.Cost.value.length !== 0 && itemsModal.ImageUrl.value.length !== 0) {
+ result = true;
+ } else {
+ this.view.onShowHint();
+ result = false;
+ }
+ return result;
+};
+
+ModalWindowController.prototype.prepareListeners = function () {
+ Controller.prototype.prepareListeners.call(this);
+ var items = this.items;
+ items.btnResetForm.addEventListener("click", this.hide.bind(this));
+ items.btnSbmtForm.addEventListener("click", this.addBook.bind(this));
+};
\ No newline at end of file
diff --git a/src/library/scripts/modalWindow/modalWindowView.js b/src/library/scripts/modalWindow/modalWindowView.js
new file mode 100644
index 000000000..89ff9e0b0
--- /dev/null
+++ b/src/library/scripts/modalWindow/modalWindowView.js
@@ -0,0 +1,51 @@
+function ModalWindowView() {
+ View.call(this);
+ this.containerName = "addBooKModal";
+ this.prepareItems();
+ return this;
+}
+ModalWindowView.prototype = Object.create(View.prototype);
+ModalWindowView.prototype.constructor = ModalWindowView;
+ModalWindowView.prototype.updateContainer = function () {
+ this.items.AuthorName.value = "";
+ this.items.AuthorNameLast.value = "";
+ this.items.rdbMustRead.checked= false;
+ this.items.rdbBest.checked= false;
+ this.items.rdbClassic.checked= false;
+ this.items.rdbNonFiction.checked= false;
+ this.items.Cost.value = "";
+ this.items.ImageUrl.value = "";
+ this.items.Title.value = "";
+ this.onHideHint();
+};
+ModalWindowView.prototype.onShow = function () {
+ this.container.style.display = "flex";
+}
+ModalWindowView.prototype.onHide = function () {
+ this.container.style.display = "none";
+}
+ModalWindowView.prototype.onHideHint = function () {
+ this.items.Hint.style.display = "none";
+}
+ModalWindowView.prototype.onShowHint = function () {
+ this.items.Hint.style.display = "block";
+}
+
+ModalWindowView.prototype.prepareItems = function () {
+ var items = this.items;
+ items.AuthorName = document.getElementById("nameAuthor");
+ items.AuthorNameLast = document.getElementById("lastnameAuthor");
+ items.rdbMustRead = document.getElementById("radioMustRead");
+ items.rdbBest = document.getElementById("radioBest");
+ items.rdbClassic = document.getElementById("radioClassic");
+ items.rdbNonFiction = document.getElementById("radioNon");
+ items.Cost = document.getElementById("cost");
+ items.ImageUrl = document.getElementById("image");
+ items.Rating = 1;
+ items.Title = document.getElementById("title");
+
+ items.Hint = document.getElementById("formHint");
+
+ items.btnSbmtForm = document.getElementById("formSubmit");
+ items.btnResetForm = document.getElementById("formReset");
+};
\ No newline at end of file
diff --git a/src/library/scripts/utils/debounce.js b/src/library/scripts/utils/debounce.js
new file mode 100644
index 000000000..87048ae0a
--- /dev/null
+++ b/src/library/scripts/utils/debounce.js
@@ -0,0 +1,10 @@
+function debounce(cb, delay) {
+ var timer = null;
+ return function(...args) {
+ if (timer) clearTimeout(timer);
+ timer = setTimeout(function() {
+ cb.apply(this, args);
+ timer = null;
+ }, delay);
+ };
+}
\ No newline at end of file
diff --git a/src/library/scripts/utils/idGenerator.js b/src/library/scripts/utils/idGenerator.js
new file mode 100644
index 000000000..100b2ce49
--- /dev/null
+++ b/src/library/scripts/utils/idGenerator.js
@@ -0,0 +1,5 @@
+var globalId = 0;
+
+function generateId() {
+ return globalId++;
+}
\ No newline at end of file
diff --git a/src/library/scripts/utils/request.js b/src/library/scripts/utils/request.js
new file mode 100644
index 000000000..e4cebf8b3
--- /dev/null
+++ b/src/library/scripts/utils/request.js
@@ -0,0 +1,12 @@
+async function request(url) {
+ try {
+ var response = await fetch(url);
+ if (response.ok) {
+ return await response.json();
+ }
+ throw new Error(response.statusText);
+ } catch (err) {
+ alert("Server request failed: " + err);
+ }
+ return undefined;
+}
\ No newline at end of file