├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── custom
├── main.js
├── scripts.js
├── startDemo.js
└── styles.css
├── docs
├── features.md
├── images
│ ├── logitech.png
│ ├── material-canvas.png
│ ├── material-printing.png
│ ├── ribbon-canvas.png
│ └── ribbon-printing.png
├── index.html
├── logitech.md
├── printing.md
└── shortcuts.md
├── gulpfile.js
├── index.html
├── package-lock.json
├── package.json
└── pictures
├── 2requests.png
├── author.png
├── demo-2.gif
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon-96x96.png
├── fetch-cancel.png
├── iterator-pattern.png
├── observer-pattern.png
├── palpatin-50.png
├── pipe-3.gif
├── rx-stachka-table-1-bg-2.png
├── rx-stachka-table-1-bg-3.png
├── rx-stachka-table-1.png
├── rx-stachka-table-2.png
├── rx-stachka-table-3.png
├── rx-stachka-table-4.png
├── rx-stachka-table-5.png
├── rx-stachka-table-6.png
├── rxJs.png
├── rxjs-clicks-1.png
├── rxjs-clicks-2-2.png
├── rxjs-clicks-2.png
├── rxjs-clicks-3.png
├── rxjs-clicks-4.png
├── rxjs-logo.png
├── rxjs-qr.png
├── rxmarbles-combine-latest.png
├── rxmarbles-filter.png
├── rxmarbles-switch-map.png
├── table-1.png
├── table-2.png
└── table-3.png
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 |
12 | [{bower.json,package.json,.travis.yml}]
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | prepared
4 | archive.zip
5 | .DS_Store
6 | .publish
7 | custom-scripts.js
8 | rxjs.todo
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License
2 |
3 | Copyright © 2010–2015 Vadim Makeev, http://pepelsbey.net/
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
11 | ---
12 |
13 | # Лицензия MIT
14 |
15 | Copyright © 2010–2015 Вадим Макеев, http://pepelsbey.net/
16 |
17 | Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, добавление, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, также как и лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий:
18 |
19 | Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения.
20 |
21 | ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RxJS: введение
2 |
3 | ## Видео
4 |
5 | [MoscowJS 42 – RxJS по косточкам](https://www.youtube.com/watch?v=3rEDHnqn-Cw)
6 |
7 | ## **Важно: пожалуйста, оцените выступлениe**
8 |
9 | Анкета на 5 минут: [https://goo.gl/forms/zxr41nHXqcHxaict2](https://goo.gl/forms/zxr41nHXqcHxaict2)
10 |
11 | ## Cсылки
12 |
13 | ### Observable
14 | - Введение в документацию [http://reactivex.io/rxjs/manual/overview.html](http://reactivex.io/rxjs/manual/overview.html)
15 | - Справочник операторов [https://www.learnrxjs.io/](https://www.learnrxjs.io/)
16 | - Понимание паттернов, из которых состоит Observable [http://anasfirdousi.com/understanding-observable-patterns-behind-observables-rxjs-rx.html](http://anasfirdousi.com/understanding-observable-patterns-behind-observables-rxjs-rx.html)
17 |
18 | ### Subjects и multicast
19 | - Типы subject-ов [https://medium.com/@poudanen/%D0%BF%D0%BE%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5-rxjava-subject-publish-replay-behavior-%D0%B8-async-subject-35ad50cd1064](https://medium.com/@poudanen/%D0%BF%D0%BE%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5-rxjava-subject-publish-replay-behavior-%D0%B8-async-subject-35ad50cd1064)
20 | - Hot vs Cold Observables [https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339](https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339)
21 | - RxJS: Understanding the publish and share Operators [https://blog.angularindepth.com/rxjs-understanding-the-publish-and-share-operators-16ea2f446635](https://blog.angularindepth.com/rxjs-understanding-the-publish-and-share-operators-16ea2f446635)
22 | - Документация multicast оператора [http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-multicast](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-multicast)
23 |
24 | ### Egghead
25 |
26 | - Use Higher Order Observables in RxJS Effectively [https://egghead.io/courses/use-higher-order-observables-in-rxjs-effectively](https://egghead.io/courses/use-higher-order-observables-in-rxjs-effectively)
27 | - Save time avoiding common mistakes using RxJS [https://egghead.io/courses/save-time-avoiding-common-mistakes-using-rxjs](https://egghead.io/courses/save-time-avoiding-common-mistakes-using-rxjs)
28 | - RxJS Subjects and Multicasting Operators [https://egghead.io/courses/rxjs-subjects-and-multicasting-operators](https://egghead.io/courses/rxjs-subjects-and-multicasting-operators)
29 |
30 | ### Не попали в категории
31 | - Rx Visualizer – песочница по визуализации Observable [https://rxviz.com/](https://rxviz.com/)
32 | - RxJS: Don’t Unsubscribe [https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87](https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87)
33 |
--------------------------------------------------------------------------------
/custom/main.js:
--------------------------------------------------------------------------------
1 | import {of} from 'rxjs';
2 | import {delay, switchMap, takeUntil} from 'rxjs/operators';
3 | import {startDemo} from './startDemo';
4 |
5 | startDemo();
6 |
7 | let START = new Date();
8 | let isRunning = false;
9 | const STEP = 50;
10 | const TICK_MS = 500;
11 | const MAX_TIME_MS = 6000;
12 | const COLOR_SCHEME = [
13 | '#FB000D',
14 | '#009D91',
15 | '#8AB32D'
16 | ];
17 |
18 | const timelineTemplate = document.getElementById('timeline-template');
19 |
20 | function createElementWithClass(className) {
21 | const element = document.createElement('div');
22 |
23 | element.classList.add(className);
24 |
25 | return element;
26 | }
27 |
28 | function createTimelineElement() {
29 | const element = timelineTemplate.content.cloneNode(true);
30 | return element.querySelector('.timeline');
31 | }
32 |
33 | function createEntryElement() {
34 | return createElementWithClass('entry');
35 | }
36 |
37 | function createCompleteElement() {
38 | return createElementWithClass('entry-complete');
39 | }
40 |
41 | function getTimeDiff() {
42 | return (new Date() - START) / TICK_MS;
43 | }
44 |
45 | function renderNext(value, timelineElement) {
46 | const newElement = createEntryElement();
47 |
48 | newElement.textContent = value;
49 |
50 | const offset = getTimeDiff() * STEP;
51 |
52 | newElement.style.left = offset + 'px';
53 |
54 | const last = timelineElement.lastElementChild;
55 |
56 | if (last) {
57 | const lastOffset = parseInt(last.style.left, 10);
58 |
59 | if (offset - lastOffset < 5) {
60 | newElement.classList.add('shift');
61 | }
62 | }
63 |
64 | timelineElement.appendChild(newElement);
65 | }
66 |
67 | function renderComplete(timelineElement) {
68 | timelineElement.style.width = getTimeDiff() * STEP + 'px';
69 | timelineElement.style.transitionDuration = '0s';
70 | }
71 |
72 | function renderCompleteElement(timelineElement) {
73 | const newElement = createCompleteElement();
74 |
75 | let offset = getTimeDiff() * STEP;
76 |
77 | const last = timelineElement.lastElementChild;
78 |
79 | if (last) {
80 | const lastOffset = parseInt(last.style.left, 10);
81 |
82 | if (offset - lastOffset < last.offsetWidth) {
83 | // offset += last.offsetWidth;
84 | newElement.classList.add('close-to-entry')
85 | }
86 | }
87 |
88 | newElement.style.left = offset + 'px';
89 |
90 | timelineElement.appendChild(newElement);
91 | }
92 |
93 | function setTimeline(parentElement) {
94 | parentElement.appendChild(createTimelineElement());
95 |
96 | const timelineElement = parentElement.lastElementChild;
97 | const index = (parentElement.childNodes.length - 1) % COLOR_SCHEME.length;
98 |
99 | // timelineElement.style.transitionDuration = MAX_TIME_MS + 'ms';
100 | timelineElement.style.color = COLOR_SCHEME[index];
101 |
102 | setTimeout(() => {
103 | timelineElement.style.width = '0px';
104 | }, 0);
105 |
106 | setTimeout(() => {
107 | timelineElement.style.width = (((MAX_TIME_MS) / TICK_MS) * STEP) + 'px';
108 | }, 100);
109 |
110 |
111 | parentElement.appendChild(timelineElement);
112 |
113 | return timelineElement;
114 | }
115 |
116 | function startStream(parentElement, input$, delayTime = 0) {
117 | const timelineElement = setTimeline(parentElement);
118 |
119 | const subscription = of(null)
120 | .pipe(
121 | delay(delayTime),
122 | switchMap(() => input$),
123 | takeUntil(
124 | of(null)
125 | .pipe(
126 | delay(MAX_TIME_MS)
127 | )
128 | )
129 | )
130 | .subscribe({
131 | next: (value) => {
132 | renderNext(value, timelineElement);
133 | },
134 | complete: () => {
135 | renderCompleteElement(timelineElement);
136 | }
137 | });
138 |
139 | const unsubscribe = subscription.unsubscribe;
140 |
141 | subscription.unsubscribe = function (...args) {
142 | renderComplete(timelineElement);
143 |
144 | unsubscribe.apply(this, args);
145 | };
146 |
147 | return subscription;
148 | }
149 |
150 | function setSubjectTick1(subject) {
151 | subject.next(0);
152 | setTimeout(() => subject.next(1), 1000);
153 | setTimeout(() => subject.next(2), 2000);
154 | setTimeout(() => subject.next(3), 3000);
155 | setTimeout(() => subject.next(4), 4000);
156 | setTimeout(() => subject.complete(), 5000);
157 | }
158 |
159 | function run(stepFn, btnElement) {
160 | if (isRunning) {
161 | return;
162 | }
163 |
164 | START = new Date();
165 | isRunning = true;
166 | btnElement.disabled = true;
167 | stepFn();
168 |
169 | setTimeout(() => {
170 | isRunning = false;
171 | btnElement.disabled = false;
172 | }, MAX_TIME_MS);
173 | }
174 |
175 | function logToOutput(outputElement, value) {
176 | const logElement = document.createElement('div');
177 |
178 | logElement.textContent = value || '\xa0';
179 |
180 | outputElement.insertBefore(logElement, outputElement.firstChild);
181 | }
182 |
183 | function logAllToOutput(outputElement, items) {
184 | outputElement.innerHTML = '';
185 |
186 | items.forEach((item) => {
187 | logToOutput(outputElement, item);
188 | });
189 | }
190 |
191 | function fetchData(url) {
192 | if (!url) {
193 | return of([]);
194 | }
195 |
196 | const times = 1 + Math.floor(Math.random() * 7);
197 | const result = new Array(times)
198 | .fill(null)
199 | .map(() => getRandomWord(url));
200 |
201 | return of(result)
202 | .pipe(
203 | delay(300 + Math.floor(Math.random() * 1000))
204 | );
205 | }
206 |
207 | function getRandomWord(source) {
208 | return source + '_' + getRandomChar() + getRandomChar() + getRandomChar();
209 | }
210 |
211 | function getRandomChar() {
212 | const num = 97 + Math.floor(26 * Math.random());
213 |
214 | return String.fromCharCode(num);
215 | }
216 |
217 | window.run = run;
218 | window.startStream = startStream;
219 | window.logToOutput = logToOutput;
220 | window.logAllToOutput = logAllToOutput;
221 | window.fetchData = fetchData;
222 | window.TICK_MS = TICK_MS;
223 | window.setSubjectTick1 = setSubjectTick1;
224 |
225 |
--------------------------------------------------------------------------------
/custom/scripts.js:
--------------------------------------------------------------------------------
1 | let START = new Date();
2 | let isRunning = false;
3 | const STEP = 50;
4 | const TICK_MS = 500;
5 | const MAX_TIME_MS = 6000;
6 | const COLOR_SCHEME = [
7 | '#FB000D',
8 | '#009D91',
9 | '#8AB32D'
10 | ];
11 |
12 | const timelineTemplate = document.getElementById('timeline-template');
13 |
14 | function createElementWithClass(className) {
15 | const element = document.createElement('div');
16 |
17 | element.classList.add(className);
18 |
19 | return element;
20 | }
21 |
22 | function createTimelineElement() {
23 | const element = timelineTemplate.content.cloneNode(true);
24 | return element.querySelector('.timeline');
25 | }
26 |
27 | function createEntryElement() {
28 | return createElementWithClass('entry');
29 | }
30 |
31 | function createCompleteElement() {
32 | return createElementWithClass('entry-complete');
33 | }
34 |
35 | function getTimeDiff() {
36 | return (new Date() - START) / TICK_MS;
37 | }
38 |
39 | function renderNext(value, timelineElement) {
40 | const newElement = createEntryElement();
41 |
42 | newElement.textContent = value;
43 |
44 | const offset = getTimeDiff() * STEP;
45 |
46 | newElement.style.left = offset + 'px';
47 |
48 | const last = timelineElement.lastElementChild;
49 |
50 | if (last) {
51 | const lastOffset = parseInt(last.style.left, 10);
52 |
53 | if (offset - lastOffset < 5) {
54 | newElement.classList.add('shift');
55 | }
56 | }
57 |
58 | timelineElement.appendChild(newElement);
59 | }
60 |
61 | function renderComplete(timelineElement) {
62 | timelineElement.style.width = getTimeDiff() * STEP + 'px';
63 | timelineElement.style.transitionDuration = '0s';
64 | }
65 |
66 | function renderCompleteElement(timelineElement) {
67 | const newElement = createCompleteElement();
68 |
69 | let offset = getTimeDiff() * STEP;
70 |
71 | const last = timelineElement.lastElementChild;
72 |
73 | if (last) {
74 | const lastOffset = parseInt(last.style.left, 10);
75 |
76 | if (offset - lastOffset < last.offsetWidth) {
77 | // offset += last.offsetWidth;
78 | newElement.classList.add('close-to-entry')
79 | }
80 | }
81 |
82 | newElement.style.left = offset + 'px';
83 |
84 | timelineElement.appendChild(newElement);
85 | }
86 |
87 | function setTimeline(parentElement) {
88 | parentElement.appendChild(createTimelineElement());
89 |
90 | const timelineElement = parentElement.lastElementChild;
91 | const index = (parentElement.childNodes.length - 1) % COLOR_SCHEME.length;
92 |
93 | // timelineElement.style.transitionDuration = MAX_TIME_MS + 'ms';
94 | timelineElement.style.color = COLOR_SCHEME[index];
95 |
96 | setTimeout(() => {
97 | timelineElement.style.width = '0px';
98 | }, 0);
99 |
100 | setTimeout(() => {
101 | timelineElement.style.width = (((MAX_TIME_MS) / TICK_MS) * STEP) + 'px';
102 | }, 100);
103 |
104 |
105 | parentElement.appendChild(timelineElement);
106 |
107 | return timelineElement;
108 | }
109 |
110 | function startStream(parentElement, input$, delay = 0) {
111 | const timelineElement = setTimeline(parentElement);
112 |
113 | const subscription = Rx.Observable.of(null)
114 | .delay(delay)
115 | .switchMap(() => input$)
116 | .takeUntil(Rx.Observable.of(null).delay(MAX_TIME_MS))
117 | .subscribe({
118 | next: (value) => {
119 | renderNext(value, timelineElement);
120 | },
121 | complete: () => {
122 | renderCompleteElement(timelineElement);
123 | }
124 | });
125 |
126 | const unsubscribe = subscription.unsubscribe;
127 |
128 | subscription.unsubscribe = function (...args) {
129 | renderComplete(timelineElement);
130 |
131 | unsubscribe.apply(this, args);
132 | };
133 |
134 | return subscription;
135 | }
136 |
137 | function setSubjectTick1(subject) {
138 | subject.next(0);
139 | setTimeout(() => subject.next(1), 1000);
140 | setTimeout(() => subject.next(2), 2000);
141 | setTimeout(() => subject.next(3), 3000);
142 | setTimeout(() => subject.next(4), 4000);
143 | setTimeout(() => subject.complete(), 5000);
144 | }
145 |
146 | function run(stepFn, btnElement) {
147 | if (isRunning) {
148 | return;
149 | }
150 |
151 | START = new Date();
152 | isRunning = true;
153 | btnElement.disabled = true;
154 | stepFn();
155 |
156 | setTimeout(() => {
157 | isRunning = false;
158 | btnElement.disabled = false;
159 | }, MAX_TIME_MS);
160 | }
161 |
162 | function logToOutput(outputElement, value) {
163 | const logElement = document.createElement('div');
164 |
165 | logElement.textContent = value || '\xa0';
166 |
167 | outputElement.insertBefore(logElement, outputElement.firstChild);
168 | }
169 |
170 | function logAllToOutput(outputElement, items) {
171 | outputElement.innerHTML = '';
172 |
173 | items.forEach((item) => {
174 | logToOutput(outputElement, item);
175 | });
176 | }
177 |
178 | function fetchData(url) {
179 | if (!url) {
180 | return Rx.Observable.of([]);
181 | }
182 |
183 | const times = 1 + Math.floor(Math.random() * 7);
184 | const result = new Array(times)
185 | .fill(null)
186 | .map(() => getRandomWord(url));
187 |
188 | return Rx.Observable.of(result).delay(300 + Math.floor(Math.random() * 1000));
189 | }
190 |
191 | function getRandomWord(source) {
192 | return source + '_' + getRandomChar() + getRandomChar() + getRandomChar();
193 | }
194 |
195 | function getRandomChar() {
196 | const num = 97 + Math.floor(26 * Math.random());
197 |
198 | return String.fromCharCode(num);
199 | }
200 |
--------------------------------------------------------------------------------
/custom/startDemo.js:
--------------------------------------------------------------------------------
1 | import {fromEvent} from 'rxjs';
2 | import {map, filter} from 'rxjs/operators';
3 |
4 |
5 | export function startDemo() {
6 | fromEvent(document, 'keyup')
7 | .pipe(
8 | map(({code}) => code),
9 | filter(code => code === 'KeyA')
10 | )
11 | .subscribe(res => {
12 | const btnElt = document.querySelector('.shower.full .slide.active .btn-play');
13 |
14 | if (btnElt) {
15 | btnElt.click();
16 | }
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/custom/styles.css:
--------------------------------------------------------------------------------
1 | .rxjs-creation,
2 | .rxjs-operator,
3 | .rxjs-pipe,
4 | .rxjs-subscribe {
5 | font-weight: 700;
6 | }
7 |
8 | .rxjs-creation {
9 | color: #c62828;
10 | }
11 |
12 | .rxjs-pipe {
13 | /*color: #283593;*/
14 | color: #00695c;
15 | }
16 |
17 | .rxjs-operator {
18 | color: #4f2c85;
19 | }
20 |
21 | .rxjs-subscribe {
22 | color: #00695c;
23 | }
24 |
25 | .stachka .slide::before {
26 | display: none;
27 | background: none;
28 | }
29 |
30 | .stachka .slide .slide-header::after {
31 | border-bottom: none;
32 | height: 4px;
33 | background: linear-gradient(90deg, #4f2c85, #ec0d8f);
34 | }
35 |
36 | .stachka .slide .cover-title {
37 | background: linear-gradient(90deg, #8c55e1, #ec0d8f);
38 | padding: 30px 50px;
39 | bottom: 60px;
40 | }
41 |
42 | .stachka .slide .cover-title h2 {
43 | margin: 0;
44 | line-height: 1;
45 | font-size: 60px;
46 | }
47 |
48 | .stachka .slide .cover-title::after {
49 | background: none;
50 | }
51 |
52 | .stachka .progress {
53 | background: linear-gradient(90deg, #4f2c85, #ec0d8f);
54 | height: 8px;
55 | border: none;
56 | clip: unset;
57 | padding: 0;
58 | left: 0;
59 | }
60 |
61 | .stachka .img-boxed {
62 | box-shadow: 0 0 0 1px #aaa;
63 | }
64 |
65 | .stachka .img-centered {
66 | display: block;
67 | margin: 0 auto;
68 | }
69 |
70 | /*.stachka .slide ul,*/
71 | .stachka .slide p {
72 | font-size: 28px;
73 | line-height: 1.5;
74 | margin: 1em 0;
75 | }
76 |
77 | .stachka .slide pre code {
78 | /*font-size: 18px;*/
79 | font-size: 20px;
80 | line-height: 1.8;
81 | }
82 |
83 | .stachka .slide pre.pre-centered-big code {
84 | font-size: 26px;
85 | }
86 |
87 | .pre-centered-big {
88 | display: inline-block;
89 | margin-top: 25px;
90 | }
91 |
92 | .stachka .slide .text-big {
93 | font-size: 30px;
94 | }
95 |
96 | /*.stachka .slide li {*/
97 | /*line-height: 1.5;*/
98 | /*margin: 10px 0;*/
99 | /*}*/
100 |
101 | .stachka .shout.shout-small {
102 | font-size: 60px;
103 | }
104 |
105 | .shower {
106 | font-size: 20px;
107 | }
108 |
109 | .slide .shout {
110 | font-size: 80px;
111 | }
112 |
113 | .slide-description small {
114 | margin-top: 10px;
115 | display: block;
116 | font-size: 14px;
117 | }
118 |
119 | .slide .slide-header {
120 | margin-bottom: 30px;
121 | }
122 |
123 | .slide-description {
124 | min-height: 70px;
125 | position: relative;
126 | padding-right: 35px;
127 | }
128 |
129 | .slide-description.big {
130 | min-height: 175px;
131 | }
132 |
133 | .intro {
134 | text-align: center;
135 | }
136 |
137 | .playground {
138 | padding: 10px 0;
139 | min-height: 54px;
140 | margin-bottom: 25px;
141 | }
142 |
143 | .playground.playground-two-streams {
144 | min-height: 98px;
145 | }
146 |
147 | .playground.playground-three-streams {
148 | min-height: 144px;
149 | }
150 |
151 | .playground-clock .entry {
152 | animation: clock ease-in-out .15s;
153 | animation-iteration-count: 21;
154 | transform-origin: center center;
155 | }
156 |
157 | @keyframes clock {
158 | 0% {
159 | padding-left: 0;
160 | transform: rotate(-5deg);
161 | }
162 |
163 | 50% {
164 | padding-left: 2px;
165 | transform: rotate(5deg);
166 | }
167 | }
168 |
169 | .text-hot {
170 | color: #C0504D;
171 | }
172 |
173 | .text-cold {
174 | color: #4F81BD;
175 | }
176 |
177 | .text-muted {
178 | color: #777;
179 | }
180 |
181 | .text-center {
182 | text-align: center;
183 | }
184 |
185 | .relative-container {
186 | position: relative;
187 | }
188 |
189 | .relative-image {
190 | position: absolute;
191 | box-shadow: 0 0 0 2px #434343;
192 | }
193 |
194 | .code-special {
195 | position: absolute;
196 | top: 0;
197 | right: 0;
198 | font-size: 11px;
199 | padding: 10px;
200 | padding-left: 35px;
201 | padding-bottom: 0;
202 | border: 1px solid #787878;
203 | opacity: .7;
204 | }
205 |
206 | .btn {
207 | font-size: 20px;
208 | padding: .3em .7em;
209 | background: #ffcf0f;
210 | color: #000000;
211 | border: none;
212 | cursor: pointer;
213 | transition: background .2s, box-shadow .2s;
214 | }
215 |
216 | .btn:hover {
217 | background: #f5c400;
218 | }
219 |
220 | .btn:active {
221 | background: #dbaf00;
222 | box-shadow: inset 0 0 .1em rgba(0, 0, 0, 0.5);
223 | }
224 |
225 | .btn:focus {
226 | outline: none;
227 | }
228 |
229 | .btn[disabled] {
230 | background: #c29b00;
231 | opacity: .7;
232 | cursor: default;
233 | }
234 |
235 | .btn.hidden {
236 | display: none;
237 | }
238 |
239 | .btn-play {
240 | position: absolute;
241 | right: 0;
242 | top: 0;
243 | }
244 |
245 | .timeline {
246 | color: red;
247 | margin: 15px 0;
248 | height: 4px;
249 | background-color: currentColor;
250 | position: relative;
251 | z-index: 0;
252 | transition: width linear 6s;
253 | width: 0;
254 | }
255 |
256 | .hidden {
257 | display: none !important;
258 | }
259 |
260 | .button {
261 | margin: 20px 0;
262 | display: inline-block;
263 | padding: 8px 16px;
264 | min-width: 120px;
265 | color: #009D91;
266 | border: 4px solid;
267 | font-size: 20px;
268 | font-weight: bold;
269 | text-transform: uppercase;
270 | border-radius: 8px;
271 | }
272 |
273 | .button:active {
274 | color: #FB000D;
275 | }
276 |
277 | .button:focus {
278 | outline: none;
279 | }
280 |
281 | .button .button-text,
282 | .button:active .button-text-active {
283 | display: inline;
284 | }
285 |
286 | .button .button-text-active,
287 | .button:active .button-text {
288 | display: none;
289 | }
290 |
291 | .timeline + .timeline {
292 | margin-top: 40px;
293 | }
294 |
295 | .entry {
296 | background-color: #ffffff;
297 | position: absolute;
298 | top: -14px;
299 | margin-left: -15px;
300 | left: 0;
301 | display: inline-block;
302 | vertical-align: top;
303 | box-sizing: border-box;
304 | width: 30px;
305 | height: 30px;
306 | line-height: 30px;
307 | /*font-size: 13px;*/
308 | font-size: 16px;
309 | font-family: PT Mono, monospace, monospace;
310 | box-sizing: border-box;
311 | box-shadow: 0 0 0 2px currentColor;
312 | text-align: center;
313 | border-radius: 50%;
314 | z-index: 1;
315 | }
316 |
317 | .entry.shift {
318 | top: -6px;
319 | z-index: 2;
320 | background: rgba(255, 255, 255, 0.6);
321 | }
322 |
323 | .entry-complete {
324 | width: 0;
325 | height: 40px;
326 | box-shadow: 0 0 0 2px currentColor;
327 | position: absolute;
328 | top: -19px;
329 | z-index: 0;
330 | }
331 |
332 | .entry-complete.close-to-entry {
333 |
334 | }
335 |
336 | .entry-start {
337 | width: 0;
338 | height: 0;
339 | border: 4px solid transparent;
340 | border-left: 8px solid red;
341 | border-right: 0;
342 | position: absolute;
343 | }
344 |
345 | .slide-search {
346 | display: flex;
347 | }
348 |
349 | .search-code {
350 | padding-right: 10px;
351 | flex: 1 0 auto;
352 | font-size: 22px;
353 | }
354 |
355 | .search-playground {
356 | width: 200px;
357 | flex: 0 0 200px;
358 | border: 2px solid #4f2c85;
359 | }
360 |
361 | .search-input {
362 | display: block;
363 | width: 100%;
364 | border: none;
365 | border-bottom: 2px solid #4f2c85;
366 | font-size: inherit;
367 | line-height: 1;
368 | padding: 10px;
369 | height: 52px;
370 | }
371 |
372 | .search-input:focus {
373 | outline: none;
374 | box-shadow: inset 0 0 0 1px #ec0d8f;
375 | }
376 |
377 | .search-output {
378 | overflow-x: hidden;
379 | overflow-y: auto;
380 | padding: 10px;
381 | /*min-height: 54px;*/
382 | /*max-height: 400px;*/
383 | height: 380px;
384 | }
385 |
386 | .search-output.search-loading {
387 | background: url('../pictures/rxjs-logo.png') center center no-repeat;
388 | background-size: 140px auto;
389 | animation: 3s blink linear infinite;
390 | }
391 |
392 | @keyframes blink {
393 | 0% {
394 | opacity: .2;
395 | }
396 |
397 | 50% {
398 | opacity: .7;
399 | }
400 |
401 | 100% {
402 | opacity: .2;
403 | }
404 | }
405 |
406 | .search-output div {
407 | white-space: nowrap;
408 | text-overflow: ellipsis;
409 | overflow: hidden;
410 | }
411 |
412 | .emoji {
413 | display: inline-block;
414 | vertical-align: middle;
415 | font-size: 50px;
416 | }
417 |
418 | .nowrap {
419 | white-space: nowrap;
420 | }
421 |
422 | .excel {
423 | font-size: 32px;
424 | }
425 |
426 | .excel code {
427 | background: none;
428 | }
429 |
430 | .excel + .excel {
431 | border-top: 2px solid #434343;
432 | padding-top: 1.2em;
433 | }
434 |
435 | .push-pull-example.next {
436 | opacity: 0;
437 | transform: translateY(100px);
438 | transition: all .15s;
439 | }
440 |
441 | .push-pull-example.next.active {
442 | transform: translateY(0);
443 | opacity: 1;
444 | }
445 |
446 | .notify-clearfix {
447 | overflow: hidden;
448 | }
449 |
450 | .notify-wrap {
451 | position: absolute;
452 | padding: 0 0 20px 10px;
453 | left: 0;
454 | bottom: 0;
455 | }
456 |
457 | .notify-baloon {
458 | width: 320px;
459 | color: #fff;
460 | background: rgba(54, 56, 59, .96);
461 | box-shadow: 0 2px 3px rgba(0, 0, 0, .2);
462 | border-radius: 4px;
463 | font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
464 | line-height: 1.154;
465 | font-weight: 400;
466 | font-size: 12.5px;
467 | }
468 |
469 | .notify-head {
470 | display: block;
471 | padding: 10px 12px 0;
472 | }
473 |
474 | .notify-wrap a {
475 | color: #bfe2fe;
476 | font-weight: 700;
477 | -webkit-font-smoothing: antialiased;
478 | -moz-osx-font-smoothing: grayscale;
479 | }
480 |
481 | .notify-close {
482 | float: right;
483 | display: block;
484 | padding: 10px;
485 | margin: -10px -12px -11px 12px;
486 | opacity: .65;
487 | outline: none;
488 | }
489 |
490 | .notify-title {
491 | font-size: 12.5px;
492 | color: inherit;
493 | padding: 0;
494 | margin: 0;
495 | font-weight: 700;
496 | }
497 |
498 | .notify-body {
499 | padding: 12px;
500 | }
501 |
502 | .notify-image-wrap {
503 | float: left;
504 | overflow: hidden;
505 | width: 50px;
506 | height: 50px;
507 | max-height: 50px;
508 | margin-right: 12px
509 | }
510 |
511 | .notify-image {
512 | width: 50px;
513 | height: 50px;
514 | border: 0;
515 | color: transparent
516 | }
517 |
518 | .notify-baloon-msg {
519 | overflow: hidden;
520 | line-height: 16px;
521 | }
522 |
523 | .modal-container {
524 | width: 480px;
525 | box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
526 | font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
527 | line-height: 1.154;
528 | font-weight: 400;
529 | font-size: 12.5px;
530 | background: #f7f7f7;
531 | border-radius: 4px;
532 | position: absolute;
533 | right: 20px;
534 | bottom: 20px;
535 | z-index: 20;
536 | }
537 |
538 | .modal-title-wrap {
539 | position: relative;
540 | padding: 0;
541 | background-color: #5b88bd;
542 | color: #fff;
543 | overflow: hidden;
544 | }
545 |
546 | .modal-close {
547 | float: right;
548 | padding: 17px 25px 21px 12px;
549 | width: 12px;
550 | height: 12px;
551 | opacity: 0.75;
552 | filter: alpha(opacity=75);
553 | cursor: pointer;
554 | outline: none
555 | }
556 |
557 | .modal-title {
558 | padding-left: 25px;
559 | font-size: 14px;
560 | color: #fff;
561 | height: 48px;
562 | line-height: 48px;
563 | overflow: hidden;
564 | text-overflow: ellipsis;
565 | white-space: nowrap
566 | }
567 |
568 | .modal-body {
569 | padding: 16px 25px;
570 | }
571 |
572 | .modal-user-info {
573 | overflow: hidden;
574 | }
575 |
576 | .modal-image-wrap {
577 | width: 50px;
578 | height: 50px;
579 | overflow: hidden;
580 | border-radius: 50%;
581 | float: left;
582 | }
583 |
584 | .modal-image {
585 | width: 50px;
586 | height: 50px;
587 | }
588 |
589 | .modal-user-text {
590 | padding-left: 13px;
591 | overflow: hidden;
592 | }
593 |
594 | .slide .modal-link {
595 | color: #42648b;
596 | text-decoration: underline;
597 | background: none;
598 | display: inline-block;
599 | line-height: 1.27em;
600 | padding: 6px 0 5px;
601 | font-weight: 700;
602 | -webkit-font-smoothing: antialiased;
603 | -moz-osx-font-smoothing: grayscale
604 | }
605 |
606 | .modal-user-status {
607 | display: block;
608 | color: #939393;
609 | margin: 0;
610 | padding: 0;
611 | }
612 |
613 | .modal-msg {
614 | background: #fff;
615 | color: #000;
616 | border: 1px solid #c0cad5;
617 | height: 80px;
618 | vertical-align: top;
619 | margin: 15px 0;
620 | overflow: hidden;
621 | outline: 0;
622 | cursor: text
623 | }
624 |
625 | .modal-controls {
626 | overflow: hidden;
627 | }
628 |
629 | .modal-btn {
630 | float: right;
631 | padding: 7px 16px 8px;
632 | margin: 0;
633 | font-size: 13px;
634 | display: inline-block;
635 | cursor: pointer;
636 | white-space: nowrap;
637 | outline: none;
638 | font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
639 | vertical-align: top;
640 | line-height: 15px;
641 | text-align: center;
642 | text-decoration: none;
643 | background: none;
644 | background-color: #5181b8;
645 | color: #fff;
646 | border: 0;
647 | border-radius: 4px;
648 | box-sizing: border-box
649 | }
650 |
651 | .stachka.shower.full .slide .next.highlight {
652 | visibility: visible;
653 | }
654 |
655 | .stachka.shower.full .slide .next:not(.active) .next.highlight {
656 | visibility: hidden;
657 | }
658 |
659 | .stachka.shower.full .slide .next.highlight:not(.active) {
660 | background: none;
661 | }
662 |
663 | .stachka.shower.full .slide .next.invisible-next {
664 | display: none;
665 | }
666 |
667 | .stachka.shower.full .slide .next.invisible-next.active {
668 | display: block;
669 | }
670 |
671 | .invisible-next.active + .after-invisible-next {
672 | display: none;
673 | }
674 |
675 | .margin-0,
676 | .slide .margin-0 {
677 | margin: 0;
678 | }
679 |
--------------------------------------------------------------------------------
/docs/features.md:
--------------------------------------------------------------------------------
1 | # Features
2 |
3 | All theme’s features are demonstrated in the [index.html](index.html) file. Use it as a reference while building your presentation. More detailed features overview follows below.
4 |
5 | - [Anatomy](#anatomy)
6 | - [Common](#common)
7 | - [Language](#language)
8 | - [Canvas](#canvas)
9 | - [Title](#title)
10 | - [Badge](#badge)
11 | - [Progress](#progress)
12 | - [Slide](#slide)
13 | - [Number](#number)
14 | - [Types](#types)
15 | - [White](#white)
16 | - [Black](#black)
17 | - [Grid](#grid)
18 | - [Content](#content)
19 | - [Header](#header)
20 | - [Paragraphs](#paragraphs)
21 | - [Inline](#inline)
22 | - [Quotes](#quotes)
23 | - [Lists](#lists)
24 | - [Columns](#columns)
25 | - [Tables](#tables)
26 | - [Code](#code)
27 | - [Elements](#elements)
28 | - [Cover](#cover)
29 | - [Shout](#shout)
30 | - [Place](#place)
31 | - [Notes](#notes)
32 |
33 | ## Anatomy
34 |
35 | Theme package consists of the following folders and files:
36 |
37 | 2. `fonts` folder with fonts in WOFF format.
38 | 3. `images` folder with decoration images.
39 | 4. `pictures` folder with sample pictures.
40 | 5. `styles` folder with built styles in 16×10 and 4×3 ratios.
41 | 6. `index.html` file with demonstration of all features.
42 |
43 | In addition to files theme’s repository contains source files:
44 |
45 | 1. `source` folder with font source files in TTF and design in [Sketch](http://bohemiancoding.com/sketch/).
46 | 2. `styles` folder also contains source styles in SCSS format.
47 |
48 | ## Common
49 |
50 | ### Language
51 |
52 | The main presentation language is set on the root element of the document, please note it and set the right one:
53 |
54 |
55 |
56 |
57 | Appropriate typography traditions are used based on this value. `lang` attribute could also be set on separate slides or elements.
58 |
59 | ### Canvas
60 |
61 | The root presentation element has the main `shower` class and additional mode class: `list` for the list and `full` for the full screen. `list` mode is usually set by default, but if there’s no one, it’ll be set to `list` anyway and slides will be opened in the list mode. If `full` is set instead of `list` then slides will be opened in the full screen mode.
62 |
63 | List:
64 |
65 |
66 |
67 | Full:
68 |
69 |
70 |
71 | Theme’s architecture is based on agreement that all presentation elements are nested in `shower` element and mode classes are hiding or showing needed elements depending on current mode.
72 |
73 | ### Caption
74 |
75 | Presentation title is marked with the `caption` element, which has following elements provided: `` for the header, ` ` for the description and also links.
76 |
77 |
78 | Presentation Title
79 | Yours Truly , Famous Inc.
80 |
81 |
82 | Caption is visible only in the list mode. Don’t forget to also specify presentation title in document’s `
` element.
83 |
84 | ### Badge
85 |
86 | Badge with “Fork me on GitHub” link (or any other call to action) is marked with `badge` element.
87 |
88 |
91 |
92 | Badge is visible only in the list mode.
93 |
94 | ### Progress
95 |
96 | Progress bar shows how much is left until presentation end and marked with `progress` element visible only in full screen mode:
97 |
98 |
99 |
100 | To remove it from presentation just remove this element from document. There’s no way to hide it for specific slides.
101 |
102 | ## Slide
103 |
104 | Slides are marked with `slide` class. Please don’t nest slides and don’t forget closing tags, things could go wrong.
105 |
106 |
109 |
112 |
113 | There are two slide ratios supported: 16×10 and 4×3. To enable needed one include appropriate file `screen-4x3.css` or `screen-16x10.css`. Wide screen 16×10 format is included by default.
114 |
115 | Slide width is 1024 px for the both ratios, for 16×10 height is 640 px, for 4×3 it’s 768. Bare in mind these sizes while preparing presentation pictures. In list mode slides are scaled down 2 or 4 times and in full screen mode they are scaled dynamically based on window size.
116 |
117 | ### Number
118 |
119 | Slide numbers help audience to remember slides for questions and open needed slide by changing number in address field. Numbers are generated automatically using CSS counters and could be turned off for specific slides.
120 |
121 | You can hide number manually:
122 |
123 |
130 |
131 | Slide type `clear` could also hide slide number.
132 |
133 | ### Types
134 |
135 | Types are changing slide’s look. You can set type by adding class to the main `slide`. There are few built-in types available in the theme, you could also describe custom types for each presention or add it to your theme.
136 |
137 | #### White
138 |
139 | White type sets white background.
140 |
141 |
142 |
143 | #### Black
144 |
145 | Black type sets black background.
146 |
147 |
148 |
149 | Please note that black slide type doesn’t change text color.
150 |
151 | #### Clear
152 |
153 | Clear type turns off slide number. Use it when you need a pure slide. May be mixed with `white` or `black` type.
154 |
155 |
156 |
157 | #### Grid
158 |
159 | Grid set a background with two guide types: main magenta guides and additional cyan guides, setting margins, rows and columns.
160 |
161 |
164 |
165 | All theme elements are aligned by this grid and it’s recommended to follow it while changing or extending a theme.
166 |
167 | ### Content
168 |
169 | Simple content: headers, paragraphs, lists.
170 |
171 | #### Header
172 |
173 | Slide header is marked with `` element:
174 |
175 |
178 |
179 | We haven’t introduced next heading levels to not provoke slides complexity.
180 |
181 | #### Paragraphs
182 |
183 | Paragraphs are marked with ` ` element. You could also make a note, less important part of a slide, by adding a `note` class to a paragraph:
184 |
185 |
186 | Text
187 | Note
188 |
189 |
190 | #### Inline
191 |
192 | There are following inline elements styled in the theme:
193 |
194 | - `` is underlined;
195 | - `` and `` are bold;
196 | - `` and `` are italic;
197 | - ``, ``, and `` are monospaced;
198 | - `` and `` make superscript and subscript indexes;
199 | - `` highlights text with background color.
200 |
201 | #### Quotes
202 |
203 | Quotes are marked with `` element which contains one or more paragraphs:
204 |
205 |
206 | Flannel bicycle rights locavore selfies.
207 |
208 |
209 | To add quote’s author wrap a quote to a `` element and put a caption in the `` right after:
210 |
211 |
212 |
213 | Post-ironic fashion axe flexitarian
214 |
215 | Yours Truly
216 |
217 |
218 | #### Lists
219 |
220 | For creating list you must use `ul` (`ol` for numerical list).
221 |
222 |
223 | Literally viral vegan
224 | Wes Anderson chillwave Marfa
225 |
226 | Retro meh brunch aesthetic
227 | Messenger bag retro cred
228 |
229 |
230 |
231 |
232 | You can also create list with inner navigation by adding `next` class to each elements following after element from each you want start navigation:
233 |
234 | Inner navigation
235 |
236 | I'll be seen right away.
237 | Just navigate to next slide and you'll see others.
238 | Hey! It's all okay ?
239 | ...
240 |
241 |
242 | And even so:
243 |
244 | Benefits
245 |
246 | The most important advantage
247 | Less important advantage
248 |
249 | Disadvantages
250 |
251 | There's nothing here
252 | ...
253 |
254 |
255 | #### Columns
256 |
257 | If you want to form text in two or three columns use `double` or `triple` class
258 |
259 |
260 | Echo Park 8-bit sustainable umami deep v Kickstarter.
261 |
262 |
263 | Also work with lists:
264 |
265 |
266 | Occupy locavore blog
267 | Mustache you haven’t heard of
268 | Something else
269 |
270 |
271 | #### Tables
272 |
273 | Create table by using usual `table`, `tr`, `th`
274 |
275 |
276 |
277 | Gentrify
278 | Twee
279 |
280 |
281 | Messenger
282 | Mixtape
283 |
284 |
285 |
286 | Class `striped` stylizes your table: even rows will turn gray background
287 |
288 |
289 |
290 | #### Code
291 |
292 | `Code` tag define your program code
293 |
294 | function action() {
295 | // TODO
296 | return true;
297 | }
298 |
299 | If you want to add lines numbers use next construction:
300 |
301 |
302 | function action() {
303 | // TODO
304 | return true;
305 | }
306 |
307 |
308 | When neccessary emphasize that code is commented, you need to use span element with `comment` class;
309 | If you want to color part of code, wrap this part with `mark` to add yellow background and `mark` with `important` class to add red background;
310 |
311 | function action() {
312 |
315 |
316 | ### Elements
317 |
318 | …
319 |
320 | #### Cover
321 |
322 | `Cover` class on img attribute indicates that picture will be background for slide
323 |
324 |
325 |
326 |
327 |
328 | To stretch the picture in width or height, you need to set a `width` or `height` class respectively;
329 |
330 | Use both classes `width height`, if you want to stretch the picture in width and height
331 |
332 |
333 |
334 |
335 |
336 | Shortcut for `width`, `height`:
337 |
338 |
339 |
340 |
341 | To insert an image description, links to the author's site or other information use `figure` tag with `figcaption`
342 |
343 |
344 |
345 |
346 | © Yours Truly
347 |
348 |
349 |
350 | #### Shout
351 |
352 | There are slides, which need to be described in only a few words. Usually they display a call for action, define common themes, link to project or something else. To stylize this text, use the `shout` class.
353 |
354 |
357 |
358 | Add `grow` class to animate text from small to big size
359 |
360 |
361 | Growing Shout
362 |
363 |
364 | Or, on the contrary, for animate text size from big to small add `shrink` class.
365 |
366 |
367 | Shrinking Shout
368 |
369 |
370 | #### Place
371 |
372 | Use `place` class on img attribute give same effect as `cover` class - set background image
373 |
374 |
375 |
376 |
377 |
378 | If you want collocate picture at a certain side, you need to use `top` / `right` / `bottom` / `left` class as shown below
379 |
380 |
381 |
382 |
383 |
384 |
385 | You can also combine classes for location in corners:
386 |
387 |
388 |
389 |
390 |
391 |
392 | #### Notes
393 |
394 | When neccessary to add some notes for slide, you may use `footer` class, that hide your notes at all time and show them when you hover to slide:
395 |
396 |
397 | Retro meh brunch aesthetic.
398 |
401 |
402 |
403 |
--------------------------------------------------------------------------------
/docs/images/logitech.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/docs/images/logitech.png
--------------------------------------------------------------------------------
/docs/images/material-canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/docs/images/material-canvas.png
--------------------------------------------------------------------------------
/docs/images/material-printing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/docs/images/material-printing.png
--------------------------------------------------------------------------------
/docs/images/ribbon-canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/docs/images/ribbon-canvas.png
--------------------------------------------------------------------------------
/docs/images/ribbon-printing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/docs/images/ribbon-printing.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/docs/logitech.md:
--------------------------------------------------------------------------------
1 | # Logitech Spotlight
2 |
3 | There’s a brand new [Logitech Spotlight](http://www.logitech.com/en-us/product/spotlight-presentation-remote) presentation remote, which is already fully compatible with Shower: you can switch between slides, hover and click your presentation. But you can make the most of it by enabling two additional shortcuts in Logitech Presentation application, available for macOS and Windows.
4 |
5 | 1. [Download](http://support.logitech.com/en_us/software/logi-presentation) and install an app.
6 | 2. Go to the main menu and locate additional shortcuts options.
7 | 3. Change _Hold Next Button_ to `Cmd Enter` for macOS or `Shift F5` for Windows (just press it).
8 | 4. Change _Hold Back Button_ to `Esc` (just press it).
9 |
10 |
11 |
12 | You might have to enable accessibility access on macOS in order to use this features. App will ask you once it’s needed.
13 |
14 | Now when all is set you can:
15 |
16 | - Start your presentation by holding next button: from the first slide (if none selected) or from the current slide (if there’s one).
17 | - Exit full screen mode by holding back button. It might be useful when you need to find a certain slide and navigate the list.
18 |
--------------------------------------------------------------------------------
/docs/printing.md:
--------------------------------------------------------------------------------
1 | # Printing
2 |
3 | You can export your presentation to PDF by sending it to print in list mode in Chrome and Opera desktop browsers.
4 |
5 | 1. Press `Cmd P` or `Ctrl P`
6 | 2. Select PDF in targets list
7 | 3. Save resulted file
8 |
9 | 
10 |
11 | ## Other options
12 |
13 | You could also try [Prince](http://princexml.com), [wkhtmltopdf](http://code.google.com/p/wkhtmltopdf) and such console utilities for printing, although not complete modern CSS features support and complicated configuration make them less convenient.
14 |
--------------------------------------------------------------------------------
/docs/shortcuts.md:
--------------------------------------------------------------------------------
1 | # Keyboard Shortcuts
2 |
3 | You can navigate between slides, start and stop presentation using keyboard shortcuts.
4 |
5 | - [Start and Stop](#start-and-stop)
6 | - [Forward](#forward)
7 | - [Backward](#backward)
8 | - [First and Last](#first-and-last)
9 |
10 | ## Start and Stop
11 |
12 | - Start from the current slide: `Cmd Enter`, `Shift F5`, `Cmd Option P`
13 | - Start from the first slide `Cmd Shift Enter`
14 | - Stop: `Esc` key
15 |
16 | ## Forward
17 |
18 | - `Right` and `Down` arrows
19 | - `Page Down` key or `Fn Down` on Mac
20 | - `N` for “next”, `J` or `L` keys
21 | - `Enter` key
22 | - `Space` key during presentation
23 |
24 | ## Backward
25 |
26 | - `Left` and `Up` arrows
27 | - `Page Up` key or `Fn Up` on Mac
28 | - `P` for “previous”, `K` or `H` keys, like in vim
29 | - `Shift Enter` key
30 | - `Shift Space` key during presentation
31 |
32 | ## First and Last
33 |
34 | - First slide: `Home` key or `Fn Left` on Mac
35 | - Last slide: `End` key or `Fn Right` on Mac
36 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const del = require('del');
2 | const fs = require('fs');
3 | const gulp = require('gulp');
4 | const merge = require('merge-stream');
5 | const rename = require('gulp-rename');
6 | const replace = require('gulp-replace');
7 | const rsync = require('gulp-rsync');
8 | const sequence = require('run-sequence');
9 | const zip = require('gulp-zip');
10 | const pages = require('gulp-gh-pages');
11 | const browserSync = require('browser-sync').create();
12 | const rollup = require('gulp-better-rollup');
13 | const resolve = require('rollup-plugin-node-resolve');
14 |
15 | gulp.task('scripts', () => {
16 | gulp.src([
17 | './custom/main.js'
18 | ])
19 | .pipe(rollup({
20 | plugins: [
21 | resolve({
22 | customResolveOptions: {
23 | moduleDirectory: 'node_modules'
24 | }
25 | }),
26 | ],
27 | external: ['rxjs']
28 | },
29 | 'iife'
30 | ))
31 | .pipe(rename('custom-scripts.js'))
32 | .pipe(gulp.dest('./'));
33 | });
34 |
35 | gulp.task('prepare', () => {
36 |
37 | const shower = gulp.src([
38 | '**',
39 | '!docs{,/**}',
40 | '!node_modules{,/**}',
41 | '!prepared{,/**}',
42 | '!utils{,/**}',
43 | '!CONTRIBUTING.md',
44 | '!LICENSE.md',
45 | '!README.md',
46 | '!gulpfile.js',
47 | '!package.json',
48 | '!package-lock.json'
49 | ])
50 | .pipe(replace(
51 | /()/g,
52 | '$1shower/themes/tinkoff$3/$4', {skipBinary: true}
53 | ))
54 | .pipe(replace(
55 | /( )/g,
56 | '$1shower/themes/$3/$4', {skipBinary: true}
57 | ))
58 | .pipe(replace(
59 | /(
549 |
550 |
551 |
552 |
553 |
554 |
555 | Observable
556 | – наблюдаемый объект, доставляет изменения в подписку.
557 |
558 |
559 |
560 |
561 | const stream$ = Observable.create ((observer) => {...});
562 |
563 | const subscribtion = stream$.subscribe (...);
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 | Observer
574 | – объект-наблюдатель,
575 |
576 | опциональные обработчики next/error/complete
577 |
578 |
579 |
580 |
581 | const subscribtion = stream$.subscribe (
582 | {
583 | next: (value) => renderNext(value),
584 | error: (error) => renderError(error),
585 | complete: () => renderComplete()
586 | }
587 | );
588 |
589 | stream$.subscribe ((value) => renderNext(value));
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 | Функция создания
601 | определяет, как наблюдатель
602 | будет получать значения.
603 |
604 |
605 |
606 |
607 | const stream$ = Observable.create ((observer ) => {
608 | observer .next(1 );
609 | setTimeout(() => { observer .next(2 );}, 1000);
610 | setTimeout(() => { observer .next(3 );}, 2000);
611 | setTimeout(() => { observer .complete();}, 3000);
612 | });
613 |
614 | stream$.subscribe (renderNext);
615 |
616 |
617 |
618 |
619 |
1
620 |
2
621 |
3
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 | const stream$ = Observable.create ((observer ) => {
632 | observer .next(1 );
633 | setTimeout(() => { observer .next(2 );}, 1000);
634 | setTimeout(() => { observer .next(3 );}, 2000);
635 | setTimeout(() => { observer .complete();}, 3000);
636 | });
637 |
638 | const promise = new Promise((resolve , reject ) => {
639 | resolve (1 );
640 | });
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 | Subscribtion
650 | – подписка. Старт получения значений.
651 |
652 |
653 |
654 |
655 | const stream$ = Observable.create ((observer) => {...});
656 |
657 | const subscribtion = stream$.subscribe (...);
658 | subscribtion.unsubscribe ();
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 | unsubscribe – завершение получения значений.
668 |
669 |
670 |
671 |
672 | const stream$ = Observable.create ((observer) => {
673 | const interval = setInterval(() => observer.next(1), 1000);
674 |
675 |
676 | return function unsubscribe() {
677 | clearInterval(interval);
678 | }
679 |
680 | });
681 |
682 | const subscribtion = stream$.subscribe (...);
683 | subscribtion.unsubscribe ();
684 |
685 |
687 |
688 |
689 |
690 |
691 |
692 | Нет подписки — нет потока
693 |
694 |
695 |
696 |
697 | const stream$ = Observable.create ((observer) => {...});
698 |
699 |
700 | const subscribtionA = stream$.subscribe (observerA);
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 | Каждый observer создаст свой поток значений
711 |
712 | Подписки не зависят друг от друга
713 |
714 |
715 |
716 |
717 | const stream$ = Observable.create ((observer) => {...});
718 |
719 |
720 | const subscribtionA = stream$.subscribe (observerA);
721 |
722 | const subscribtionB = stream$.subscribe (observerBC );
723 | const subscribtionC = stream$.subscribe (observerBC );
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
1
734 |
2
735 |
3
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 | const stream$ = Observable.create ((observer) => {
744 | observer.next(1);
745 | setTimeout(() => { observer.next(2); }, 1000);
746 | setTimeout(() => { observer.next(3); }, 2000);
747 | setTimeout(() => { observer.complete(); }, 3000);
748 | });
749 |
750 |
751 | const subscribtion = stream$.subscribe ({
752 | next: (value) => renderNext(value) ,
753 | error: (error) => renderError(error),
754 | complete: () => renderComplete()
755 | });
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 | Ок, что дальше?
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 | cоздание
774 | преобразование одного потока
775 | комбинирование потоков
776 | Observable высшего порядка
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 | import {of } from 'rxjs';
786 | of ('foo');
787 | // ---foo-|
788 |
789 |
790 | import {from } from 'rxjs';
791 | from (['foo', 'bar', 'baz']);
792 | // ---foo---bar---baz-|
793 |
794 |
795 |
796 | import {timer } from 'rxjs';
797 | timer (100, 500);
798 | // -0---1---2---3--...
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 | import {from } from 'rxjs';
810 | const promise = new Promise((resolve) => { resolve('foo')});
811 | from (promise);
812 | // ----------------foo-|
813 |
814 |
815 | import {fromEvent } from 'rxjs';
816 | const btnElement = document.getElementById('btn');
817 | fromEvent (btnElement, 'click');
818 | // --evt-evt------evt----evt--...
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
831 |
832 |
833 | import {take , filter } from 'rxjs/operators';
835 | timer (0, 500).pipe (
836 | take (12)
837 | ).subscribe (renderNext);
838 |
839 |
840 | timer (0, 500).pipe (
841 | take (12),
842 | filter (() => Math.random() < 0.3)
843 | ).subscribe (renderNext);
844 |
845 |
846 |
847 |
867 |
868 |
869 |
870 |
871 |
872 |
873 | .pipe (
874 |
875 |
877 |
878 | ).subscribe (...)
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
888 |
889 | ---2--30--22--5--60--1-----|
890 | filter(x => x > 10)
891 | ------30--22-----60--------|
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
▶
901 |
902 | timer (0, 250).pipe (
903 | take (40),
904 | filter (() => Math.random() < 0.3),
905 | debounceTime (500)
906 | ).subscribe (renderNext);
907 |
908 |
909 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
▶
937 |
938 | const streamA$ = timer (0, 1500).pipe (take (4));
940 | const streamB$ = timer (800, 1000).pipe (
942 | take (6),
943 | map ((i) => 'abcdef'[i])
944 | );
945 | merge (streamA$, streamB$)
946 | .subscribe (renderNext);
947 |
948 |
949 |
972 |
973 |
974 |
975 |
976 |
977 |
978 |
▶
979 |
980 | const streamA$ = timer (0, 1500).pipe (take (4));
982 | const streamB$ = timer (800, 1000).pipe (
984 | take (6),
985 | map ((i) => 'abcdef'[i])
986 | );
987 | combineLatest (streamA$, streamB$).pipe (
989 | map (([a, b]) => a + b)
990 | ).subscribe (renderNext);
991 |
992 |
993 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
▶
1026 |
1027 | timer (0, 1500).pipe (
1028 | take (3),
1029 | map ((i) => 'abcd'[i]),
1030 | mergeMap (letter => {
1031 | return timer (0, 450).pipe (
1033 | take (6),
1034 | map (digit => letter + digit));
1035 | })
1036 | ).subscribe (renderNext);
1037 |
1038 |
1039 |
1065 |
1066 |
1067 |
1068 |
1069 |
1073 |
1074 |
1075 | timer (0, 1500).pipe (
1076 | take (3),
1077 | map ((i) => 'abcd'[i]),
1078 | switchMap (letter => {
1079 | return timer (0, 450).pipe (
1081 | take (6),
1082 | map (digit => letter + digit));
1083 | })
1084 | ).subscribe (renderNext);
1085 |
1086 |
1087 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 | SwitchMap
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 |
1137 |
▶
1138 |
1139 | const getPromise = (value) => new Promise((resolve) => {
1140 | setTimeout(() => {
1141 | resolve(value);
1142 | }, 1500);
1143 | });
1144 |
1145 | from (getPromise('⏰'))
1146 | .subscribe(renderNext);
1147 |
1148 |
1149 |
1165 |
1166 |
1167 |
1168 |
1169 |
1173 |
1174 |
1175 | const streamA$ = Observable.create ((observer) => {
1176 | observer.next(1);
1177 | setTimeout(() => { observer.next(2);}, 2000);
1178 | setTimeout(() => { observer.next(3);}, 3000);
1179 | setTimeout(() => { observer.complete()}, 4000);
1180 | })
1181 | const streamB$ = streamA$.pipe (
1182 | switchMap ((value) => getPromise(value))
1183 | ).subscribe (renderNext);
1184 |
1185 |
1186 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 |
1225 |
SwitchMap автоматически отписывает внутренний поток — вызывает subscription.unsubscribe ()
1227 |
1228 |
Можем написать отменяемые запросы:
1229 |
1230 | Angular HttpClient
1231 | Fetch + AbortController
1232 | XMLHttpRequest -> xhr.abort()
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 | const request$ = Observable.create ((observer) => {
1242 |
1243 | const controller = new AbortController();
1244 | const signal = controller.signal;
1245 |
1246 |
1247 |
1248 | fetch(url, {signal} ).then(res => res.json())
1249 |
1250 |
1251 | .then(data => {
1252 | observer.next(data);
1253 | observer.complete();
1254 | });
1255 |
1256 |
1257 |
1258 | return function unsubscribe() {
1259 | controller.abort();
1260 | };
1261 |
1262 | });
1263 |
1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 | ДЕМО
1278 |
1279 |
1280 |
1281 |
1282 |
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1292 | fromEvent (inputElement, 'input')
1293 | .pipe (
1294 | map ((event) => event.target.value)
1295 |
1296 | )
1297 | .subscribe (logToOutput)
1298 |
1299 |
1300 |
1301 |
1302 |
1303 |
1304 |
1305 |
1306 |
1307 |
1336 |
1337 |
1338 |
1339 |
1340 |
1341 |
1342 | fromEvent (inputElement, 'input')
1343 | .pipe (
1344 | map ((event) => event.target.value),
1345 | debounceTime (500)
1346 | )
1347 | .subscribe (logToOutput)
1348 |
1349 |
1350 |
1351 |
1352 |
1353 |
1354 |
1355 |
1356 |
1387 |
1388 |
1389 |
1390 |
1391 |
1392 |
1393 | fromEvent (inputElement, 'input')
1394 | .pipe (
1395 | map ((event) => event.target.value),
1396 | debounceTime (500),
1397 | distinctUntilChanged ()
1398 | )
1399 | .subscribe (logToOutput)
1400 |
1401 |
1402 |
1403 |
1404 |
1405 |
1406 |
1407 |
1408 |
1440 |
1441 |
1442 |
1443 |
1444 |
1445 |
1446 | fromEvent (inputElement, 'input')
1447 | .pipe (
1448 | map ((event) => event.target.value),
1449 | debounceTime (500),
1450 | distinctUntilChanged (),
1451 | switchMap (query => fetchData(query))
1452 | )
1453 | .subscribe (logToOutput)
1454 |
1455 |
1456 |
1457 |
1458 |
1459 |
1460 |
1461 |
1462 |
1495 |
1496 |
1497 |
1498 |
1499 |
1500 |
1501 | fromEvent (inputElement, 'input')
1502 | .pipe (
1503 | map ((event) => event.target.value),
1504 | debounceTime (500),
1505 | distinctUntilChanged (),
1506 | tap (() => setLoading(true)) ,
1507 | switchMap (query => fetchData(query)),
1508 | tap (() => setLoading(false))
1509 | )
1510 | .subscribe (logToOutput)
1511 |
1512 |
1513 |
1514 |
1515 |
1516 |
1517 |
1518 |
1519 |
1560 |
1561 |
1562 |
1563 |
1564 |
1565 |
1566 | fromEvent (inputElement, 'input')
1567 | .pipe (
1568 | map ((event) => event.target.value),
1569 | debounceTime (500),
1570 | distinctUntilChanged (),
1571 | tap (() => setLoading(true)),
1572 | switchMap (query => fetchData(query)),
1573 | tap (() => setLoading(false))
1574 | )
1575 | .subscribe (logToOutput)
1576 |
1577 |
1578 |
1579 |
1580 |
1581 |
1582 |
1583 |
Работаем с потоками,
1584 | а не с промежуточными состояниями
1585 |
1586 | видна вся последовательность обработки
1587 | минимум сайд эффектов
1588 | самодостаточный компонент
1589 |
1590 |
1591 |
1592 |
1593 |
1594 |
1595 | SUBJECTS
1596 |
1597 |
1598 |
1599 |
1600 |
1601 |
1605 |
1606 |
1607 | const stream$ = timer (0, 500).pipe (
1608 | take (5),
1609 | map (() => random()));
1610 | stream$.subscribe (/* ... */);
1611 | stream$.subscribe (/* ... */);
1612 |
1613 | setTimeout(() => {
1614 | stream$.subscribe (/* ... */);
1615 | }, 800);
1616 |
1617 |
1618 |
1619 |
1635 |
1636 |
1637 |
1638 |
1639 |
1640 |
Объединение Observable и Observer. Передает значение всем подписчикам
1641 |
1642 |
1643 |
1644 |
1645 | const subject = new Subject ();
1646 |
1647 | subject.next(0);
1648 | setTimeout(() => subject.next(1), 1000);
1649 | setTimeout(() => subject.next(2), 2000);
1650 | setTimeout(() => subject.next(3), 3000);
1651 | setTimeout(() => subject.next(4), 4000);
1652 | setTimeout(() => subject.complete(), 5000);
1653 |
1654 |
1655 |
1656 |
1657 |
1658 |
1659 |
1660 |
1664 |
1665 |
1666 |
1667 | const subject = new Subject ();
1668 | setSubjectTick(subject);
1669 |
1670 | subject.subscribe ();
1671 | setTimeout(() => subject.subscribe (/* ... */), 1500);
1672 | setTimeout(() => subject.subscribe (/* ... */), 2500);
1673 |
1674 |
1675 |
1676 |
1689 |
1690 |
1691 |
1692 |
1693 |
1694 |
1695 |
1696 |
1697 |
1698 |
1699 |
1700 |
1701 |
1702 |
1703 |
1704 |
1705 | const subscription = streamA$.pipe (
1706 | map (...),
1707 | switchMap (...)
1708 | ).subscribe (...);
1709 |
1710 |
...
1711 |
onDestroy() {
1712 |
subscription.unsubscribe ();
1713 |
subscription2.unsubscribe ();
1714 |
subscription3.unsubscribe ();
1715 |
subscription4.unsubscribe ();
1716 |
1717 | ...
1718 | }
1719 |
1720 |
1721 |
1722 |
1723 |
1724 |
1725 |
1726 |
1727 |
1728 |
1729 | const destroy$ = new Subject ();
1730 |
1731 | streamA$.pipe (
1732 | map (...),
1733 | switchMap (...),
1734 | takeUntil (destroy$ )
1735 | ).subscribe ()
1736 | ...
1737 |
1738 |
1739 | onDestroy() {
1740 | destroy$.next ();
1741 | }
1742 |
1743 |
1744 |
1745 |
1746 |
1747 |
1748 |
1749 |
1750 |
1751 | class StateService {
1752 | private authSubject = new BehaviorSubject <boolean>(false);
1753 |
1754 |
1755 | setAuthState(state: boolean) {
1756 | this.authSubject.next (state);
1757 | }
1758 |
1759 | }
1760 |
1761 |
1762 |
1763 |
1764 |
1765 |
1766 |
1767 |
1768 | class StateService {
1769 | private authSubject = new BehaviorSubject <boolean>(false);
1770 | setAuthState(state: boolean) {...}
1771 |
1772 |
1773 | getStateChange(): Observable<boolean> {
1774 | return this.authSubject.asObservable ();
1775 | }
1776 |
1777 | }
1778 |
1779 |
1780 |
1781 |
1782 |
1783 |
1784 |
1785 |
1786 | class StateService {
1787 | private authSubject = new BehaviorSubject <boolean>(false);
1788 | setAuthState(state: boolean) {...}
1789 | getStateChange(): Observable<boolean> {...}
1790 |
1791 |
1792 | getCurrentState(): boolean {
1793 | return this.authSubject.getValue ();
1794 | }
1795 |
1796 | }
1797 |
1798 |
1799 |
1800 |
1801 |
1802 |
1803 |
1804 |
1805 | let cachingSubject;
1806 |
1807 | getResource() {
1808 |
1809 |
if (!cachingSubject) {
1810 |
1811 | cachingSubject = new ReplaySubject (1);
1812 |
1813 |
1814 |
1815 | loadData().subscribe ((result) => {
1816 | this.cachingSubject.next (result);
1817 | });
1818 |
1819 |
}
1820 |
1821 |
1822 |
1823 | return this.cachingSubject.asObservable ();
1824 |
1825 | }
1826 |
1827 |
1828 |
1829 |
1830 |
1831 |
1832 | ИТОГИ
1833 |
1834 |
1835 |
1836 |
1837 |
1838 |
1839 |
1840 | Push / pull
1841 | Observable = Observer + Iterator
1842 | Observable - паттерн для работы с потоками данных
1843 | Операторы: преобразования, комбинирования потоков
1844 | Observable высшего порядка
1845 | Как сделать отменяемые запросы
1846 | Поле поиска через цепочку операторов
1847 | Subjects – один поток на множество подписчиков
1848 |
1849 |
1850 |
1851 |
1852 |
1853 |
1854 |
1855 |
1856 |
1857 |
1858 |
1861 |
1862 |
1863 |
1864 |
1867 |
1868 |
1869 |
1870 |
1871 |
1872 |
1873 |
1874 |
1875 |
1876 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rxjs-intro-talk",
3 | "description": "RxJS intro",
4 | "version": "0.0.1",
5 | "author": {
6 | "name": "Andrey Alexeev",
7 | "url": "http://aalexeev.ru"
8 | },
9 | "dependencies": {
10 | "rxjs": "^6.2.1",
11 | "shower-core": "^2.1.0",
12 | "shower-material": "^1.1.0",
13 | "tinkoff-shower": "git+git@github.com:lekzd/tinkoff-shower.git"
14 | },
15 | "devDependencies": {
16 | "browser-sync": "^2.18.12",
17 | "del": "^3.0.0",
18 | "fs": "0.0.2",
19 | "gulp": "^3.9.1",
20 | "gulp-better-rollup": "^3.3.0",
21 | "gulp-gh-pages": "^0.5.4",
22 | "gulp-rename": "^1.2.2",
23 | "gulp-replace": "^0.6.1",
24 | "gulp-rsync": "0.0.8",
25 | "gulp-zip": "^4.0.0",
26 | "merge-stream": "^1.0.0",
27 | "path-exists-cli": "^1.0.0",
28 | "rollup-plugin-node-resolve": "^3.3.0",
29 | "run-sequence": "^2.1.0"
30 | },
31 | "scripts": {
32 | "start": "gulp",
33 | "prepare": "gulp prepare",
34 | "archive": "gulp archive",
35 | "publish": "gulp publish",
36 | "test": "npm run prepare && ls prepared && npm run archive && path-exists archive.zip"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pictures/2requests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/2requests.png
--------------------------------------------------------------------------------
/pictures/author.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/author.png
--------------------------------------------------------------------------------
/pictures/demo-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/demo-2.gif
--------------------------------------------------------------------------------
/pictures/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/favicon-16x16.png
--------------------------------------------------------------------------------
/pictures/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/favicon-32x32.png
--------------------------------------------------------------------------------
/pictures/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/favicon-96x96.png
--------------------------------------------------------------------------------
/pictures/fetch-cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/fetch-cancel.png
--------------------------------------------------------------------------------
/pictures/iterator-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/iterator-pattern.png
--------------------------------------------------------------------------------
/pictures/observer-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/observer-pattern.png
--------------------------------------------------------------------------------
/pictures/palpatin-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/palpatin-50.png
--------------------------------------------------------------------------------
/pictures/pipe-3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/pipe-3.gif
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-1-bg-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-1-bg-2.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-1-bg-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-1-bg-3.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-1.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-2.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-3.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-4.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-5.png
--------------------------------------------------------------------------------
/pictures/rx-stachka-table-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rx-stachka-table-6.png
--------------------------------------------------------------------------------
/pictures/rxJs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxJs.png
--------------------------------------------------------------------------------
/pictures/rxjs-clicks-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-clicks-1.png
--------------------------------------------------------------------------------
/pictures/rxjs-clicks-2-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-clicks-2-2.png
--------------------------------------------------------------------------------
/pictures/rxjs-clicks-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-clicks-2.png
--------------------------------------------------------------------------------
/pictures/rxjs-clicks-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-clicks-3.png
--------------------------------------------------------------------------------
/pictures/rxjs-clicks-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-clicks-4.png
--------------------------------------------------------------------------------
/pictures/rxjs-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-logo.png
--------------------------------------------------------------------------------
/pictures/rxjs-qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxjs-qr.png
--------------------------------------------------------------------------------
/pictures/rxmarbles-combine-latest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxmarbles-combine-latest.png
--------------------------------------------------------------------------------
/pictures/rxmarbles-filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxmarbles-filter.png
--------------------------------------------------------------------------------
/pictures/rxmarbles-switch-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/rxmarbles-switch-map.png
--------------------------------------------------------------------------------
/pictures/table-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/table-1.png
--------------------------------------------------------------------------------
/pictures/table-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/table-2.png
--------------------------------------------------------------------------------
/pictures/table-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aalexeev239/rxjs-intro/edc61370aabb0491f69e817e3f74c133560cd3a5/pictures/table-3.png
--------------------------------------------------------------------------------