diff --git a/package-lock.json b/package-lock.json
index 24c1378..d30eec4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3375,7 +3375,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -3396,12 +3397,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3416,17 +3419,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3543,7 +3549,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -3555,6 +3562,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3569,6 +3577,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3576,12 +3585,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3600,6 +3611,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3680,7 +3692,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -3692,6 +3705,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3777,7 +3791,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -3813,6 +3828,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -3832,6 +3848,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3875,12 +3892,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -7221,7 +7240,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -7242,12 +7262,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7262,17 +7284,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7389,7 +7414,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -7401,6 +7427,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7415,6 +7442,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -7422,12 +7450,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -7446,6 +7476,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -7526,7 +7557,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -7538,6 +7570,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -7623,7 +7656,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7659,6 +7693,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -7678,6 +7713,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7721,12 +7757,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
diff --git a/package.json b/package.json
index 04a3e41..49fb3e6 100644
--- a/package.json
+++ b/package.json
@@ -21,12 +21,12 @@
"test": "react-scripts test",
"build": "react-scripts build",
"lint": "npm-run-all lint:*",
- "lint:js": "eslint '**/*.{js,jsx}'",
- "lint:css": "stylelint '**/*.css'",
+ "lint:js": "eslint \"**/*.{js,jsx}\"",
+ "lint:css": "stylelint \"**/*.css\"",
"lint-fix": "npm-run-all lint-fix:*",
- "lint-fix:js": "eslint --fix '**/*.{js,jsx}'",
- "lint-fix:css": "stylelint --fix '**/*.css'",
- "format": "prettier --write '**/*.{js,jsx,css,json,md}'",
+ "lint-fix:js": "eslint --fix \"**/*.{js,jsx}\"",
+ "lint-fix:css": "stylelint --fix \"**/*.css\"",
+ "format": "prettier --write \"**/*.{js,jsx,css,json,md}\"",
"now-build": "npm run build"
},
"browserslist": [
diff --git a/public/fonts/HelveticaNeue.ttf b/public/fonts/HelveticaNeue.ttf
new file mode 100644
index 0000000..7864fce
Binary files /dev/null and b/public/fonts/HelveticaNeue.ttf differ
diff --git a/public/fonts/Yandex Sans Display-Regular.ttf b/public/fonts/Yandex Sans Display-Regular.ttf
new file mode 100644
index 0000000..29a8017
Binary files /dev/null and b/public/fonts/Yandex Sans Display-Regular.ttf differ
diff --git a/public/images/cat-face.png b/public/images/cat-face.png
new file mode 100644
index 0000000..b86d178
Binary files /dev/null and b/public/images/cat-face.png differ
diff --git a/public/images/logo.png b/public/images/logo.png
new file mode 100644
index 0000000..ae13ce7
Binary files /dev/null and b/public/images/logo.png differ
diff --git a/public/images/owl-face.jpg b/public/images/owl-face.jpg
new file mode 100644
index 0000000..829e062
Binary files /dev/null and b/public/images/owl-face.jpg differ
diff --git a/public/images/sova.png b/public/images/sova.png
new file mode 100644
index 0000000..e8b5dd7
Binary files /dev/null and b/public/images/sova.png differ
diff --git a/public/images/spam.png b/public/images/spam.png
new file mode 100644
index 0000000..6ab6df2
Binary files /dev/null and b/public/images/spam.png differ
diff --git a/public/index.html b/public/index.html
index 9a8ef8f..5a426cf 100644
--- a/public/index.html
+++ b/public/index.html
@@ -7,8 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
-
-
React App
+ Почта
You need to enable JavaScript to run this app.
diff --git a/src/app/app.css b/src/app/app.css
index 1c4d511..f20cbff 100644
--- a/src/app/app.css
+++ b/src/app/app.css
@@ -1,27 +1,21 @@
-.app {
- text-align: center;
+@font-face {
+ font-family: Yandex Sans;
+ src: url('/fonts/Yandex Sans Display-Regular.ttf');
}
-.app-header {
- display: flex;
- min-height: 100vh;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: #282c34;
- color: #fff;
- font-size: calc(10px + 2vmin);
+@font-face {
+ font-family: Helvetica Neue;
+ src: url('/fonts/HelveticaNeue.ttf');
}
-.app-link {
- color: #61dafb;
+html {
+ height: 100%;
}
-@keyframes app-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+body {
+ min-width: 800px;
+ height: 100%;
+ min-height: 500px;
+ margin: 0;
+ background-color: #e5eaf0;
}
diff --git a/src/app/app.jsx b/src/app/app.jsx
index f759eed..ce607b5 100644
--- a/src/app/app.jsx
+++ b/src/app/app.jsx
@@ -1,24 +1,15 @@
import React, { Component } from 'react';
+import Header from './header/header';
+import Main from './main/main';
import './app.css';
export class App extends Component {
render() {
return (
-
-
+
+
+
);
}
diff --git a/src/app/header/hamburger/hamburger.css b/src/app/header/hamburger/hamburger.css
new file mode 100644
index 0000000..30c407f
--- /dev/null
+++ b/src/app/header/hamburger/hamburger.css
@@ -0,0 +1,11 @@
+.hamburger {
+ width: 20px;
+ margin: 15px 11px 0px 0px;
+ float: left;
+}
+
+.hamburger__div {
+ height: 3px;
+ margin: 4px 0;
+ background-color: black;
+}
diff --git a/src/app/header/hamburger/hamburger.jsx b/src/app/header/hamburger/hamburger.jsx
new file mode 100644
index 0000000..dd6fb20
--- /dev/null
+++ b/src/app/header/hamburger/hamburger.jsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import './hamburger.css';
+
+function Header() {
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/src/app/header/header.css b/src/app/header/header.css
new file mode 100644
index 0000000..2e23e82
--- /dev/null
+++ b/src/app/header/header.css
@@ -0,0 +1,14 @@
+header {
+ width: 100%;
+ height: 56px;
+ box-sizing: border-box;
+ padding: 0px 20px 0px 22px;
+ font-family: Yandex Sans, sans-serif;
+ font-size: 15px;
+ text-align: center;
+}
+
+.header__inline-element {
+ display: inline-block;
+ text-align: left;
+}
diff --git a/src/app/header/header.jsx b/src/app/header/header.jsx
new file mode 100644
index 0000000..2d75ae4
--- /dev/null
+++ b/src/app/header/header.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import Hamburger from './hamburger/hamburger';
+import Logo from './logo/logo';
+import Search from './search/search';
+
+import './header.css';
+
+function Header() {
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/src/app/header/logo/logo.css b/src/app/header/logo/logo.css
new file mode 100644
index 0000000..9cff198
--- /dev/null
+++ b/src/app/header/logo/logo.css
@@ -0,0 +1,4 @@
+.logo {
+ margin-top: 12px;
+ float: left;
+}
diff --git a/src/app/header/logo/logo.jsx b/src/app/header/logo/logo.jsx
new file mode 100644
index 0000000..4860e4a
--- /dev/null
+++ b/src/app/header/logo/logo.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+import './logo.css';
+
+function Logo() {
+ return (
+
+
+
+ );
+}
+
+export default Logo;
diff --git a/src/app/header/search/search.css b/src/app/header/search/search.css
new file mode 100644
index 0000000..4c2dbea
--- /dev/null
+++ b/src/app/header/search/search.css
@@ -0,0 +1,27 @@
+.header__search {
+ margin-top: 11px;
+}
+
+.search {
+ width: 301px;
+ height: 32px;
+ background-color: white;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
+ line-height: 32px;
+}
+
+.search__text {
+ overflow: hidden;
+ width: 255px;
+ margin: auto 0 auto 16px;
+ color: black;
+ float: left;
+ font-weight: 400;
+}
+
+.search__close {
+ margin: auto 9px;
+ color: grey;
+ float: right;
+ font-size: 20px;
+}
diff --git a/src/app/header/search/search.jsx b/src/app/header/search/search.jsx
new file mode 100644
index 0000000..8122766
--- /dev/null
+++ b/src/app/header/search/search.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import './search.css';
+
+function Search() {
+ return (
+
+ );
+}
+
+export default Search;
diff --git a/src/app/main/mailbox/data.js b/src/app/main/mailbox/data.js
new file mode 100644
index 0000000..f32670a
--- /dev/null
+++ b/src/app/main/mailbox/data.js
@@ -0,0 +1,192 @@
+export const MAX_VISIBLE_MAILS = 5;
+export const MIN_AUTO_MAIL_INTERVAL = 10;
+export const MIN_AUTO_MAIL_CONSEQUENT_SPAN_INTERVAL = 300000;
+export const MAX_AUTO_MAIL_INTERVAL = 600000;
+
+let mailCnt = 0;
+
+export class Mail {
+ constructor(img, author, title, date, text, old, state = 'hidden') {
+ this.img = img;
+ this.author = author;
+ this.title = title;
+ this.date = date;
+ this.text = text;
+ this.old = old;
+ this.id = mailCnt++;
+ this.state = state;
+ this.checked = false;
+ this.deleted = false;
+ }
+
+ setCheck(value) {
+ this.checked = value;
+ }
+
+ markDeleted() {
+ this.deleted = true;
+ }
+}
+
+export const PRELOADED_MAILS = [
+ new Mail(
+ 'images/owl-face.jpg',
+ 'Сова',
+ 'От совы',
+ 'Mar 9',
+ `
Как падают кошки
+
+
+ Большинство представителей семейства кошачьих имеет склонность к обзору местности с высоты. Крупные лесные кошки-рыси вообше значительную часть времени проводят на деревьях, находясь в засаде или погоне за добычей. А львы и леопарды в саваннах Африки приспособились в жаркое время отдыхать на деревьях, распластавшись на ветках и опустив вниз лапы. Случается, однако, что кошки не удерживаются на высоте и падают. Но и в падении у них есть свои особенности. Многим приходилось наблюдать, как падает обыкновенная кошка, сорвавшись с карниза дома, с дерева или с забора. Сначала она падает к земле головой, спиной или боком, но затем, сделав резкий поворот в воздухе, вывертывается и становится на лапки. И так всегда. Как бы ни падала кошка, приземляется она всегда на лапки и тотчас же может бежать дальше. Такое мгновенное выравнивание положения тела у кошек обеспечивается действием ее вестибулярного аппарата.
+
+
+
+ При падении кошки вестибулярный аппарат помогает ей осуществить ряд последовательно возникающих рефлексов и приземлиться на лапы. Ненормальное положение тела в пространстве приводит в раздражение отолитовый прибор каналов внутреннего уха кошки. В ответ на это раздражение происходит рефлекторное сокращение мускулов шеи, приводящих голову животного в нормальное положение по отношению к горизонту. Это - первый рефлекс. Сокращение же шейных мышц и постановка шеи при повороте головы являются возбудителем для осуществления другого рефлекса - сокращения определенных мышц туловища и конечностей. В итоге животное принимает правильное положение.
+
+
+ Этот сложный врожденный цепной рефлекс выработался у некоторых животных как приспособление к образу жизни. Ведь животным, особенно из семейства кошачьих, часто приходится во время охоты прыгать и падать с деревьев, скал или со спины своей жертвы. И не будь у них этого приспособительного рефлекса, от них не только ушла бы добыча, но иной раз и самому охотнику пришлось бы пострадать от зубов, рогов или копыт своей жертвы.
+
+ Текст статьи взят с сайта petsi.net
+ `,
+ false,
+ 'showed'
+ ),
+ new Mail(
+ 'images/cat-face.png',
+ 'Кот',
+ 'Старое сообщение',
+ 'Feb 23',
+ 'Какое-то старое сообщение',
+ true,
+ 'showed'
+ ),
+ new Mail(
+ 'images/spam.png',
+ 'Неспамнеспамнеспамнеспам',
+ 'Легкий способ зарабатывать 10000000000 в секунду, нужно всего-лишь...',
+ 'Jan 1',
+ '[Читать продолжение в источнике]',
+ false,
+ 'showed'
+ )
+];
+
+export const CAT_NAMES = [
+ 'Барсик',
+ 'Боня',
+ 'Бакс',
+ 'Алекс',
+ 'Бади',
+ 'Амур',
+ 'Абуссель',
+ 'Баксик',
+ 'Кузя',
+ 'Персик',
+ 'Абрек',
+ 'Абрикос',
+ 'Тимоша',
+ 'Авалон',
+ 'Саймон',
+ 'Бурбузяка Жабс',
+ 'Марсик',
+ 'Абу',
+ 'Маркиз',
+ 'Аадон',
+ 'Дымок',
+ 'Лаки',
+ 'Сёма',
+ 'Симба',
+ 'Абрамович',
+ 'Пушок',
+ 'Айс',
+ 'Бося',
+ 'Кекс',
+ 'Басик',
+ 'Алмаз',
+ 'Макс',
+ 'Гарфилд',
+ 'Феликс',
+ 'Том',
+ 'Тиша',
+ 'Тишка',
+ 'Цезарь',
+ 'Мася',
+ 'Абакан',
+ 'Лакки',
+ 'Васька',
+ 'Марсель',
+ 'Адольф',
+ 'Вася',
+ 'Бабасик',
+ 'Зевс',
+ 'Вольт',
+ 'Лео',
+ 'Адидас',
+ 'Зефир',
+ 'Максик',
+ 'Вайс',
+ 'Барс',
+ 'Кокос',
+ 'Рыжик',
+ 'Мартин',
+ 'Айс-Крим',
+ 'Томас',
+ 'Филя',
+ 'Нафаня',
+ 'Дарсик',
+ 'Марс',
+ 'Валера',
+ 'Абориген',
+ 'Тошка',
+ 'Базиль',
+ 'Сосисыч',
+ 'Абрико',
+ 'Масик',
+ 'Абус',
+ 'Абсент',
+ 'Умка',
+ 'Жужа',
+ 'Веня',
+ 'Каспер',
+ 'Грей',
+ 'Живчик',
+ 'Убийца мышей',
+ 'Глюк',
+ 'Патрик',
+ 'Оптимус Прайм',
+ 'Виски',
+ 'Акакий',
+ 'Симка',
+ 'Тёма',
+ 'Баффи',
+ 'Аватар',
+ 'Гаврик',
+ 'Жан батист Гренуй',
+ 'Ганс',
+ 'Вегас',
+ 'Гаврюша',
+ 'Авдон',
+ 'Вин Дизель',
+ 'Вафлик',
+ 'Бонни',
+ 'Снежок',
+ 'Люцифер',
+ 'Базилио',
+ 'Тима',
+ 'Байрон'
+];
+
+export const MONTHS = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+];
diff --git a/src/app/main/mailbox/footer/footer.css b/src/app/main/mailbox/footer/footer.css
new file mode 100644
index 0000000..1de51e4
--- /dev/null
+++ b/src/app/main/mailbox/footer/footer.css
@@ -0,0 +1,22 @@
+.footer {
+ position: absolute;
+ bottom: 0;
+
+ width: 100%;
+ height: 35px;
+
+ border-top: 1px solid #e2e2e2;
+ color: #9b9b9b;
+ font-size: 11px;
+ line-height: 35px;
+}
+
+.footer__inline-element {
+ margin-right: 20px;
+ float: right;
+}
+
+.footer__link {
+ color: #9b9b9b;
+ text-decoration: none;
+}
diff --git a/src/app/main/mailbox/footer/footer.jsx b/src/app/main/mailbox/footer/footer.jsx
new file mode 100644
index 0000000..6924c15
--- /dev/null
+++ b/src/app/main/mailbox/footer/footer.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import './footer.css';
+
+function Footer() {
+ return (
+
+
© 2019 – 2019, Ямдекс
+
+
+
+ );
+}
+
+export default Footer;
diff --git a/src/app/main/mailbox/helper.js b/src/app/main/mailbox/helper.js
new file mode 100644
index 0000000..f4afb64
--- /dev/null
+++ b/src/app/main/mailbox/helper.js
@@ -0,0 +1,59 @@
+import { Mail, CAT_NAMES, MONTHS } from './data';
+
+function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min)) + min;
+}
+
+function getAuthor() {
+ // Cat names
+ const names = CAT_NAMES;
+ return names[getRandomInt(0, names.length)];
+}
+
+function getCurrentTime() {
+ return new Date().getTime();
+}
+
+function getDate() {
+ const date = new Date();
+ const months = MONTHS;
+ const day = date.getDate();
+ const monthIndex = date.getMonth();
+ return `${months[monthIndex]} ${day}`;
+}
+
+function getImg() {
+ const size = 100 + (getCurrentTime() % 200);
+ return `http://placekitten.com/${size}/${size}`;
+}
+
+function sendRequest(link) {
+ const xhttp = new XMLHttpRequest();
+ let res = '';
+ xhttp.onreadystatechange = () => {
+ if (xhttp.readyState === 4 && xhttp.status === 200) {
+ res = xhttp.responseText;
+ }
+ };
+ xhttp.open('GET', link, false);
+ xhttp.send();
+ return res;
+}
+
+function getTitle() {
+ return sendRequest(
+ 'https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=0&format=text'
+ );
+}
+
+function getText() {
+ return sendRequest(
+ 'https://baconipsum.com/api/?type=all-meat¶graphs=5&start-with-lorem=1&format=text'
+ );
+}
+
+function generateMail() {
+ return new Mail(getImg(), getAuthor(), getTitle(), getDate(), getText());
+}
+
+export { generateMail, getCurrentTime, getRandomInt };
diff --git a/src/app/main/mailbox/mail/mail.css b/src/app/main/mailbox/mail/mail.css
new file mode 100644
index 0000000..4d74a08
--- /dev/null
+++ b/src/app/main/mailbox/mail/mail.css
@@ -0,0 +1,98 @@
+.mailbox__mail {
+ width: 100%;
+ height: 40px;
+
+ border-bottom: 1px solid #e2e2e2;
+ color: #cccccc;
+ font-size: 13px;
+ line-height: 40px;
+ overflow-y: hidden;
+}
+
+.mailbox__mail-element {
+ display: inline-block;
+ overflow: hidden;
+ vertical-align: middle;
+}
+
+.mail__pic {
+ margin-right: 10px;
+}
+
+.pic__img {
+ width: 30px;
+ height: 30px;
+ vertical-align: middle;
+}
+
+.mail__author {
+ width: 155px;
+ color: black;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.mail__title {
+ width: calc(100% - 355px);
+ box-sizing: border-box;
+ padding-left: 10px;
+ color: black;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.mail__new {
+ font-weight: 700;
+}
+
+.mail__dot {
+ width: 10px;
+ height: 10px;
+ background-color: #6287bd;
+ border-radius: 50%;
+ visibility: hidden;
+}
+
+.mail__new .mail__dot {
+ visibility: visible;
+}
+
+.mail__time {
+ width: 50px;
+ margin-right: 20px;
+ color: #969799;
+ float: right;
+ font-weight: 500;
+ text-align: right;
+}
+
+.mailbox__mail[data-state='collapsed'] {
+ height: 0px;
+
+ border: none;
+ transition: height 0.4s;
+}
+
+.mailbox__mail[data-state='init'] {
+ height: 0px;
+
+ border-width: 0px;
+
+ animation: appearance 0.4s 1 ease-out forwards;
+}
+
+@keyframes appearance {
+ from {
+ height: 0px;
+ background-color: #6287bd;
+ }
+ to {
+ height: 40px;
+ background-color: white;
+ }
+}
+
+.mailbox__mail[data-state='showed'] {
+ height: 40px;
+ transition: height 0.4s;
+}
diff --git a/src/app/main/mailbox/mail/mail.jsx b/src/app/main/mailbox/mail/mail.jsx
new file mode 100644
index 0000000..a4808c4
--- /dev/null
+++ b/src/app/main/mailbox/mail/mail.jsx
@@ -0,0 +1,55 @@
+import React, { Component } from 'react';
+
+import './mail.css';
+
+export class Mail extends Component {
+ constructor(props) {
+ super(props);
+ this.trigger = React.createRef();
+ }
+
+ render() {
+ const { mail, onClick, onAnimationEnd } = this.props;
+ const checkboxId = `checkbox_${mail.id}`;
+
+ return (
+
{
+ if (event.key === 'Enter') {
+ this.trigger.current.click();
+ }
+ }}
+ >
+
+
+ mail.setCheck(event.target.checked)}
+ />
+
+
+
+
+
+
{mail.author}
+
+
{mail.title}
+
{mail.date}
+
+
+ );
+ }
+}
+
+export default Mail;
diff --git a/src/app/main/mailbox/mailbox.css b/src/app/main/mailbox/mailbox.css
new file mode 100644
index 0000000..f0b5f5f
--- /dev/null
+++ b/src/app/main/mailbox/mailbox.css
@@ -0,0 +1,118 @@
+button {
+ display: inherit;
+ padding: inherit;
+
+ border: inherit;
+ background-color: inherit;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ font-family: inherit;
+ outline: inherit;
+ text-align: inherit;
+}
+
+.mailbox {
+ position: relative;
+
+ overflow: hidden;
+ height: 100%;
+ box-sizing: border-box;
+ background-color: white;
+ border-radius: 3px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.34);
+ font-family: Helvetica Neue, sans-serif;
+}
+
+.checkbox {
+ display: inline-block;
+ margin: 0px 10px 0px 20px;
+ vertical-align: middle;
+}
+
+.checkbox__input {
+ position: relative;
+
+ display: none;
+ float: left;
+}
+
+.checkbox__span {
+ width: 16px;
+ height: 16px;
+
+ border: 1px solid;
+ border-radius: 3px;
+ float: left;
+}
+
+.checkbox__input:checked + .checkbox__span:before {
+ content: '✓';
+ float: left;
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 16px;
+}
+
+.mailbox__header {
+ top: 0;
+
+ width: 100%;
+ height: 35px;
+
+ border-bottom: 1px solid #e2e2e2;
+ color: #cccccc;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 35px;
+}
+
+.mailbox__header-element {
+ display: inline-block;
+ margin: 0px 14px 0px 10px;
+}
+
+.mailbox__mail-contents {
+ position: absolute;
+ top: 36px;
+ bottom: 35px;
+
+ display: none;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 20px;
+ margin: 0 auto;
+ overflow-y: scroll;
+ overflow-y: auto;
+}
+
+.mailbox__msg-close {
+ margin: 0px 10px;
+ color: grey;
+ float: right;
+ font-size: 40px;
+ font-weight: bold;
+}
+
+.mailbox__mail-list {
+ position: absolute;
+ top: 36px;
+ bottom: 35px;
+
+ width: 100%;
+ box-sizing: border-box;
+ margin: 0 auto;
+ overflow-y: scroll;
+}
+
+.mailbox__trigger {
+ display: none;
+}
+
+.mailbox__trigger:checked ~ .mailbox__mail-contents {
+ display: block;
+}
+
+.mailbox__trigger:checked + .mailbox__mail-list {
+ display: none;
+}
diff --git a/src/app/main/mailbox/mailbox.jsx b/src/app/main/mailbox/mailbox.jsx
new file mode 100644
index 0000000..ef7f804
--- /dev/null
+++ b/src/app/main/mailbox/mailbox.jsx
@@ -0,0 +1,193 @@
+import React, { Component } from 'react';
+import Footer from './footer/footer';
+import MailItem from './mail/mail';
+
+import './mailbox.css';
+
+import { generateMail, getCurrentTime, getRandomInt } from './helper';
+import {
+ PRELOADED_MAILS,
+ MAX_VISIBLE_MAILS,
+ MIN_AUTO_MAIL_INTERVAL,
+ MIN_AUTO_MAIL_CONSEQUENT_SPAN_INTERVAL,
+ MAX_AUTO_MAIL_INTERVAL
+} from './data';
+
+export class MailBox extends Component {
+ constructor() {
+ super();
+
+ // local non-concurrent data
+ this.previousMailTime = 0;
+ this.penultimateMailTime = 0;
+
+ // binding methods
+ this.updateState = this.updateState.bind(this);
+ this.newMail = this.newMail.bind(this);
+ this.autoMails = this.autoMails.bind(this);
+ this.deleteSelected = this.deleteSelected.bind(this);
+ this.updateVisibility = mails => {
+ let visibleMails = MAX_VISIBLE_MAILS;
+ return mails.map(mail => {
+ if (mail.deleted) return mail;
+
+ const newMail = mail;
+
+ if (visibleMails > 0) {
+ visibleMails--;
+ if (mail.state === 'collapsed') {
+ newMail.state = 'showed';
+ }
+ } else if (mail.state === 'showed') {
+ newMail.state = 'collapsed';
+ }
+
+ return newMail;
+ });
+ };
+
+ this.state = {
+ currentMail: 0,
+ mails: PRELOADED_MAILS
+ };
+
+ // initialize automatic generation of mails
+ const timeout = getRandomInt(MIN_AUTO_MAIL_INTERVAL, MAX_AUTO_MAIL_INTERVAL);
+ setTimeout(this.autoMails, timeout);
+ }
+
+ componentDidMount() {
+ window.newMail = this.newMail;
+ }
+
+ updateState(genState) {
+ this.setState(prevState => genState(prevState));
+ }
+
+ autoMails() {
+ const currentTime = getCurrentTime();
+ const timeout = getRandomInt(
+ Math.max(
+ MIN_AUTO_MAIL_INTERVAL,
+ Math.min(currentTime - this.penultimateMailTime, MIN_AUTO_MAIL_CONSEQUENT_SPAN_INTERVAL)
+ ),
+ MAX_AUTO_MAIL_INTERVAL
+ );
+ this.penultimateMailTime = this.previousMailTime;
+ this.previousMailTime = currentTime + timeout;
+ this.newMail();
+ setTimeout(this.autoMails, timeout);
+ }
+
+ newMail() {
+ const newMail = generateMail();
+ newMail.state = 'init';
+
+ this.updateState(prevState => {
+ const { mails, currentMail } = prevState;
+ const newMails = mails;
+ newMails.unshift(newMail);
+ return {
+ currentMail,
+ mails: this.updateVisibility(newMails)
+ };
+ });
+ }
+
+ deleteSelected() {
+ this.updateState(prevState => {
+ const { mails, currentMail } = prevState;
+ const newMails = mails.map(mail => {
+ const newMail = mail;
+ if (mail.checked) {
+ newMail.state = 'collapsed';
+ newMail.markDeleted();
+ }
+ return newMail;
+ });
+ return {
+ currentMail,
+ mails: this.updateVisibility(newMails)
+ };
+ });
+ }
+
+ render() {
+ const currentMailObj = this.state.mails.find(mail => mail.id === this.state.currentMail);
+ const mailHTML = currentMailObj && currentMailObj.text;
+
+ return (
+
+
+
+
+
+
+
+ Переслать
+
+
+ Удалить
+
+
+ Это спам
+
+
+ Прочитано
+
+
+
+
+
+ {this.state.mails.map((mail, index) => (
+
+ this.updateState(prevState => {
+ return {
+ currentMail: mail.id,
+ mails: prevState.mails
+ };
+ })
+ }
+ onAnimationEnd={() => {
+ this.updateState(prevState => {
+ const { currentMail, mails } = prevState;
+ let newMails = mails;
+ if (mail.state === 'init') {
+ newMails[index].state = 'showed';
+ }
+ if (mail.deleted) {
+ newMails = mails.filter(curMail => curMail.id !== mail.id);
+ }
+ return {
+ currentMail,
+ mails: newMails
+ };
+ });
+ }}
+ />
+ ))}
+
+
+
+
+
+ );
+ }
+}
+
+export default MailBox;
diff --git a/src/app/main/main.css b/src/app/main/main.css
new file mode 100644
index 0000000..e72884a
--- /dev/null
+++ b/src/app/main/main.css
@@ -0,0 +1,17 @@
+main {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ min-width: inherit;
+ min-height: inherit;
+ box-sizing: border-box;
+ padding: 56px 20px 14px 22px;
+}
+
+.article__sova {
+ float: left;
+ shape-outside: circle(160px);
+}
diff --git a/src/app/main/main.jsx b/src/app/main/main.jsx
new file mode 100644
index 0000000..d4e1f3e
--- /dev/null
+++ b/src/app/main/main.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import MailWrapper from './mailbox/mailbox';
+import SidePanel from './side-panel/side-panel';
+
+import './main.css';
+
+function Main() {
+ return (
+
+
+
+
+ );
+}
+
+export default Main;
diff --git a/src/app/main/side-panel/side-panel.css b/src/app/main/side-panel/side-panel.css
new file mode 100644
index 0000000..42616dd
--- /dev/null
+++ b/src/app/main/side-panel/side-panel.css
@@ -0,0 +1,51 @@
+.side-panel {
+ margin-right: 22px;
+ float: left;
+ font-family: Helvetica Neue, sans-serif;
+}
+
+button {
+ display: inherit;
+ padding: inherit;
+
+ border: inherit;
+ background-color: inherit;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ font-family: inherit;
+ outline: inherit;
+ text-align: inherit;
+}
+
+.side-panel__button {
+ width: 147px;
+ box-sizing: border-box;
+ padding: 0 10px;
+ border-radius: 3px;
+}
+
+.side-panel__button_primary {
+ height: 32px;
+ margin-bottom: 8px;
+ background-color: #6287bd;
+ color: white;
+ font-size: 12px;
+ line-height: 32px;
+ text-align: center;
+}
+
+.side-panel__button_secondary {
+ height: 22px;
+ margin: 1px 0;
+ color: #707070;
+ font-size: 11px;
+ font-weight: 500;
+ line-height: 22px;
+}
+
+.side-panel__button_active {
+ background-color: #cdd6e4;
+ color: #555555;
+ font-weight: 700;
+}
diff --git a/src/app/main/side-panel/side-panel.jsx b/src/app/main/side-panel/side-panel.jsx
new file mode 100644
index 0000000..41af7f6
--- /dev/null
+++ b/src/app/main/side-panel/side-panel.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import './side-panel.css';
+
+function SidePanel() {
+ return (
+
+
+ Написать
+
+
+ Входящие
+
+
+ Отправленные
+
+
+ Удалённые
+
+
+ Спам
+
+
+ Черновики
+
+
+ Создать папку
+
+
+ );
+}
+
+export default SidePanel;
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 2b6e525..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,12 +0,0 @@
-body {
- padding: 0;
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
- 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
-}
diff --git a/src/index.jsx b/src/index.jsx
index ffc72ee..ce996f0 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -3,6 +3,4 @@ import ReactDOM from 'react-dom';
import { App } from './app';
-import './index.css';
-
ReactDOM.render(
, document.getElementById('root'));