├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── gh-pages.yml
│ └── sanity-check.yml
├── .gitignore
├── .husky
└── pre-commit
├── .nojekyll
├── .reveal-md
├── listing.html
└── styles.css
├── .yaspellerrc.js
├── README.md
├── lessons
├── final-task
│ ├── ht.md
│ └── lecture.md
├── lesson01
│ ├── code
│ │ └── fizzbuzz
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── sandbox.config.json
│ │ │ └── src
│ │ │ ├── ..js
│ │ │ ├── index.js
│ │ │ └── index.test.js
│ ├── ht.md
│ ├── images
│ │ ├── question1.png
│ │ ├── question2.png
│ │ ├── question3.png
│ │ ├── question4.png
│ │ ├── question5.png
│ │ ├── question6.png
│ │ └── question7.png
│ └── lecture.md
├── lesson02
│ ├── code
│ │ └── isPalindrom
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ ├── isPalindrom.js
│ │ │ └── isPalindrom.test.js
│ └── lecture.md
├── lesson03
│ ├── 1.lecture.md
│ ├── 2.lecture.md
│ ├── 3.lecture.md
│ ├── code
│ │ ├── bind
│ │ │ ├── .codesandbox
│ │ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ │ ├── bind.js
│ │ │ │ ├── bind.test.js
│ │ │ │ └── index.js
│ │ ├── singleton
│ │ │ ├── .codesandbox
│ │ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ │ ├── index.js
│ │ │ │ ├── makeSingleton.js
│ │ │ │ ├── makeSingleton.test.js
│ │ │ │ ├── singleton.js
│ │ │ │ ├── singleton.test.js
│ │ │ │ └── styles.css
│ │ └── smart-getter
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ ├── get.js
│ │ │ ├── get.test.js
│ │ │ └── index.js
│ ├── homework_lec1.md
│ └── homework_lec3.md
├── lesson04
│ ├── 01.md
│ ├── 02.md
│ └── code
│ │ ├── DOMExamples
│ │ ├── index.html
│ │ ├── package.json
│ │ └── src
│ │ │ └── script.js
│ │ ├── DOMExamplesPractice
│ │ ├── index.html
│ │ ├── package.json
│ │ └── src
│ │ │ └── script.js
│ │ ├── dataStoragePractice
│ │ ├── index.html
│ │ ├── package.json
│ │ └── src
│ │ │ └── script.js
│ │ ├── fetchPractice
│ │ ├── index.html
│ │ ├── package.json
│ │ └── src
│ │ │ └── script.js
│ │ └── singleThreadInJs
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── sandbox.config.json
│ │ └── src
│ │ └── script.js
├── lesson05
│ ├── code
│ │ ├── isEqualXAndO
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ │ ├── isEqualXAndO.js
│ │ │ │ └── isEqualXAndO.test.js
│ │ └── uicalc
│ │ │ ├── .gitignore
│ │ │ ├── babel.config.js
│ │ │ ├── index.html
│ │ │ ├── jest.config.js
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ └── index.js
│ ├── homework.md
│ ├── homework2.md
│ └── lecture.md
├── lesson06
│ ├── homework.md
│ ├── images
│ │ └── git_show.png
│ └── lecture.md
├── lesson07
│ ├── ht.md
│ └── lecture.md
├── lesson08
│ └── lecture.md
├── lesson09
│ ├── homework.md
│ └── lecture.md
├── lesson10
│ ├── images
│ │ └── callbackhell.png
│ └── lecture.md
├── lesson13
│ ├── ht.md
│ └── lecture.md
├── lesson19
│ ├── ht.md
│ └── lecture.md
├── lesson20
│ ├── code
│ │ └── notes-list
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ ├── addForm.js
│ │ │ ├── addForm.test.js
│ │ │ ├── index.js
│ │ │ └── styles.css
│ ├── ht.md
│ └── lecture.md
├── lesson21
│ ├── code
│ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ ├── index.html
│ │ ├── package.json
│ │ └── src
│ │ │ ├── Game.test.ts
│ │ │ ├── Game.ts
│ │ │ ├── GameField.test.ts
│ │ │ ├── GameField.ts
│ │ │ ├── GameView.test.ts
│ │ │ ├── GameView.ts
│ │ │ ├── index.ts
│ │ │ ├── styles.css
│ │ │ └── types
│ │ │ └── Cell.ts
│ ├── images
│ │ └── slicing.png
│ └── lecture.md
├── lesson22
│ ├── code
│ │ ├── compose
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── sandbox.config.json
│ │ │ └── src
│ │ │ │ ├── index.js
│ │ │ │ └── index.test.js
│ │ └── memo
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── sandbox.config.json
│ │ │ └── src
│ │ │ ├── ..js
│ │ │ ├── index.js
│ │ │ └── index.test.js
│ ├── ht.md
│ └── lecture.md
├── lesson23
│ └── lecture.md
├── lesson24
│ ├── ht.md
│ └── lecture.md
├── lesson25
│ ├── code
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── sandbox.config.json
│ │ ├── src
│ │ │ ├── ToDoListController.ts
│ │ │ ├── ToDoListModel.ts
│ │ │ ├── ToDoListView.ts
│ │ │ ├── components
│ │ │ │ ├── Form.ts
│ │ │ │ ├── List.ts
│ │ │ │ └── ListItem.ts
│ │ │ └── index.ts
│ │ ├── style
│ │ │ └── index.css
│ │ └── tsconfig.json
│ ├── images
│ │ ├── MKC.jpg
│ │ ├── MKC_components.jpg
│ │ ├── MVC-Process.png
│ │ ├── ModelViewControllerDiagram.png
│ │ └── todolist.png
│ └── lecture.md
├── lesson26
│ ├── images
│ │ └── mrbean.jpg
│ └── lecture.md
├── lesson27
│ └── lecture.md
├── lesson28
│ ├── ht.md
│ ├── ht2.md
│ └── lecture.md
├── lesson31
│ ├── code
│ │ └── code
│ │ │ └── templatePractice
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── index.ts
│ │ │ ├── template.test.ts
│ │ │ └── template.ts
│ │ │ └── tsconfig.json
│ └── lecture.md
├── lesson33
│ ├── code
│ │ └── eventEmitter
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── EventEmitter.test.ts
│ │ │ ├── EventEmitter.ts
│ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ ├── homework.md
│ ├── images
│ │ ├── EventBus.jpeg
│ │ └── ObservableUML.png
│ └── lecture.md
├── lesson34
│ ├── code
│ │ ├── combineReducers
│ │ │ ├── .codesandbox
│ │ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ │ ├── combineReducers.test.ts
│ │ │ │ ├── combineReducers.ts
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ └── reduxBasic
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── babel.config.js
│ │ │ ├── index.html
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── configureStore.test.ts
│ │ │ ├── configureStore.ts
│ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ ├── homework.md
│ ├── images
│ │ ├── EventBus.jpeg
│ │ ├── middlewares.gif
│ │ └── redux-data-flow.png
│ └── lecture.md
├── lesson35
│ ├── code
│ │ ├── client.js
│ │ ├── index.html
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── server.ts
│ │ ├── style.css
│ │ ├── tsconfig.json
│ │ └── types.d.ts
│ ├── images
│ │ ├── Archive_repo.png
│ │ ├── REST_CRUD_operations.png
│ │ ├── comparision1.png
│ │ ├── comparision2.png
│ │ ├── oauth_example1.png
│ │ ├── oauth_example2.png
│ │ ├── oauth_flow.png
│ │ ├── requestScope.png
│ │ ├── slack_RPC.png
│ │ ├── slack_conversation_API.png
│ │ ├── ta.png
│ │ ├── ta1.png
│ │ └── tripAdvisor.png
│ └── lecture.md
├── lesson36
│ └── lecture.md
├── lesson37
│ └── lecture.md
├── lesson38
│ ├── code
│ │ └── reduxDataLoading
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── actions.ts
│ │ │ ├── api.ts
│ │ │ ├── index.ts
│ │ │ ├── logger.ts
│ │ │ ├── reducer.ts
│ │ │ └── store.ts
│ │ │ └── tsconfig.json
│ ├── homework.md
│ ├── images
│ │ └── middlewares.gif
│ └── lecture.md
├── lesson39
│ ├── code
│ │ └── routing-examples
│ │ │ ├── README.md
│ │ │ ├── examples
│ │ │ ├── hash-api.js
│ │ │ ├── history-api.js
│ │ │ ├── practice.js
│ │ │ └── router.js
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ └── sandbox.config.json
│ ├── hw.md
│ └── lecture.md
├── lesson40
│ ├── code
│ │ ├── .github
│ │ │ └── workflows
│ │ │ │ └── build.yml
│ │ ├── .gitignore
│ │ ├── .husky
│ │ │ ├── .gitignore
│ │ │ └── pre-commit
│ │ ├── README.md
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── public
│ │ │ └── index.html
│ │ ├── src
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── webpack.config.js
│ ├── hw.md
│ └── lecture.md
├── lesson42
│ ├── hw.md
│ ├── images
│ │ ├── calendar.png
│ │ └── folder-structure.png
│ └── lecture.md
├── lesson43
│ ├── hw.md
│ ├── hw2.md
│ ├── images
│ │ ├── createElement.png
│ │ ├── dom-reconciliation.webp
│ │ └── vdom.png
│ └── lecture.md
├── lesson44
│ ├── code
│ │ ├── controlled-counter
│ │ │ ├── .codesandbox
│ │ │ │ └── workspace.json
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── index.html
│ │ │ ├── src
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── CounterButton.tsx
│ │ │ │ └── index.tsx
│ │ │ └── tsconfig.json
│ │ └── counter-button
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ └── index.html
│ │ │ ├── src
│ │ │ ├── CounterButton.test.tsx
│ │ │ ├── CounterButton.tsx
│ │ │ └── index.tsx
│ │ │ └── tsconfig.json
│ ├── images
│ │ └── unidirectional-data-flow.png
│ └── lecture.md
├── lesson45
│ ├── images
│ │ ├── tree-state.png
│ │ └── tree.png
│ └── lecture.md
├── lesson46
│ ├── code
│ │ └── reactredux
│ │ │ ├── .codesandbox
│ │ │ └── workspace.json
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ └── index.html
│ │ │ ├── src
│ │ │ ├── 1_pureReact
│ │ │ │ └── index.tsx
│ │ │ ├── 2_withHOC
│ │ │ │ ├── index.tsx
│ │ │ │ └── withRedux.tsx
│ │ │ ├── 3_react-redux
│ │ │ │ ├── Counter.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ ├── store
│ │ │ │ ├── actionsCounter.ts
│ │ │ │ ├── counter.ts
│ │ │ │ └── index.ts
│ │ │ └── styles.css
│ │ │ └── tsconfig.json
│ └── lecture.md
├── lesson47
│ ├── homework.md
│ └── lecture.md
├── lesson48
│ ├── homework.md
│ └── lecture.md
├── lesson49
│ ├── code
│ │ ├── assets
│ │ │ ├── read.txt
│ │ │ ├── song.mp3
│ │ │ ├── tmp.json
│ │ │ └── tmp.txt
│ │ ├── commander
│ │ │ ├── cli.ts
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ └── tsconfig.json
│ │ ├── examples
│ │ │ ├── 1-writeFileSync.ts
│ │ │ ├── 2-readFileSync.ts
│ │ │ ├── 3-readWriteFileCb.ts
│ │ │ ├── 4-fsWatch.ts
│ │ │ ├── 5-fsStat.ts
│ │ │ ├── 6-dirRead.ts
│ │ │ ├── 7-openAndReadFile.ts
│ │ │ ├── 8-fsWriteStream.ts
│ │ │ ├── 9-fsReadStream.ts
│ │ │ └── os.ts
│ │ └── inquirer
│ │ │ ├── cli.ts
│ │ │ ├── configs
│ │ │ ├── tsconfig.node.json
│ │ │ ├── tsconfig.react.json
│ │ │ └── tsconfig.recommended.json
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ └── tsconfig.json
│ ├── images
│ │ ├── arch.png
│ │ └── nodejs.png
│ └── lecture.md
├── lesson50
│ ├── README.md
│ ├── homework.md
│ ├── images
│ │ ├── bob_martin.jpg
│ │ ├── exorcism.gif
│ │ ├── jsguy.gif
│ │ ├── polnomochiya.jpg
│ │ └── ron_jeffries.jpg
│ └── lecture.md
├── lesson51
│ ├── images
│ │ ├── http.png
│ │ ├── middleware.jpg
│ │ └── osi.jpeg
│ └── lecture.md
└── lesson52
│ ├── code
│ ├── ls.js
│ ├── package.json
│ ├── practice
│ │ ├── ls.js
│ │ └── server.js
│ └── sample.js
│ ├── hw1.md
│ ├── hw2.md
│ └── lecture.md
├── package.json
├── plugins.js
├── plugins
└── tableofcontents.js
├── preparation
└── tasks.md
├── reveal-md.json
├── reveal.json
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: "@typescript-eslint/parser",
3 | extends: ["plugin:react/recommended", "plugin:markdown/recommended"],
4 | plugins: ["@typescript-eslint", "react", "markdown"],
5 | overrides: [
6 | {
7 | files: ["**/*.md"],
8 | processor: "markdown/markdown",
9 | },
10 | {
11 | files: ["**/*.md/*.js"],
12 | rules: {
13 | "comma-dangle": ["error", "only-multiline"],
14 | },
15 | },
16 | ],
17 | settings: {
18 | react: {
19 | version: "17.0.2",
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["master"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v3
34 |
35 | - name: Install Packages
36 | run: |
37 | yarn install
38 |
39 | - name: Build
40 | run: |
41 | npx reveal-md lessons/ --static dist --static-dirs=plugins
42 | rsync -a -r lessons/ dist/
43 | cp -r plugins dist/_assets/
44 | cp -r .reveal-md dist/_assets/
45 |
46 | - name: Setup Pages
47 | uses: actions/configure-pages@v3
48 | - name: Upload artifact
49 | uses: actions/upload-pages-artifact@v2
50 | with:
51 | # Upload entire repository
52 | path: "dist"
53 | - name: Deploy to GitHub Pages
54 | id: deployment
55 | uses: actions/deploy-pages@v2
56 |
--------------------------------------------------------------------------------
/.github/workflows/sanity-check.yml:
--------------------------------------------------------------------------------
1 | name: PR Sanity Check
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-20.04
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v2
11 |
12 | - name: Install Packages
13 | run: |
14 | yarn install
15 | - name: Lint
16 | run: |
17 | yarn lint
18 | - name: Build
19 | run: |
20 | npx reveal-md lessons/ --static dist
21 | rsync -a -r lessons/ dist/
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .vscode
5 | .eslintcache
6 | tmp
7 | .idea
8 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/.nojekyll
--------------------------------------------------------------------------------
/.reveal-md/listing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{pageTitle}}
6 |
7 |
8 |
9 |
10 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.reveal-md/styles.css:
--------------------------------------------------------------------------------
1 | .reveal {
2 | font-size: 36px;
3 | }
4 |
5 | .reveal pre code {
6 | max-height: 80%;
7 | }
8 |
9 | * {
10 | font-family: Helvetica, sans-serif !important;
11 | }
12 |
13 | .table-of-contents h1 {
14 | font-size: 16px;
15 | }
16 | .table-of-contents {
17 | max-height: 95%;
18 | overflow-y: scroll;
19 | overflow: -moz-scrollbars-vertical;
20 | font-size: 14px;
21 | }
22 |
23 | .section[data-markdown-parsed="true"] i {
24 | font-style: italic;
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Developer. Basic
2 |
3 | Это репозиторий курса ["OTUS JavaScript Developer. Basic"](https://otus.ru/lessons/javascript-basic/)
4 |
5 |
6 | Структура репозитория
7 |
8 | Репозиторий имеет следующую структуру
9 |
10 | - в корне проекта находятся файлы настроек проекта и используемых инструментов
11 | - в директории `lessons` находится под-директории по маске `lesson{XX}` с материалами для каждого занятия
12 |
13 |
14 |
15 | Работа с репозиторием
16 |
17 | Репозиторий обслуживается инструментами на базе Node.js. Для работы понадобится `node.js` и `yarn` (в качестве пакетного менеджера).
18 |
19 | ```bash
20 | # Склонируйте репозиторий
21 | git clone https://github.com/vvscode/otus--javascript-basic
22 |
23 | # Установите зависимости
24 | cd otus--javascript-basic && yarn
25 |
26 | # Создайте директорию для нового занятия
27 | mkdir lessons/lessonXX
28 |
29 | # Создайте файл для нового занятия
30 | touch lessons/lessonXX/lecture.md
31 |
32 | # Запустите reveal-md в режиме разработки
33 | yarn dev lessons/lessonXX/lecture.md
34 | ```
35 |
36 | При коммите должны сработать husky-хуки для проверки и форматирования измененных файлов. Дополнительная проверка настроена через github-actions.
37 |
38 | При мерже пуллреквеста в мастер автоматически произойдет деплой изменений на сервис gh-pages. Результат можно будет увидеть здесь [otus--javascript-basic](https://vvscode.github.io/otus--javascript-basic/index.html).
39 |
40 | Если для занятия (для демонстрации или для активностей) нужно запускать примеры кода - используйте codesandbox, разместив код в поддиректории соответствующего урока (чтобы держать все материалы в одном месте).
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lessons/final-task/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Выбрать/придумать проект, к которому вы будете применять получаемые знания. В идеале это решение какой-то задачи/проблемы, которая близка/интересна лично вам. В первую очередь это нужно для практического закрепления знаний/навыков, во вторую - для демонстрации этого другим людям.
3 | ---
4 |
5 | В личном кабинете, в уроке к заданию, вам нужно нажать кнопку "Предложить тему" и ввести название темы (именно в таком виде она пойдет в итоговый сертификат).
6 |
7 | После этого нужно отдельным сообщением описать предполагаемый функционал (чтобы было ясно, что именно вы планируете реализовать).
8 |
--------------------------------------------------------------------------------
/lessons/final-task/lecture.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Выбор темы и организация проектной работы
3 | description: Выбор темы и организация проектной работы
4 | ---
5 |
6 | # OTUS
7 |
8 | ## Javascript Basic
9 |
10 |
11 |
12 | Вопросы?
13 |
14 |
15 |
16 | Требования к финальному проекту
17 |
18 |
19 |
20 | Обязательно все из списка:
21 |
22 | - поддержка пользовательских сессий / сохранения пользовательских данных
23 | - использование внешнего API
24 | - минимум 3 разных роута
25 | - использование параметризированных роутов
26 | - настроенные линтеры, тесты и автодеплой через CI/CD
27 | - возможность проверить приложение без локального запуска (с исключениями)
28 | - оформленный репозиторий и README
29 |
30 | - покрытие кода тестами от 70% \*
31 | - основной функционал покрыт интеграционными тестами (testing-library/cypress/testcafe/etc) \*
32 |
33 |
34 |
35 | Поддержка хотя бы одного пункта из списка:
36 |
37 | - аутентификация пользователя
38 | - отзывчивый дизайн
39 | - работа в оффлайн (PWA) / нативные технологии под мобильные платформы (React Native/cordova)
40 | - совместная работа пользователей
41 |
42 |
43 |
44 | Примеры:
45 |
46 | - крокодил c рисованием на канвасе
47 | - сеги
48 | - книга рецептов
49 | - расширение для браузера для подсветки и создания заметок
50 | - клиент для slack / tg / vk
51 | - базовая аркадная игра с преследованием
52 |
53 |
54 |
55 | Вопросы?
56 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 | Напишите программу, которая выводит на экран числа от 1 до 100.
11 |
12 | При этом вместо чисел,
13 | - кратных 3, программа должна выводить слово Fizz,
14 | - а вместо чисел, кратных 5 — слово Buzz,
15 | - a число кратно 15, то программа должна выводить слово FizzBuzz
16 |
17 | Для вывода данных использовать `console.log` (Например `console.log('FizzBuzz');`)
18 |
20 | Начало вывода:
21 |
22 | 1
23 | 2
24 | Fizz
25 | 4
26 | Buzz
27 | Fizz
28 | 7
29 | 8
30 | Fizz
31 | Buzz
32 | 11
33 | Fizz
34 | 13
35 | 14
36 | FizzBuzz
37 | ...
38 |
39 |
40 | Краткая справка по конструкциям языка
43 |
44 |
45 |
46 |
50 | Краткие заметки в помощь
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "otus-fizzbuzz",
3 | "version": "1.0.0",
4 | "description": "Basic fizzBuzz practice sandbox",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": [
16 | "otus"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": false,
3 | "hardReloadOnChange": true,
4 | "view": "browser"
5 | }
6 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/src/..js:
--------------------------------------------------------------------------------
1 | export function getCorrectCallsArgs() {
2 | return [
3 | [1],
4 | [2],
5 | ["Fizz"],
6 | [4],
7 | ["Buzz"],
8 | ["Fizz"],
9 | [7],
10 | [8],
11 | ["Fizz"],
12 | ["Buzz"],
13 | [11],
14 | ["Fizz"],
15 | [13],
16 | [14],
17 | ["FizzBuzz"],
18 | [16],
19 | [17],
20 | ["Fizz"],
21 | [19],
22 | ["Buzz"],
23 | ["Fizz"],
24 | [22],
25 | [23],
26 | ["Fizz"],
27 | ["Buzz"],
28 | [26],
29 | ["Fizz"],
30 | [28],
31 | [29],
32 | ["FizzBuzz"],
33 | [31],
34 | [32],
35 | ["Fizz"],
36 | [34],
37 | ["Buzz"],
38 | ["Fizz"],
39 | [37],
40 | [38],
41 | ["Fizz"],
42 | ["Buzz"],
43 | [41],
44 | ["Fizz"],
45 | [43],
46 | [44],
47 | ["FizzBuzz"],
48 | [46],
49 | [47],
50 | ["Fizz"],
51 | [49],
52 | ["Buzz"],
53 | ["Fizz"],
54 | [52],
55 | [53],
56 | ["Fizz"],
57 | ["Buzz"],
58 | [56],
59 | ["Fizz"],
60 | [58],
61 | [59],
62 | ["FizzBuzz"],
63 | [61],
64 | [62],
65 | ["Fizz"],
66 | [64],
67 | ["Buzz"],
68 | ["Fizz"],
69 | [67],
70 | [68],
71 | ["Fizz"],
72 | ["Buzz"],
73 | [71],
74 | ["Fizz"],
75 | [73],
76 | [74],
77 | ["FizzBuzz"],
78 | [76],
79 | [77],
80 | ["Fizz"],
81 | [79],
82 | ["Buzz"],
83 | ["Fizz"],
84 | [82],
85 | [83],
86 | ["Fizz"],
87 | ["Buzz"],
88 | [86],
89 | ["Fizz"],
90 | [88],
91 | [89],
92 | ["FizzBuzz"],
93 | [91],
94 | [92],
95 | ["Fizz"],
96 | [94],
97 | ["Buzz"],
98 | ["Fizz"],
99 | [97],
100 | [98],
101 | ["Fizz"],
102 | ["Buzz"],
103 | ];
104 | }
105 |
106 | export function getNormalizedSource(fn) {
107 | return Function.prototype.toString
108 | .apply(fn)
109 | .replace(/\s+/g, " ")
110 | .replace(/(\w)\s+\(/g, "$1(")
111 | .replace(/(\w)\s+{/g, "$1{")
112 | .replace(/}\s+(\w)/g, "}$1");
113 | }
114 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Напишите программу, которая выводит на экран числа от 1 до 100.
3 | * При этом вместо чисел,
4 | * - кратных 3, программа должна выводить слово Fizz,
5 | * - а вместо чисел, кратных 5 — слово Buzz,
6 | * - a число кратно 15, то программа должна выводить слово FizzBuzz
7 | * Для вывода данных использовать `console.log` (Например `console.log('FizzBuzz');`)
8 | *
9 | * Краткая справка по конструкциям языка:
10 | * https://javascript.ru/basic/syntax-switch-for
11 | * https://gist.github.com/vvscode/5b5eec5bb5270de3ac27a3e0298430d9
12 | */
13 | export function fizzBuzz() {
14 | // ваш код пишите здесь
15 | console.log("FizzBuzz");
16 | }
17 |
18 | fizzBuzz();
19 |
--------------------------------------------------------------------------------
/lessons/lesson01/code/fizzbuzz/src/index.test.js:
--------------------------------------------------------------------------------
1 | import { fizzBuzz } from "./index.js";
2 | import { getCorrectCallsArgs, getNormalizedSource } from "./..js";
3 |
4 | // Решить задачу без использования конструкций if/else
5 | const SHOULD_NOT_USE_IF = false;
6 | // Решить задачу без использования тернарного оператора
7 | const SHOULD_NOT_USE_THERNARY = false;
8 | // Решить задачу без использования логических && и ||
9 | const SHOULD_NOT_USE_AND_OR = false;
10 |
11 | describe("fizzBuzz", () => {
12 | jest.spyOn(console, "log");
13 |
14 | beforeEach(() => {
15 | jest.resetAllMocks();
16 | });
17 |
18 | it("is a function", () => expect(typeof fizzBuzz).toBe("function"));
19 |
20 | it("prints values using console.log", () => {
21 | fizzBuzz();
22 | expect(console.log).toHaveBeenCalled();
23 | });
24 |
25 | it("prints exactly 100 items", () => {
26 | fizzBuzz();
27 | expect(console.log).toHaveBeenCalledTimes(100);
28 | });
29 |
30 | describe("proper results", () => {
31 | getCorrectCallsArgs().forEach((args, index) =>
32 | it(`is expected to see ${args} at position #${index + 1}`, () => {
33 | fizzBuzz();
34 | expect(console.log.mock.calls[index]).toEqual(args);
35 | })
36 | );
37 | });
38 |
39 | SHOULD_NOT_USE_IF &&
40 | describe("Should not use if/else constructions", () => {
41 | ["if", "else"].forEach((stopWord) =>
42 | it(`should not use ${stopWord}`, () => {
43 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false);
44 | })
45 | );
46 | });
47 |
48 | SHOULD_NOT_USE_THERNARY &&
49 | describe("Should not use thernary operator (? :)", () => {
50 | ["?", ":"].forEach((stopWord) =>
51 | it(`should not use ${stopWord}`, () => {
52 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false);
53 | })
54 | );
55 | });
56 |
57 | SHOULD_NOT_USE_AND_OR &&
58 | describe("Should not use logical operators &&/||", () => {
59 | ["&&", "||"].forEach((stopWord) =>
60 | it(`should not use ${stopWord}`, () => {
61 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false);
62 | })
63 | );
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/lessons/lesson01/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Сформулировать свои ожидания и цели (в идеале по SMART), сразу получить обрантную связь относительно того, на сколько ожидания могут быть закрыты курсом. Познакомиться с продуктивным подходом к задаванию вопросов, чтобы общение по учебе было эффективным (это же применимо к рабочим контекстам).
3 | ---
4 |
5 | ## Знакомство
6 |
7 | > Сформулирует цели и задачи поступления на курс, опишет свои ожидания от обучения. Познакомится с правилами "Хорошего тона" для продуктивного общения на технические темы
8 |
9 | 1. Развернуто (от 1000 символов) написать о следующих вещах
10 |
11 | - цель поступления на курс
12 | - ожидания от курса
13 | - как вы поймете, что курс был полезен (по каким конкретным критериям)
14 |
15 | 2. Написать конспект/чеклист/план к действию по мотивам статьи https://www.opennet.ru/docs/RUS/smart_question/
16 |
17 | И то и другое отправить в "Чат с преподавателем"
18 |
19 | **Важно!**
20 | Если у вас нет опыта в JS, приступите к самостоятельному изучению видеокурса ["Javascript-для начинающих разработчиков"](https://otus.ru/online/online-js/). Если у вас нет к нему доступа, пожалуйста, напишите запрос на help@otus.ru. Вам нужно освоить его до начала модуля "Базовый JavaScript. Работа с GIT и настройка окружения".
21 |
22 | ### Критерии приемки
23 |
24 | - Описаны цели - 1 балл
25 | - Описаны ожидания - 1 балл
26 | - Описаны критерии оценки пользы курса - 1 балл
27 | - Описания даны в четком формате ("сенсорно очевидные"), чтобы другой человек глядя на описание мог сказать, достигнуто/выполнено или нет - по 1 баллу за цели / ожидания / критерии
28 | - Описание развернутое (больше 1000 символов) - 1 балл
29 |
30 | - Написан конспект к статье - 1 балл
31 | - Конспект содержит от 5 пунктов - 1 балл
32 | -Задание сдано в срок - 1 балл
33 |
34 | **Задание принято** от 9 баллов
35 |
--------------------------------------------------------------------------------
/lessons/lesson01/images/question1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question1.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question2.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question3.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question4.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question5.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question6.png
--------------------------------------------------------------------------------
/lessons/lesson01/images/question7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question7.png
--------------------------------------------------------------------------------
/lessons/lesson02/code/isPalindrom/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | isPalindrom
8 |
9 |
10 | isPalindrom
11 |
12 | Палиндром — в буквальном переводе перевертень (от греч. παλινδρομέω —
13 | возвращаться) — это слово или фраза, которые одинаково читаются по буквам
14 | или по словам как слева направо, так и справа налево.
15 |
16 |
17 | Напишите функцию isPalindrom
в файле
18 | src/isPalindrom.js
, которая будет принимать строку и
19 | возвращать результат работы на палиндром
20 |
21 | Перейдите на вкладку Tests для проверки решения
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lessons/lesson02/code/isPalindrom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ispalindrom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson02/code/isPalindrom/src/isPalindrom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Проверка строки на палиндром
3 | * https://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BB%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC
4 | * @param {string} str - строка для проверки на палиндром
5 | * @returns {boolean}
6 | */
7 | export function isPalindrom(str) {}
8 |
--------------------------------------------------------------------------------
/lessons/lesson02/code/isPalindrom/src/isPalindrom.test.js:
--------------------------------------------------------------------------------
1 | import { isPalindrom } from "./isPalindrom";
2 |
3 | describe("isPalindrom", () => {
4 | // palindroms
5 | ["", "a", "aa", "aba", "aabaa", "abcba"].forEach((palindrom) =>
6 | it(`detects "${palindrom}" as palindrom`, () => {
7 | expect(isPalindrom(palindrom)).toBe(true);
8 | })
9 | );
10 |
11 | // not palindroms
12 | ["ab", "aab", "aaba", "aaabaa", "abc", "aaaa "].forEach((notPalindrom) =>
13 | it(`does not detect "${notPalindrom}" as palindrom`, () => {
14 | expect(isPalindrom(notPalindrom)).toBe(false);
15 | })
16 | );
17 | });
18 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 | bind(function, object)
10 |
11 | Связывает функцию function с объектом object. Это значит, что каждый раз
12 | когда она будет вызвана, this будет указывать на object.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bind",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "7.2.0"
15 | },
16 | "resolutions": {
17 | "@babel/preset-env": "7.13.8"
18 | },
19 | "keywords": []
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/src/bind.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Создает фукнцию-обертку, с зафиксированным контекстом
3 | * @param {Function} fn
4 | * @param {any} context
5 | */
6 | export function bind(fn) {
7 | // put your code here
8 | return fn;
9 | }
10 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/src/bind.test.js:
--------------------------------------------------------------------------------
1 | import { bind } from "./bind";
2 |
3 | describe("bind", () => {
4 | it("is a funciton", () => {
5 | expect(bind).toBeInstanceOf(Function);
6 | });
7 |
8 | it("returns new function", () => {
9 | expect(bind(jest.fn())).toBeInstanceOf(Function);
10 | });
11 |
12 | it("returns function which calls with fixed context", () => {
13 | const bob = { name: "Bob" };
14 | const spy = jest.fn();
15 |
16 | const bindedFunction = bind(function () {
17 | spy(this);
18 | }, bob);
19 | expect(spy).not.toHaveBeenCalled();
20 | bindedFunction();
21 | expect(spy).toHaveBeenCalledWith(bob);
22 | });
23 |
24 | it("passes params to the function", () => {
25 | const bob = { name: "Bob" };
26 | const spy = jest.fn();
27 | const [a, b] = [Math.random(), Math.random()];
28 |
29 | const bindedFunction = bind(spy, bob);
30 | expect(spy).not.toHaveBeenCalled();
31 | bindedFunction(a, b);
32 | expect(spy).toHaveBeenCalledWith(a, b);
33 | });
34 |
35 | it("returns function results", () => {
36 | const x = Math.random();
37 | const spy = jest.fn(() => x);
38 | expect(bind(spy, null)()).toBe(x);
39 | });
40 |
41 | it("does not use .bind method from Function", () => {
42 | const x = Math.random();
43 | jest.spyOn(Function.prototype, "bind");
44 | const spy = jest.fn(() => x);
45 | expect(bind(spy, null)()).toBe(x);
46 | expect(Function.prototype.bind).not.toHaveBeenCalled();
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/bind/src/index.js:
--------------------------------------------------------------------------------
1 | import { bind } from "./bind";
2 |
3 | const bob = {
4 | name: "Bob",
5 | };
6 |
7 | function greet() {
8 | alert(`Hello from ${this.name}`);
9 | }
10 |
11 | const greetFromBob = bind(greet, bob);
12 |
13 | document.getElementById("app").innerHTML = `
14 | Hello bind!
15 | `;
16 |
17 | document.querySelector("button").addEventListener("click", greetFromBob);
18 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "singleton",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "7.2.0"
15 | },
16 | "resolutions": {
17 | "@babel/preset-env": "7.13.8"
18 | },
19 | "keywords": []
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/index.js:
--------------------------------------------------------------------------------
1 | import "./styles.css";
2 |
3 | document.getElementById("app").innerHTML = `
4 |
5 |
6 | Одиночка (англ. Singleton )
7 | — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
8 |
`;
9 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/makeSingleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns singleton version of passed class/constructor function
3 | * @param {Call|Constructor Function} cls
4 | */
5 | // export it with name makeSingleton
6 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/makeSingleton.test.js:
--------------------------------------------------------------------------------
1 | import { makeSingleton } from "./makeSingleton";
2 |
3 | describe.skip("makeSingleton", () => {
4 | it("is a function", () => {
5 | expect(makeSingleton).toBeInstanceOf(Function);
6 | });
7 |
8 | it("returns a constructor for passed class", () => {
9 | class X {}
10 | let SingletonX = makeSingleton(X);
11 | expect(SingletonX).toBeInstanceOf(Function);
12 | expect(new SingletonX()).toBeInstanceOf(X);
13 | });
14 |
15 | it("returns singlton version of passed class", () => {
16 | class X {}
17 | let SingletonX = makeSingleton(X);
18 | expect(new X()).not.toBe(new X());
19 | expect(new SingletonX()).toBe(new SingletonX());
20 | });
21 |
22 | it("passes arguments to the constructor", () => {
23 | class Y {
24 | constructor(a, b) {
25 | this.a = a;
26 | this.b = b;
27 | }
28 | }
29 | let SingletonY = makeSingleton(Y);
30 | let a = Math.random();
31 | let b = Math.random();
32 | let y = new SingletonY(a, b);
33 | expect(y.a).toBe(a);
34 | expect(y.b).toBe(b);
35 | });
36 |
37 | it("passes arguments to the constructor only on first instance", () => {
38 | function Z(a, b) {
39 | this.a = a;
40 | this.b = b;
41 | }
42 | let SingletonZ = makeSingleton(Z);
43 | let a = Math.random();
44 | let b = Math.random();
45 | let z1 = new SingletonZ(a, b);
46 | let z2 = new SingletonZ(1, 2);
47 | let z3 = new SingletonZ(3, 4);
48 | expect(z1.a).toBe(a);
49 | expect(z2.b).toBe(b);
50 | expect(z1).toBe(z2);
51 | expect(z1).toBe(z3);
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/singleton.js:
--------------------------------------------------------------------------------
1 | // Put your singleton here
2 | // export it with name Singleton
3 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/singleton.test.js:
--------------------------------------------------------------------------------
1 | import { Singleton } from "./singleton";
2 |
3 | describe.skip("Singleton", () => {
4 | it("is a funciton", () => expect(Singleton).toBeInstanceOf(Function));
5 |
6 | it("is a constructor", () =>
7 | expect(new Singleton()).toBeInstanceOf(Singleton));
8 |
9 | it("is a singleton", () => {
10 | expect(new Singleton()).toBe(new Singleton());
11 | });
12 |
13 | it("does not keep props in creator", () => {
14 | const singleton1 = new Singleton();
15 |
16 | Object.keys(Singleton).forEach((extraKey) => {
17 | delete Singleton[extraKey];
18 | });
19 | const singleton2 = new Singleton();
20 | expect(singleton1).toBe(singleton2);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/singleton/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smart-getter",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "7.2.0"
15 | },
16 | "resolutions": {
17 | "@babel/preset-env": "7.13.8"
18 | },
19 | "keywords": []
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/src/get.js:
--------------------------------------------------------------------------------
1 | /**
2 | * get: Smart getter for object props
3 | *
4 | * Check tests for usage examples
5 | *
6 | * @param {any} obj
7 | * @param {string} property
8 | * @param {any} fallbackValue
9 | */
10 | // export function get() {}
11 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/src/get.test.js:
--------------------------------------------------------------------------------
1 | import { get } from "./get";
2 |
3 | const getObj = () => ({
4 | a: 1,
5 | b: 2,
6 | c: {
7 | d: 3,
8 | e: 4,
9 | f: [
10 | 0,
11 | 1,
12 | 2,
13 | 3,
14 | {
15 | a: 10,
16 | b: 20,
17 | c: {
18 | d: {
19 | e: 30,
20 | },
21 | },
22 | },
23 | ],
24 | },
25 | });
26 |
27 | describe("get", () => {
28 | const invalidKeys = ["d", "c.z", "x.d", "c.f.100", "c.f.4.x", "z.f.4.b"];
29 | let obj;
30 | beforeEach(() => (obj = getObj()));
31 |
32 | it("is a function", () => expect(get).toBeInstanceOf(Function));
33 |
34 | it("returns valid properties", () => {
35 | expect(get(obj, "a")).toBe(1);
36 | expect(get(obj, "b")).toBe(2);
37 | expect(get(obj, "c.d")).toBe(3);
38 | expect(get(obj, "c.e")).toBe(4);
39 | expect(get(obj, "c.f.0")).toBe(0);
40 | expect(get(obj, "c.f.1")).toBe(1);
41 | expect(get(obj, "c.f.2")).toBe(2);
42 | expect(get(obj, "c.f.3")).toBe(3);
43 | expect(get(obj, "c.f.4.a")).toBe(10);
44 | expect(get(obj, "c.f.4.b")).toBe(20);
45 | expect(get(obj, "c.f.4.c.d.e")).toBe(30);
46 | });
47 |
48 | it("returns undefined for invalid properties", () => {
49 | invalidKeys.forEach((invalidKey) =>
50 | expect(get(obj, invalidKey)).toBe(undefined)
51 | );
52 | });
53 |
54 | it("returns fallback value if provided", () => {
55 | invalidKeys.forEach((invalidKey) => {
56 | const fallbackValue = Math.random();
57 | expect(get(obj, invalidKey, fallbackValue)).toBe(fallbackValue);
58 | });
59 | });
60 |
61 | it("works not only with objects", () => {
62 | expect(get([1, 2, 3], "1")).toBe(2);
63 | expect(get("hello", "1")).toBe("e");
64 | expect(get(23, "1")).toBe(undefined);
65 | expect(get(null, "1")).toBe(undefined);
66 | expect(get(undefined, "1")).toBe(undefined);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/lessons/lesson03/code/smart-getter/src/index.js:
--------------------------------------------------------------------------------
1 | // import { get } from "./get";
2 |
3 | window.data = {
4 | person: {
5 | name: "Bob",
6 | details: {
7 | age: 18,
8 | catName: "Bars",
9 | },
10 | },
11 | };
12 |
13 | document.getElementById("app").innerHTML = `Getter `;
14 |
15 | // window._get = get;
16 |
--------------------------------------------------------------------------------
/lessons/lesson03/homework_lec1.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Вы будете решать большое число достаточно простых задач. В аналогии с изучением иностранных языков - это упражнения на подстановку слов вместо пробелов. Нужно это для насмотренности на код и применения знаний элементарных конструкций языка.
3 | ---
4 |
5 | В ходе выполнения задания вы поработаете с двумя репозиториями, которые содержат в себе типичные задачи работы на Javascript.
6 |
7 | Работать вы будете с двумя репозиториями:
8 |
9 | https://github.com/mrdavidlaing/javascript-koans
10 |
11 | и
12 |
13 | https://github.com/liammclennan/JavaScript-Koans
14 |
15 |
16 |
17 | Вам нужно для каждого из репозиториев выполнить следующие шаги:
18 |
19 | - скачать (склонировать или просто скачать) к себе репозиторий
20 | - разобраться как запустить задачи (которые оформлены в виде тестов)
21 | - внести изменения так, чтобы решения задач проходили тесты
22 | - сделать коммит и пуллреквест с решением
23 | - сделать скриншот всех пройденных тестов
24 |
25 |
26 |
27 | Задание можно сдавать в формате:
28 |
29 | скриншоты пройденных тестов
30 |
31 | **или**
32 |
33 | скриншоты пройденных тестов и ссылка на пуллреквест с вашими изменениями (работу с гитом мы еще не разбирали, так что это задание со звездочкой)
34 |
35 |
36 |
37 | Критерии оценки:
38 |
39 | - решены все задачи в 1 репозитории (и предоставлены скриншоты пройденных тестов) - 5 баллов (по 5 за каждый)
40 | - сделан пуллреквест с изменениями по решенным задачам - 3 балла (по 3 за каждый репозиторий)
41 |
42 | **Принято ставится от 10 баллов**
43 |
--------------------------------------------------------------------------------
/lessons/lesson03/homework_lec3.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Продолжаем тренировку насмотренности и базовые операции. Большая часть задач не требует знаний алгоритмов, достаточно просто сформулировать последовательность шагов/действий. Вы тренируетесь писать функции, продолжаете знакомство с проверкой кода тестами. Продолжая аналогию с изучением иностранных языков - вы пишете прописи, тренируете руковой водить прямые линии и скругления. Нам здесь важен факт того, что вы прорешали нужное число задач (если бы могли дать вам задание решить одну задачу 20 способами - это задание было бы здесь, но увы). И т.к. это прописи - мы этот код не проверяем (нам важен факт написания кода, и это мы видим в вашем профиле), но при желании, вы можете прислать задачи на разбор.
3 | ---
4 |
5 | ### "Практика кодирования"
6 |
7 | 1. Зарегистрировать аккаунт на сайте [codewars.com](https://www.codewars.com/) (это можно сделать с помощью github аккаунта) - логин должен быть такой же как в вашем github профиле, имя и фамилия такие же как в личном кабинете ОТУС
8 | 2. Решить 19 задач из списка ниже
9 | 3. Убедиться, что в вашем профиле видно когда, сколько и какие задачи вы решили
10 | 4. Ссылку на ваш профиль codewars сбросить в чат по дз
11 |
12 |
13 |
14 | Задачи для решения:
15 |
16 | **String**
17 | 7 kyu https://www.codewars.com/kata/56d6b921c9ae3fd926000601
18 | 7 kyu https://www.codewars.com/kata/anagram-detection
19 | 7 kyu https://www.codewars.com/kata/disemvowel-trolls/
20 | 7 kyu https://www.codewars.com/kata/mumbling/
21 | 7 kyu https://www.codewars.com/kata/highest-and-lowest/
22 | 7 kyu https://www.codewars.com/kata/isograms/
23 | 7 kyu https://www.codewars.com/kata/char-code-calculation
24 | 7 kyu https://www.codewars.com/kata/cat-and-mouse-2d-version/
25 |
26 |
27 |
28 | **Number**
29 | 8 kyu https://www.codewars.com/kata/opposite-number
30 | 8 kyu https://www.codewars.com/kata/even-or-odd
31 | 8 kyu https://www.codewars.com/kata/century-from-year
32 | 7 kyu https://www.codewars.com/kata/greatest-common-divisor
33 | 7 kyu https://www.codewars.com/kata/factorial
34 | 7 kyu https://www.codewars.com/kata/squares-sequence
35 | 7 kyu https://www.codewars.com/kata/concatenated-sum
36 | 7 kyu https://www.codewars.com/kata/filter-the-number
37 |
38 |
39 |
40 | **Array**
41 | 8 kyu https://www.codewars.com/kata/removing-elements
42 | 8 kyu https://www.codewars.com/kata/remove-duplicates-from-list
43 | 8 kyu https://www.codewars.com/kata/sum-of-positive
44 |
45 | Задание принято, если решено **минимум 19 задач** из списка с момента старта курса
46 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | DOM Examples
8 |
14 |
15 |
16 | Пример работы с DOM
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "domexamples",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamples/src/script.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | name: "Bob",
3 | age: 18,
4 | };
5 |
6 | function alertName() {
7 | alert(data.name);
8 | }
9 | window.alertName = alertName; // это нужно для `onclick="alertName()"`
10 |
11 | function alertAge() {
12 | alert(data.age);
13 | }
14 |
15 | function drawDataViaInnerHTML(el, data) {
16 | el.innerHTML = `
17 |
${data.name}
18 |
19 | ${data.age}
20 | `;
21 |
22 | el.querySelector("h2").addEventListener("click", alertAge);
23 | }
24 |
25 | function drawDataViaCreateElement(el, data) {
26 | const div = document.createElement("div");
27 |
28 | const h1 = document.createElement("h1");
29 | h1.innerText = data.name;
30 | h1.addEventListener("click", alertName);
31 |
32 | const h2 = document.createElement("h2");
33 | h2.innerText = data.age;
34 | h2.onclick = alertAge;
35 |
36 | const hr = document.createElement("hr");
37 |
38 | div.appendChild(h1);
39 | div.appendChild(hr);
40 | div.appendChild(h2);
41 | el.appendChild(div);
42 | }
43 |
44 | drawDataViaInnerHTML(document.querySelector("#container1"), data);
45 | drawDataViaCreateElement(document.getElementById("container2"), data);
46 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamplesPractice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | DOM Examples
8 |
14 |
15 |
16 | Пример работы с DOM
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamplesPractice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "domexamplespractice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/DOMExamplesPractice/src/script.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | coord: {
3 | lon: 37.62,
4 | lat: 55.75,
5 | },
6 | weather: [
7 | {
8 | id: 803,
9 | main: "Clouds",
10 | description: "broken clouds",
11 | icon: "04d",
12 | },
13 | ],
14 | base: "stations",
15 | main: {
16 | temp: 2.15,
17 | feels_like: -3.03,
18 | temp_min: 2,
19 | temp_max: 2.22,
20 | pressure: 1029,
21 | humidity: 69,
22 | },
23 | visibility: 10000,
24 | wind: {
25 | speed: 4,
26 | deg: 310,
27 | },
28 | clouds: {
29 | all: 75,
30 | },
31 | dt: 1605098072,
32 | sys: {
33 | type: 1,
34 | id: 9029,
35 | country: "RU",
36 | sunrise: 1605070630,
37 | sunset: 1605101407,
38 | },
39 | timezone: 10800,
40 | id: 524901,
41 | name: "Moscow",
42 | cod: 200,
43 | };
44 |
45 | /**
46 | * Функция должна отображать в элементе следующие данные
47 | * - имя города
48 | * - текущую температуру (main.temp)
49 | * - иконку для погоды (одну или все - weather[index]icon)
50 | * (см https://openweathermap.org/weather-conditions#How-to-get-icon-URL)
51 | * например http://openweathermap.org/img/wn/10d@2x.png
52 | *
53 | * Разметка любая
54 | */
55 | function drawWeather(el, data) {
56 | // put your code here
57 | }
58 |
59 | drawWeather(document.querySelector("#container"), data);
60 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/dataStoragePractice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Storage Examples
8 |
9 |
10 | Пример работы с Storage
11 |
12 |
13 | Вы создаете прототип "списка задач". Пользователь может вводить данные в
14 | поле ввода, и видеть список введенных данных ниже. При обновлении страницы
15 | (или закрытии браузера) список сохраняется.
16 |
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/dataStoragePractice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datastoragepractice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/dataStoragePractice/src/script.js:
--------------------------------------------------------------------------------
1 | (async function () {
2 | // Должна возвращать список пользователя
3 | // Если пользователь ничего не вводил - пустой список
4 | async function readList() {
5 | // put your code here
6 | }
7 |
8 | // Сохраняет список
9 | function saveList(items) {
10 | // put your code here
11 | }
12 |
13 | function drawList(el, items) {
14 | el.innerHTML = `${items.map((el) => `${el} `).join("")} `;
15 | }
16 |
17 | // Получаем указатели на нужные элементы
18 | const form = document.querySelector("form");
19 | const listEl = document.querySelector("#list");
20 |
21 | // Читаем список при старте
22 | const items = await readList();
23 |
24 | // и отрисовываем список
25 | drawList(listEl, items);
26 |
27 | form.addEventListener("submit", (ev) => {
28 | // чтобы не перезагружать страницу
29 | ev.preventDefault();
30 |
31 | // читаем значение из формы
32 | const formElement = ev.target;
33 | const input = formElement.querySelector("input");
34 | const value = input.value;
35 | input.value = "";
36 |
37 | // добавляем элемент в список
38 | items.push(value);
39 |
40 | // обновляем список
41 | drawList(listEl, items);
42 |
43 | // сохраняем список
44 | saveList(items);
45 | });
46 | })();
47 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/fetchPractice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Fetch Examples
8 |
9 |
10 | Пример работы с fetch
11 |
12 |
13 | Вы создаете прототип "списка задач". Пользователь может вводить данные в
14 | поле ввода, и видеть список введенных данных ниже. При обновлении страницы
15 | (или закрытии браузера) список сохраняется.
16 |
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/fetchPractice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetchpractice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/fetchPractice/src/script.js:
--------------------------------------------------------------------------------
1 | (async function () {
2 | // Получаем указатели на нужные элементы
3 | const formEl = document.querySelector("form");
4 | const weatherInfoEl = document.querySelector("#weatherInfo");
5 |
6 | function showWeather(el, weatherInfo) {
7 | el.innerHTML = JSON.stringify(weatherInfo, null, 2);
8 | }
9 |
10 | /**
11 | * Функция должна делать запрос на
12 | * https://api.openweathermap.org/data/2.5/weather?units=metric&q={{CITY_NAME}}&appid={{APP_ID}}
13 | * где
14 | * {{CITY_NAME}} должен быть заменен на имя города
15 | * {{APP_ID}} должен быть заменен на ключ приложения
16 | * Запрос возвращает данные в формате JSON
17 | *
18 | * функция должна возвращать (Promise) данные с информацией о погоде
19 | * @param {string} cityName
20 | */
21 | async function getWeather(cityName) {
22 | // put your code here
23 | }
24 |
25 | formEl.addEventListener("submit", async (ev) => {
26 | // чтобы не перезагружать страницу
27 | ev.preventDefault();
28 |
29 | // читаем значение из формы
30 | const formElement = ev.target;
31 | const inputEl = formElement.querySelector("input");
32 | const cityName = inputEl.value;
33 | inputEl.value = "";
34 |
35 | const weather = await getWeather(cityName);
36 | showWeather(weatherInfoEl, weather);
37 | });
38 | })();
39 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/singleThreadInJs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | JS - однопоточный
8 |
9 |
10 | Пример проблемы однопоточности
11 |
12 |
13 |
14 |
15 |
16 |
17 | Slow button
18 | Fast button
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/singleThreadInJs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jssinglethread",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/singleThreadInJs/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lessons/lesson04/code/singleThreadInJs/src/script.js:
--------------------------------------------------------------------------------
1 | // Указатель на элемент для отображения времени
2 | const timeEl = document.querySelector("#time");
3 |
4 | // Каждую секунду мы выполняем функцию
5 | setInterval(() => {
6 | // Которая в элементе отрисует текущее время
7 | timeEl.innerHTML = new Date().toLocaleTimeString();
8 | }, 1000);
9 |
10 | // на кнопку
11 | // мы добавляем обработчик события, который выполняет какую-то операцию долгое время
12 | document.querySelector("#slowBtn").addEventListener("click", function () {
13 | // noprotect
14 | for (var i = 0; i < 500000; i++) {
15 | console.log(i);
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/isEqualXAndO/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | isEqualXAndO
8 |
9 |
10 | isEqualXAndO
11 |
12 | Given an input string, you should check if the string contains the same
13 | amount of `x` and `o`. The case doesn't matter - if the amount is equal,
14 | return a `true` otherwise return `false`
15 |
16 |
17 |
18 | Для данной строки нужно проверить, что она содержит одинаковое число `x` и
19 | `o`. Регистр не имеет значения. Если число вхождений одинаковое - вернуть
20 | `true`, иначе `false`
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/isEqualXAndO/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "isequalxando",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": []
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/isEqualXAndO/src/isEqualXAndO.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Для данной строки нужно проверить, что она содержит одинаковое число `x` и `o`.
3 | * Регистр не имеет значения.
4 | * Если число вхождений одинаковое - вернуть true, иначе false
5 | * @param {string} str - строка для проверки на палиндром
6 | * @returns {boolean}
7 | */
8 | export function isEqualXAndO(str) {}
9 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/isEqualXAndO/src/isEqualXAndO.test.js:
--------------------------------------------------------------------------------
1 | import { isEqualXAndO } from "./isEqualXAndO";
2 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/uicalc/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/uicalc/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | "@babel/preset-env",
5 | {
6 | targets: "defaults",
7 | },
8 | ],
9 | ],
10 | env: {
11 | test: {
12 | plugins: ["@babel/plugin-transform-runtime"],
13 | },
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/uicalc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
Задача:
11 | Создать калькулятор:
12 |
13 | поле ввода, кнопка, блок истории
14 |
15 | при вводе выражения и нажатии на ввод (или нажатии на кнопку)
16 | выражение вычисляется
17 |
18 | выражение и результат добавляются в блок истории ниже
19 | значение поля формы очищается
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/uicalc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uicalculator",
3 | "version": "1.0.0",
4 | "description": "JavaScript example starter project",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html",
9 | "test": "jest"
10 | },
11 | "dependencies": {},
12 | "devDependencies": {
13 | "@babel/core": "^7.12.3",
14 | "@babel/plugin-transform-runtime": "^7.12.1",
15 | "babel-jest": "^26.6.3",
16 | "jest": "^26.6.3",
17 | "parcel-bundler": "^1.6.1"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "starter"
22 | ],
23 | "jest": {
24 | "transform": {
25 | "\\.[jt]sx?$": "babel-jest"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lessons/lesson05/code/uicalc/src/index.js:
--------------------------------------------------------------------------------
1 | document.querySelector("#app").innerHTML = "Hola, OTUS";
2 |
--------------------------------------------------------------------------------
/lessons/lesson05/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Продолжаем тренировку насмотренности и базовые операции. Большая часть задач не требует знаний алгоритмов, достаточно просто сформулировать последовательность шагов/действий. Вы тренируетесь писать функции, продолжаете знакомство с проверкой кода тестами. Продолжая аналогию с изучением иностранных языков - вы пишете прописи, тренируете руковой водить прямые линии и скругления. Нам здесь важен факт того, что вы прорешали нужное число задач (если бы могли дать вам задание решить одну задачу 20 способами - это задание было бы здесь, но увы). И т.к. это прописи - мы этот код не проверяем (нам важен факт написания кода, и это мы видим в вашем профиле), но при желании, вы можете прислать задачи на разбор.
3 | ---
4 |
5 | ### "Практика кодирования"
6 |
7 | 1. Зарегистрировать аккаунт на сайте [codewars.com](https://www.codewars.com/) (это можно сделать с помощью github аккаунта) - логин должен быть такой же как в вашем github профиле, имя и фамилия такие же как в личном кабинете ОТУС
8 | 2. Решить 16 задач из списка ниже (вместе с прошлыми должно выйти 35)
9 | 3. Убедиться, что в вашем профиле видно когда, сколько и какие задачи вы решили
10 | 4. Ссылку на ваш профиль codewars сбросить в чат по дз
11 |
12 | Задачи для решения:
13 |
14 | **Array**
15 | 7 kyu https://www.codewars.com/kata/find-the-capitals
16 | 7 kyu https://www.codewars.com/kata/shortest-word
17 | 7 kyu https://www.codewars.com/kata/square-every-digit
18 | 7 kyu https://www.codewars.com/kata/playing-with-sets-intersection
19 | 7 kyu https://www.codewars.com/kata/divide-and-conquer
20 | 6 kyu https://www.codewars.com/kata/find-the-odd-int/
21 | 6 kyu https://www.codewars.com/kata/find-the-parity-outlier
22 | 6 kyu https://www.codewars.com/kata/zipwith
23 |
24 | **String**
25 | 6 kyu https://www.codewars.com/kata/duplicate-encoder
26 |
27 | **Number**
28 | 6 kyu https://www.codewars.com/kata/n-th-fibonacci
29 |
30 | **Date**
31 | 7 kyu https://www.codewars.com/kata/it-is-written-in-the-stars
32 | 6 kyu https://www.codewars.com/kata/5a0d6d8c6975982b5b000383
33 |
34 | **Object**
35 | 7 kyu https://www.codewars.com/kata/57eb936de1051801d500008a
36 | 7 kyu https://www.codewars.com/kata/who-is-the-killer-1
37 |
38 | **Regular expression**
39 | 8 kyu https://www.codewars.com/kata/simple-validation-of-a-username-with-regex
40 | 6 kyu https://www.codewars.com/kata/validate-my-password
41 |
42 | Задание принято, если решено **16 задач** из списка с момента старта курса
43 |
--------------------------------------------------------------------------------
/lessons/lesson05/homework2.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Продолжаем тренировку насмотренности и базовые операции. Большая часть задач не требует знаний алгоритмов, достаточно просто сформулировать последовательность шагов/действий. Вы тренируетесь писать функции, продолжаете знакомство с проверкой кода тестами. Продолжая аналогию с изучением иностранных языков - вы пишете прописи, тренируете руковой водить прямые линии и скругления. Нам здесь важен факт того, что вы прорешали нужное число задач (если бы могли дать вам задание решить одну задачу 20 способами - это задание было бы здесь, но увы). И т.к. это прописи - мы этот код не проверяем (нам важен факт написания кода, и это мы видим в вашем профиле), но при желании, вы можете прислать задачи на разбор.
3 | ---
4 |
5 | ### "Практика кодирования"
6 |
7 | 1. Зарегистрировать аккаунт на сайте [codewars.com](https://www.codewars.com/) (это можно сделать с помощью github аккаунта) - логин должен быть такой же как в вашем github профиле, имя и фамилия такие же как в личном кабинете ОТУС
8 | 2. Решить 5 задач из списка ниже (вместе с прошлыми должно выйти 40)
9 | 3. Убедиться, что в вашем профиле видно когда, сколько и какие задачи вы решили
10 | 4. Ссылку на ваш профиль codewars сбросить в чат по дз
11 |
12 | Задачи для решения:
13 |
14 | https://www.codewars.com/kata/568f9d15bb0d0bf2a8000009
15 | https://www.codewars.com/kata/56eff1e64794404a720002d2
16 | https://www.codewars.com/kata/56d931ecc443d475d5000003
17 | https://www.codewars.com/kata/56d9c274c550b4a5c2000d92
18 | https://www.codewars.com/kata/5507309481b8bd3b7e001638
19 |
20 | ### Критерии принятия
21 |
22 | - студент сбросил в чат профиль со своими именем и фамилией
23 | - в профиле решены требуемые задачи
24 |
--------------------------------------------------------------------------------
/lessons/lesson06/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Мы уходим из песочниц и начинам работать в условиях умеренно приближенным к реальным. Тут вы настраиваете ваше окружение (гит, редактор, тесты и линтеры), на практике создаете репозиторий и пуллреквест, получаете опыт код ревью (когда ваши задачи начинают проверяться преподавателями). По схеме из этого задания мы будем работать остаток курса.
3 | ---
4 |
5 | ### "Закрепление базового синтаксиса языка"
6 |
7 | Вам нужно будет:
8 |
9 | - создать репозиторий на гитхабе
10 | - инициировать проект в репозитории
11 | - решить предложенные в подготовительном курсе задачи (разместив код и тесты в директории `src`)
12 | - покрыть решение задач автоматическими тестами
13 | - сделать коммит (а лучше несколько - по одному на задание)
14 | - открыть пуллреквест
15 | - прислать ссылку на пуллревест в чат с преподавателем"
16 |
17 | #### Критерии оценки:
18 |
19 | - создан репозиторий на гитхабе - **1**
20 | - создан npm-проект - **1**
21 | - решены задания - **4**
22 | - сделан пуллреквест - **2**
23 | - настроен хаски и линтеры **\***
24 |
25 |
26 |
27 | #### Задание считается принятым при 7 баллах
28 |
--------------------------------------------------------------------------------
/lessons/lesson06/images/git_show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson06/images/git_show.png
--------------------------------------------------------------------------------
/lessons/lesson07/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Это подготовительное задание. Оно является подводящим к другому заданию в курсе (чтобы на вас не валалилось слишком много работы за раз). Кроме того вы еще раз потренируетесь настраивать репозиторий и автоматизации работы с ним. Заодно научитесь публиковать свой код так, чтобы его работу можно было показывать другим людям, далеким от программирования (маме/бабушке/младшему брату).
3 | ---
4 |
5 | Вам нужно создать новый репозиторий. В нем должна быть папка `src` с файлом `index.html` и необходимыми файлами стилей.
6 |
7 | В проекте нужно настроить линтеры (в тч на html), workflow (для проверки кода на merge request, добавления ссылки на codesandbox и деплоя на github pages).
8 |
9 | Страница должна содержать семантическую разметку [страницы прогноза погоды](https://excalidraw.com/#json=06TPrfLyQELM46t69GTmw,CjN_5d1N9v9n5ANydpS64A) (только разметку, фукнционал делать не нужно).
10 |
11 | В качестве домашнего задания сдаются:
12 |
13 | - ссылка на Pull Request с выполненным заданием
14 | - ссылка на задеплоенную страницу на Github Pages (где можно увидеть результат)
15 |
16 | Критерии сдачи:
17 |
18 | - создан npm проект
19 | - создана страница с разметкой и стилями
20 | - настроены prettier (для html и css) и eslint
21 | - настроены pre-commit хуки
22 | - настроены workflow для запуска линтеров в Github Actions (и проверки проходят)
23 | - настроен workflow для публикации страницы на Github Pages
24 | - страница опубликована на Github Pages
25 |
--------------------------------------------------------------------------------
/lessons/lesson10/images/callbackhell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson10/images/callbackhell.png
--------------------------------------------------------------------------------
/lessons/lesson13/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: В этом задании вы примените знания к проекту, где почти не будет JS (такое тоже бывает), и попробуете еще один способ публикации ваших страниц с Github
3 | ---
4 |
5 | Вам нужно:
6 |
7 | - Создать новый github репозиторий
8 | - подключить к нему линтеры и actions разобранные ранее
9 | - сверстать сайт из минимум 3х страниц с использованием семантической разметки
10 | - 2 страницы сайта блоговой структуры (список статей и отдельная статья)
11 | - 1 страница для онлайн чата (пользователь может указать свое имя, ввести текст сообщения и видеть список сообщений от других пользователей в списке со скролом) - только разметка, без функционала.
12 | - сделать пуллреквест для отображения изменений
13 | - к описанию пуллреквеста приложить ссылку на просмотр страницы (с использованием [rawgithack](https://raw.githack.com/))
14 |
15 | ### Критерии принятия
16 |
17 | - создан новый репозиторий
18 | - в открытом Pull Request видна вся выполненная работа
19 | - настроены линтеры и github actions
20 | - сверстаны нужные страницы, структура страниц соотвествует требованиям
21 | - нет серьезных замечаний по реализации (как верстки, как и настройке проекта)
22 | - к Pull Request приложена сслыка (ссылки) на превью через githack
23 |
--------------------------------------------------------------------------------
/lessons/lesson19/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Это подводящее занятие, чтобы облегчить вам работу далее
3 | ---
4 |
5 | Необходимо
6 |
7 | - создать новый репозиторий
8 | - настроить проект на работу с webpack, typescript и jest
9 | - написать утилитную функцию на TS, которая может рассчитывать следующее состояние поля в [игре Жизнь](https://ru.wikipedia.org/wiki/%D0%98%D0%B3%D1%80%D0%B0_%C2%AB%D0%96%D0%B8%D0%B7%D0%BD%D1%8C%C2%BB)
10 | - покрыть тестами (не менее 80%)
11 | - добавить линтеры и workflow для проверок (в тч проверку на минимальное покрытие)
12 | - PR с кодом отправить в ЛК
13 |
14 | ### Критерии принятия
15 |
16 | - Настроен новый репозиторий (линтеры, автоматизации)
17 | - сделан PR отражающий сделанные изменения
18 | - реализована функция `getNextGeneration()`
19 | - код описан типами Typescript и покрыт тестами
20 | - покрытие кода не менее 80% (установлено требование через coverageThreshold и тесты на CI проходят эту проверку)
21 | - нет серьезных нареканий к реализации
22 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vanilla",
3 | "version": "1.0.0",
4 | "description": "JavaScript example starter project",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": [
16 | "javascript",
17 | "starter"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/src/addForm.js:
--------------------------------------------------------------------------------
1 | /**
2 | На странице должны быть три текстовых
3 | параграфа, поле ввода и кнопка. Напишите скрипт,
4 | который будет выполнять следующие условия:
5 | 1.Кнопка скрыта, если в поле ввода нет значения.
6 | 2.При клике на кнопку добавляется новый параграф,
7 | содержащий текст из поля ввода.
8 | 3.*Если параграфов становится больше 4, первый из
9 | них удаляется.
*/
10 |
11 | /**
12 | * @param {HTMLElement} el
13 | */
14 | export function addForm(el) {
15 | //el = document.createElement('div');
16 | let input = document.createElement("input");
17 | let button = document.createElement("button");
18 | button.innerHTML = "add";
19 | button.addEventListener("click", () => {
20 | let p = document.createElement("p");
21 | el.appendChild(p);
22 | p.innerHTML = input.value;
23 | input.value = "";
24 | el.removeChild(button);
25 | if (el.querySelectorAll("p").length > 4) {
26 | el.removeChild(el.querySelectorAll("p")[0]);
27 | }
28 | });
29 |
30 | el.appendChild(document.createElement("p"));
31 | el.appendChild(document.createElement("p"));
32 | el.appendChild(document.createElement("p"));
33 | el.appendChild(input);
34 |
35 | input.addEventListener("keyup", () => {
36 | if (input.value !== "") {
37 | el.appendChild(button);
38 | } else {
39 | el.removeChild(button);
40 | }
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/src/addForm.test.js:
--------------------------------------------------------------------------------
1 | import { addForm } from "./addForm";
2 |
3 | describe("addForm", () => {
4 | let el;
5 | beforeEach(() => {
6 | el = document.createElement("div");
7 | });
8 | it("creates basic markup", () => {
9 | addForm(el);
10 |
11 | expect(el.querySelector("input")).not.toBe(null);
12 | expect(el.querySelectorAll("p").length).toBe(3);
13 | });
14 |
15 | it("shows button if value is not empty", () => {
16 | addForm(el);
17 |
18 | const input = el.querySelector("input");
19 | input.value = "123";
20 | input.dispatchEvent(new window.Event("keyup"));
21 | expect(el.querySelector("button")).not.toBe(null);
22 | expect(el.querySelector("button").innerHTML).toEqual("add");
23 |
24 | input.value = "";
25 | input.dispatchEvent(new window.Event("keyup"));
26 | expect(el.querySelector("button")).toBe(null);
27 | });
28 |
29 | it("adds new p on click by button", () => {
30 | addForm(el);
31 | const input = el.querySelector("input");
32 | const text = Math.random() + "";
33 | input.value = text;
34 | input.dispatchEvent(new window.Event("keyup"));
35 | const button = el.querySelector("button");
36 |
37 | button.dispatchEvent(new window.Event("click"));
38 | expect(input.value).toBe("");
39 | expect(el.querySelectorAll("p").length).toBe(4);
40 | expect(el.querySelectorAll("p")[3].innerHTML).toBe(text);
41 | });
42 |
43 | it("has limited number of paragraphs", () => {
44 | addForm(el);
45 | const input = el.querySelector("input");
46 |
47 | for (let i = 0; i < 5; i++) {
48 | const text = Math.random() + "";
49 | input.value = text;
50 | input.dispatchEvent(new window.Event("keyup"));
51 | const button = el.querySelector("button");
52 | button.dispatchEvent(new window.Event("click"));
53 |
54 | if (i > 1) {
55 | expect(el.querySelectorAll("p").length).toBe(4);
56 | expect(el.querySelectorAll("p")[3].innerHTML).toBe(text);
57 | }
58 | }
59 | });
60 |
61 | it("hides button on adding paragraph", () => {
62 | addForm(el);
63 | const input = el.querySelector("input");
64 | const text = Math.random() + "";
65 | input.value = text;
66 | input.dispatchEvent(new window.Event("keyup"));
67 | const button = el.querySelector("button");
68 |
69 | button.dispatchEvent(new window.Event("click"));
70 | expect(el.querySelector("button")).toBe(null);
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/src/index.js:
--------------------------------------------------------------------------------
1 | import { addForm } from "./addForm";
2 |
3 | addForm(document.querySelector("#app"));
4 |
--------------------------------------------------------------------------------
/lessons/lesson20/code/notes-list/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 |
--------------------------------------------------------------------------------
/lessons/lesson20/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Это второй "проект" курса. Напишите небольшую игру, для тренировки и закрепления навыка работы с Typescript, заодно потренируете базовые операции по работе с массивами.
3 | ---
4 |
5 | Необходимо
6 |
7 | - создать новый репозиторий
8 | - инициализировать его с файлом .gitignore
9 | - создать новую ветку (чтобы можно было создать PR)
10 | - настроить линтинг и actions, настроить автодеплой из PR
11 | - сконфигурировать webpack
12 | - добавить поддержку ts файлов
13 | - добавить поддержку импорта css файлов
14 | - реализовать приложение ""Игра Жизнь"" на языке Typescript https://ru.wikipedia.org/wiki/%D0%98%D0%B3%D1%80%D0%B0_%C2%AB%D0%96%D0%B8%D0%B7%D0%BD%D1%8C%C2%BB
15 | - ссылку на задеплоенную страницу и на пуллреквест сбросить в чат по дз
16 | - настроить jest и написать тесты на приложение
17 |
18 | ### Критерии принятия
19 |
20 | - Выполняются стандартные требования (PR, линтеры, автоматизации)
21 | - Игра жизнь реализует стандартный функционал + изменение скорости и размера поля
22 | - Нет серьезных нареканий по реализации
23 | - Код покрыт тестами (с покрытием не ниже 60%)
24 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 | Game of Life
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "game-of-life-ts-oopblank",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "test": "jest",
8 | "start": "parcel index.html --open",
9 | "build": "parcel build index.html"
10 | },
11 | "dependencies": {
12 | "parcel-bundler": "^1.6.1"
13 | },
14 | "devDependencies": {
15 | "@babel/core": "7.2.0"
16 | },
17 | "resolutions": {
18 | "@babel/preset-env": "7.13.8"
19 | },
20 | "keywords": []
21 | }
22 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/Game.ts:
--------------------------------------------------------------------------------
1 | import { GameField, IGameField } from "./GameField";
2 | import { GameView, IGameView } from "./GameView";
3 | import { Cell } from "./types/Cell";
4 |
5 | export interface IGame {}
6 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/GameField.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from "./types/Cell";
2 |
3 | export interface IGameField {
4 | getState(): Cell[][];
5 | toggleCellState(x: number, y: number);
6 | nextGeneration();
7 | setSize(width: number, height: number);
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/GameView.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from "./types/Cell";
2 |
3 | export interface IGameView {
4 | updateGameField(field: Cell[][]);
5 | updateGameState(state: {
6 | width?: number;
7 | height?: number;
8 | isRunning?: boolean;
9 | });
10 | onCellClick(cb: (x: number, y: number) => void);
11 | onGameStateChange(cb: (newState: boolean) => void);
12 | onFieldSizeChange(cb: (width: number, height: number) => void);
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/index.ts:
--------------------------------------------------------------------------------
1 | // import { Game } from "./Game";
2 | // import { GameField } from "./GameField";
3 | // import { GameView } from "./GameView";
4 | // import "./styles.css";
5 |
6 | // const el = document.getElementById("app") as HTMLElement;
7 |
8 | // const gameView = new GameView(el);
9 | // const gameField = new GameField(5, 5);
10 | // new Game(gameField, gameView, 1000);
11 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 |
5 | table {
6 | border-collapse: collapse;
7 | margin: 30px;
8 | }
9 |
10 | .cell {
11 | border: 1px solid lightblue;
12 | color: lightgray;
13 | width: 20px;
14 | height: 20px;
15 | text-align: center;
16 | }
17 |
18 | .cell--alive {
19 | background-color: darkcyan;
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson21/code/src/types/Cell.ts:
--------------------------------------------------------------------------------
1 | export type Cell = number;
2 |
--------------------------------------------------------------------------------
/lessons/lesson21/images/slicing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson21/images/slicing.png
--------------------------------------------------------------------------------
/lessons/lesson22/code/compose/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
11 | Напишите функцию compose, возвращающую функцию,
12 | при вызове которой к аргументу возвращенной функции
13 | применяются все переданный в compose функции-аргументы
14 |
15 |
17 |
18 |
22 | reduce
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/compose/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "otus-fizzbuzz",
3 | "version": "1.0.0",
4 | "description": "Basic fizzBuzz practice sandbox",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": [
16 | "otus"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/compose/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": false,
3 | "hardReloadOnChange": true,
4 | "view": "browser"
5 | }
6 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/compose/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Напишите функцию compose, возвращающую функцию,
3 | * при вызове которой к аргументу возвращенной функции
4 | * применяются все переданный в compose функции-аргументы
5 | */
6 |
7 | export const compose = (...args) => {};
8 |
9 | // // Пример вызова
10 | // const upperCase = str => str.toUpperCase();
11 | // const exclaim = str => `${str}!`;
12 | // const repeat = str => `${str} `.repeat(3);
13 |
14 | // const withСompose = compose(
15 | // upperCase,
16 | // exclaim,
17 | // repeat,
18 | // );
19 |
20 | // console.log(withСompose("I love coding")); // I LOVE CODING! I LOVE CODING! I LOVE CODING!
21 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/compose/src/index.test.js:
--------------------------------------------------------------------------------
1 | import { compose } from "./index.js";
2 |
3 | describe("compose", () => {
4 | const upperCase = jest.fn((val) => val.toUpperCase());
5 | const exclaim = jest.fn((val) => `${val}!`);
6 | const repeat = jest.fn((val) => val.repeat(3));
7 | it("is a function", () => expect(typeof compose).toBe("function"));
8 | const cb1 = compose(upperCase);
9 | it("should return function", () => expect(typeof cb1).toBe("function"));
10 |
11 | it("compose callbacks", () => {
12 | expect(cb1("hello")).toBe("HELLO");
13 | const cb2 = compose(cb1, exclaim, repeat);
14 | expect(cb2("hello")).toBe("HELLO!HELLO!HELLO!");
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Parcel Sandbox
5 |
6 |
7 |
8 |
9 |
10 |
11 | Напишите функци-обертку memo, которая сохраняет результаты
12 | выполнения функций для предотвращения повторных вычислений
13 |
14 |
16 |
17 |
21 | Краткие заметки в помощь Замыкания
22 |
23 |
27 | Остаточные параметры
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "otus-fizzbuzz",
3 | "version": "1.0.0",
4 | "description": "Basic fizzBuzz practice sandbox",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "@babel/core": "7.2.0",
13 | "parcel-bundler": "^1.6.1"
14 | },
15 | "keywords": [
16 | "otus"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": false,
3 | "hardReloadOnChange": true,
4 | "view": "browser"
5 | }
6 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/src/..js:
--------------------------------------------------------------------------------
1 | export function getCorrectCallsArgs() {
2 | return [
3 | [1],
4 | [2],
5 | ["Fizz"],
6 | [4],
7 | ["Buzz"],
8 | ["Fizz"],
9 | [7],
10 | [8],
11 | ["Fizz"],
12 | ["Buzz"],
13 | [11],
14 | ["Fizz"],
15 | [13],
16 | [14],
17 | ["FizzBuzz"],
18 | [16],
19 | [17],
20 | ["Fizz"],
21 | [19],
22 | ["Buzz"],
23 | ["Fizz"],
24 | [22],
25 | [23],
26 | ["Fizz"],
27 | ["Buzz"],
28 | [26],
29 | ["Fizz"],
30 | [28],
31 | [29],
32 | ["FizzBuzz"],
33 | [31],
34 | [32],
35 | ["Fizz"],
36 | [34],
37 | ["Buzz"],
38 | ["Fizz"],
39 | [37],
40 | [38],
41 | ["Fizz"],
42 | ["Buzz"],
43 | [41],
44 | ["Fizz"],
45 | [43],
46 | [44],
47 | ["FizzBuzz"],
48 | [46],
49 | [47],
50 | ["Fizz"],
51 | [49],
52 | ["Buzz"],
53 | ["Fizz"],
54 | [52],
55 | [53],
56 | ["Fizz"],
57 | ["Buzz"],
58 | [56],
59 | ["Fizz"],
60 | [58],
61 | [59],
62 | ["FizzBuzz"],
63 | [61],
64 | [62],
65 | ["Fizz"],
66 | [64],
67 | ["Buzz"],
68 | ["Fizz"],
69 | [67],
70 | [68],
71 | ["Fizz"],
72 | ["Buzz"],
73 | [71],
74 | ["Fizz"],
75 | [73],
76 | [74],
77 | ["FizzBuzz"],
78 | [76],
79 | [77],
80 | ["Fizz"],
81 | [79],
82 | ["Buzz"],
83 | ["Fizz"],
84 | [82],
85 | [83],
86 | ["Fizz"],
87 | ["Buzz"],
88 | [86],
89 | ["Fizz"],
90 | [88],
91 | [89],
92 | ["FizzBuzz"],
93 | [91],
94 | [92],
95 | ["Fizz"],
96 | [94],
97 | ["Buzz"],
98 | ["Fizz"],
99 | [97],
100 | [98],
101 | ["Fizz"],
102 | ["Buzz"],
103 | ];
104 | }
105 |
106 | export function getNormalizedSource(fn) {
107 | return Function.prototype.toString
108 | .apply(fn)
109 | .replace(/\s+/g, " ")
110 | .replace(/(\w)\s+\(/g, "$1(")
111 | .replace(/(\w)\s+{/g, "$1{")
112 | .replace(/}\s+(\w)/g, "}$1");
113 | }
114 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Напишите функци-обертку memo, которая сохраняет результаты
3 | * выполнения функций для предотвращения повторных вычислений
4 | * Заметка в помощь https://developer.mozilla.org/ru/docs/Web/JavaScript/Closures
5 | */
6 | export function memo(cb) {
7 | // напишите ваш код здесь...
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson22/code/memo/src/index.test.js:
--------------------------------------------------------------------------------
1 | import { memo } from "./index.js";
2 |
3 | describe("memo", () => {
4 | const mock = jest.fn().mockReturnValue(true);
5 | it("is a function", () => expect(typeof memo).toBe("function"));
6 | const cb = memo(mock);
7 | it("should return function", () => expect(typeof cb).toBe("function"));
8 | it("call cb with same arguments only once", () => {
9 | cb(5, 6); //once
10 | cb(5, 6); //still once
11 | expect(mock).toHaveBeenCalledTimes(1);
12 | cb(5, 7); //twice
13 | cb(5, 8); //thrice
14 | expect(mock).toHaveBeenCalledTimes(3);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/lessons/lesson22/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Это подводящее задание, которое нужно, чтобы в будущем вам не пришлось делать слишком большой объем работы (то, что вы сделаете, будет вами же использоваться позже). Тут вы получаете первый опыт проектирования интерфейса вашего API.
3 | ---
4 |
5 | **Задание:**
6 |
7 | Реализовать набор функций, для приложения учета расходов. По возможности вынести все side-effects в отдельный набор функций (и минимизировать набор таких функций).
8 |
9 | **Критерии приемки:**
10 |
11 | - продумана структура хранения данных, которая позволяет хранить дату, сумму, категорию и подкатегорию, комментарий
12 | - функция, для построение статистики (расход по категориям из диапозона дат, расход по датам из диапозона, сумма по элементам отфильтрованным поиском)
13 | - сортировка категорий по сумме за указанный диапозон дат
14 | - код оформлен в виде PR (github actions, codesandbox link, linter)
15 | - код покрыт тестами
16 |
--------------------------------------------------------------------------------
/lessons/lesson24/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Задание на практическую отработку навыков по созданию подключаемых плагинов. Не факт, что в будущем вы будете такое делать, но после выполнения задания вы сможете лучше понимать как работают подключаемые плагины от сторонних разработчиков (а вот с ними вам почти наверняка придется сталкиваться). Заодно вы тренируете решение типовой UI задачи.
3 | ---
4 |
5 | **Необходимо**
6 |
7 | - в репозитории прошлого проекта создать новую ветку
8 | - на главную страницу добавить разметку для слайдера ul > li > img, добавить на корневой ul элемент класс для виджета карусели
9 | - добавить файл js и написать в нем код, который вместо отображения списка картинок позволит работать с каруселью ( работать должно как https://getbootstrap.com/docs/4.0/components/carousel/#with-controls )
10 | - проверить работу карусели на мобильном браузере
11 | - сделать пуллреквест
12 | - ссылку на пуллреквест и на задеплоенную страницу сбросить в чат по дз
13 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 | ×
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vanilla-typescript",
3 | "version": "1.0.0",
4 | "description": "JavaScript and TypeScript example starter project",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "parcel-bundler": "^1.6.1"
13 | },
14 | "keywords": [
15 | "typescript",
16 | "javascript"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": false,
3 | "hardReloadOnChange": true,
4 | "view": "browser"
5 | }
6 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/ToDoListController.ts:
--------------------------------------------------------------------------------
1 | import { ToDoListModel } from "./ToDoListModel";
2 | import { ToDoListView } from "./ToDoListView";
3 |
4 | export class ToDoListController {
5 | constructor(private model: ToDoListModel, private view: ToDoListView) {
6 | this.view.setDeleteHanlder(this.handleDelete);
7 | this.view.setAddHandler((value: string) => {
8 | this.model.add(value);
9 | this.render();
10 | });
11 | this.render();
12 | }
13 | private handleDelete = (event: any) => {
14 | this.model.delete(event.target.parentNode.id);
15 | this.render();
16 | };
17 | private render() {
18 | this.model.getList().then((data) => this.view.render(data));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/ToDoListModel.ts:
--------------------------------------------------------------------------------
1 | export class ToDoListModel {
2 | private list: string[] = ["one", "two", "three"];
3 | async delete(index: string) {
4 | this.list.splice(+index, 1);
5 | }
6 | async add(item: string) {
7 | this.list.push(item);
8 | }
9 | async getList() {
10 | return this.list;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/ToDoListView.ts:
--------------------------------------------------------------------------------
1 | import { List } from "./components/List";
2 | import { Form } from "./components/Form";
3 |
4 | type Props = {
5 | ul: HTMLUListElement;
6 | textInput: HTMLInputElement;
7 | submitButton: HTMLButtonElement;
8 | };
9 | export class ToDoListView {
10 | private list: List;
11 | private form: Form;
12 | constructor({ ul, textInput, submitButton }: Props) {
13 | this.list = new List(ul);
14 | this.form = new Form(textInput, submitButton);
15 | }
16 |
17 | setDeleteHanlder(cb: (event: any) => void) {
18 | this.list.setDeleteHanlder(cb);
19 | }
20 |
21 | setAddHandler(cb: (value: string) => void) {
22 | this.form.setAddHandler(cb);
23 | }
24 |
25 | render(data: string[]) {
26 | this.list.render(data);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/components/Form.ts:
--------------------------------------------------------------------------------
1 | export class Form {
2 | constructor(
3 | private textInput: HTMLInputElement,
4 | private submitButton: HTMLButtonElement,
5 | private handleAdd = () => {}
6 | ) {}
7 | static textInput = document.querySelector(".form__input");
8 | static submitButton = document.querySelector(".form__submit");
9 | setAddHandler(cb: (value: string) => void) {
10 | this.handleAdd = () => {
11 | cb(this.textInput.value);
12 | this.textInput.value = "";
13 | };
14 | this.submitButton.addEventListener("click", this.handleAdd);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/components/List.ts:
--------------------------------------------------------------------------------
1 | import { ListItem } from "./ListItem";
2 |
3 | export class List {
4 | constructor(
5 | private itemsList: HTMLUListElement,
6 | private handleDelete = (event: any) => {}
7 | ) {}
8 | static itemsList = document.querySelector(".items");
9 | setDeleteHanlder(cb: (event: any) => void) {
10 | this.handleDelete = cb;
11 | this.handleDelete = this.handleDelete.bind(this);
12 | }
13 | render(list: string[]) {
14 | this.itemsList.innerHTML = "";
15 | list.forEach((text, index) => {
16 | const listItem = new ListItem({
17 | text,
18 | index: index + "",
19 | deleteHandler: this.handleDelete,
20 | });
21 | this.itemsList.append(listItem.getElement());
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/components/ListItem.ts:
--------------------------------------------------------------------------------
1 | type Props = {
2 | text: string;
3 | index: string;
4 | deleteHandler: (e: Event) => void;
5 | };
6 |
7 | export class ListItem {
8 | private element: HTMLUListElement;
9 | static itemTemplate: HTMLTemplateElement = document.querySelector(
10 | ".item_template"
11 | ) as HTMLTemplateElement;
12 | constructor({ text, index, deleteHandler }: Props) {
13 | this.element = ListItem.itemTemplate.content.cloneNode(
14 | true
15 | ) as HTMLUListElement;
16 | (this.element.querySelector(".item__text") as HTMLElement).innerText = text;
17 | (this.element.querySelector(".item") as HTMLElement).setAttribute(
18 | "id",
19 | index
20 | );
21 | (
22 | this.element.querySelector(".item__delete") as HTMLElement
23 | ).addEventListener("click", deleteHandler);
24 | }
25 | getElement(): HTMLUListElement {
26 | return this.element;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ToDoListController } from "./ToDoListController";
2 | import { ToDoListView } from "./ToDoListView";
3 | import { ToDoListModel } from "./ToDoListModel";
4 | const textInput = document.querySelector(".form__input") as HTMLInputElement;
5 | const submitButton = document.querySelector(
6 | ".form__submit"
7 | ) as HTMLButtonElement;
8 | const ul = document.querySelector(".items") as HTMLUListElement;
9 | const model = new ToDoListModel();
10 | const view = new ToDoListView({ ul, textInput, submitButton });
11 | const controller = new ToDoListController(model, view);
12 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/style/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | /* Remove margins and padding from the list */
6 | ul {
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | /* Style the list items */
12 | ul li {
13 | cursor: pointer;
14 | position: relative;
15 | padding: 12px 8px 12px 40px;
16 | background: #eee;
17 | font-size: 18px;
18 | transition: 0.2s;
19 |
20 | /* make the list items unselectable */
21 | -webkit-user-select: none;
22 | -moz-user-select: none;
23 | -ms-user-select: none;
24 | user-select: none;
25 | }
26 |
27 | /* Set all odd list items to a different color (zebra-stripes) */
28 | ul li:nth-child(odd) {
29 | background: #f9f9f9;
30 | }
31 |
32 | /* Darker background-color on hover */
33 | ul li:hover {
34 | background: #ddd;
35 | }
36 |
37 | /* When clicked on, add a background color and strike out text */
38 | ul li.checked {
39 | background: #888;
40 | color: #fff;
41 | text-decoration: line-through;
42 | }
43 |
44 | /* Add a "checked" mark when clicked on */
45 | ul li.checked::before {
46 | content: "";
47 | position: absolute;
48 | border-color: #fff;
49 | border-style: solid;
50 | border-width: 0 2px 2px 0;
51 | top: 10px;
52 | left: 16px;
53 | transform: rotate(45deg);
54 | height: 15px;
55 | width: 7px;
56 | }
57 |
58 | /* Style the close button */
59 | .item__delete {
60 | position: absolute;
61 | right: 0;
62 | top: 0;
63 | padding: 12px 16px 12px 16px;
64 | }
65 |
66 | .item__delete:hover {
67 | background-color: #f44336;
68 | color: white;
69 | }
70 |
71 | /* Style the header */
72 | .header {
73 | background-color: #f44336;
74 | padding: 30px 40px;
75 | color: white;
76 | text-align: center;
77 | }
78 |
79 | /* Clear floats after the header */
80 | .header:after {
81 | content: "";
82 | display: table;
83 | clear: both;
84 | }
85 |
86 | /* Style the input */
87 | input {
88 | margin: 0;
89 | border: none;
90 | border-radius: 0;
91 | width: 75%;
92 | padding: 10px;
93 | float: left;
94 | font-size: 16px;
95 | }
96 |
97 | /* Style the "Add" button */
98 | .form__submit {
99 | padding: 10px;
100 | width: 25%;
101 | background: #d9d9d9;
102 | color: #555;
103 | float: left;
104 | text-align: center;
105 | font-size: 16px;
106 | cursor: pointer;
107 | transition: 0.3s;
108 | border-radius: 0;
109 | }
110 |
111 | .form__submit:hover {
112 | background-color: #bbb;
113 | }
114 |
--------------------------------------------------------------------------------
/lessons/lesson25/code/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["es6", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson25/images/MKC.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson25/images/MKC.jpg
--------------------------------------------------------------------------------
/lessons/lesson25/images/MKC_components.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson25/images/MKC_components.jpg
--------------------------------------------------------------------------------
/lessons/lesson25/images/MVC-Process.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson25/images/MVC-Process.png
--------------------------------------------------------------------------------
/lessons/lesson25/images/ModelViewControllerDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson25/images/ModelViewControllerDiagram.png
--------------------------------------------------------------------------------
/lessons/lesson25/images/todolist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson25/images/todolist.png
--------------------------------------------------------------------------------
/lessons/lesson26/images/mrbean.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson26/images/mrbean.jpg
--------------------------------------------------------------------------------
/lessons/lesson28/ht.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Еще одно подводящее задание. И снова проектирование и реализация собственного API. Ключевой момент тут - понимание важности того, что стоит закладывать асинхронность для обеспечания гибкости.
3 | ---
4 |
5 | **Необходимо:**
6 |
7 | - в репозитории прошлого задания создать новую ветку
8 | - описать интерфейс для библиотеки для работы с календарем (закладывая работу с произвольным хранилищем)
9 |
10 | Должны поддерживаться следующие операции
11 |
12 | - CRUD
13 | - фильтрация задач (по тексту, дате, статусу, тегам)
14 |
15 | - Реализовать преложенный интерфейс с использованием в качестве хранилища (две реализации) - locasStorage - firebase
16 |
17 | ### Критерии принятия
18 |
19 | - Выполняются стандартные требования (PR, линтеры, автоматизации)
20 | - Предложен интерфейс (с описанием на Typescript), который позволяет делать CRUD и фильтрацию
21 | - Предложеный интерфейс закладывает гибкость для смены реализации
22 | - Предоставлены 2 реализации интерфейса (на базе синхронного и асинхронного хранилищ)
23 | - Нет серьезных замечаний по качеству решения
24 |
--------------------------------------------------------------------------------
/lessons/lesson28/ht2.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Мини-проект "Календарь задач" - используя разработанное АПИ нужно разработать календарь задач (аналог гугл-календаря)
3 | ---
4 |
5 | **Необходимо:**
6 |
7 | - на странице должен быть календарь
8 | - можно листать календарь (вперед/назад по месяцам/годам)
9 | - в ячейках дат видны задачи
10 | - двойной клик по заголовку даты позволяет создать новую задачу (и задать ей текст)
11 |
12 | Обязательный функционал:
13 |
14 | - создавать, редактировать, удалять задачи (заголовок, описание)
15 | - делать [fuzzy search](https://whatis.techtarget.com/definition/fuzzy-search) (можно взять [`Fuzzy search`](https://www.npmjs.com/package/fuzzy-search))
16 |
17 | Для выполнения нужно взять:
18 |
19 | - апи для хранения данных из прошлого домашнего задания
20 |
21 | Задание проверяется после:
22 |
23 | - открыт пуллреквест
24 | - написаны тесты (покрытие 60%)
25 | - сделан деплой на github pages
26 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Parcel Sandbox
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "template-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1"
12 | },
13 | "devDependencies": {},
14 | "resolutions": {
15 | "@babel/preset-env": "7.13.8"
16 | },
17 | "keywords": []
18 | }
19 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/src/index.ts:
--------------------------------------------------------------------------------
1 | import { template } from "./template";
2 |
3 | document.getElementById("app").innerHTML = template(
4 | `My name is {{NAME}}. And my friend are:
5 |
6 | {{for friends}}{{NAME}}, {{AGE}} y.o. {{endfor}}
7 |
8 | `,
9 | {
10 | NAME: "Bob",
11 | friends: [
12 | {
13 | NAME: "Sam",
14 | AGE: "21",
15 | },
16 | {
17 | NAME: "Alice",
18 | AGE: 23,
19 | },
20 | ],
21 | }
22 | );
23 |
24 | /*
25 | My name is Bob. And my friend are:
26 |
27 | Sam, 21 y.o. Alice, 23 y.o.
28 |
29 | */
30 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/src/template.test.ts:
--------------------------------------------------------------------------------
1 | import { template } from "./template";
2 |
3 | describe("template", () => {
4 | const data = {
5 | NAME: "Bob",
6 | AGE: "18",
7 | TEAM: "Core",
8 | items: [
9 | { A: 1, B: 2 },
10 | { A: 11, B: 22 },
11 | { A: 111, B: 222 },
12 | ],
13 | };
14 |
15 | describe("basic data placing", () => {
16 | it("puts data into placeholders", () => {
17 | expect(template("Hi, {{NAME}}", data)).toBe("Hi, Bob");
18 | });
19 |
20 | it("puts empty string into placeholders in no data provided", () => {
21 | expect(template("Hi, {{NAME}} {{SURNAME}}", data)).toBe("Hi, Bob ");
22 | });
23 |
24 | it("replaces all placeholders", () => {
25 | expect(template("Hi, {{NAME}}. My name is {{NAME}} too", data)).toBe(
26 | "Hi, Bob. My name is Bob too"
27 | );
28 | });
29 | });
30 |
31 | describe.skip("loops", () => {
32 | it("puts values from list elements inside loop", () => {
33 | expect(
34 | template("{{NAME}}{{for items}}{{NAME}},{{endfor}}", {
35 | NAME: "0 ",
36 | items: [{ NAME: "1" }, { NAME: "2" }],
37 | })
38 | ).toBe("0 1,2,");
39 | });
40 |
41 | it("handles basic loops", () => {
42 | expect(template("{{for items}}{{A}},{{endfor}}", data)).toBe("1,11,111,");
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/src/template.ts:
--------------------------------------------------------------------------------
1 | export function template(tpl: string, data: object): string {
2 | // put your code here
3 | return tpl;
4 | }
5 |
--------------------------------------------------------------------------------
/lessons/lesson31/code/code/templatePractice/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["es6", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Parcel Sandbox
4 |
5 |
6 |
7 |
8 | Event Emitter / Event Bus
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "event-emitter",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "@types/jest": "26.0.21",
12 | "parcel-bundler": "^1.6.1"
13 | },
14 | "devDependencies": {
15 | "typescript": "4.2.3"
16 | },
17 | "resolutions": {
18 | "@babel/preset-env": "7.13.8"
19 | },
20 | "keywords": []
21 | }
22 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/src/EventEmitter.ts:
--------------------------------------------------------------------------------
1 | export class EventEmitter {
2 | // @todo: put your code here
3 | }
4 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/src/index.ts:
--------------------------------------------------------------------------------
1 | // import { EventEmitter } from "./EventEmitter";
2 |
3 | // (document.querySelector("#app") as HTMLElement).innerHTML = `
4 | //
5 | //
6 | //
7 | // `;
8 | // const input1 = document.querySelector("input[name=input1") as HTMLInputElement;
9 | // const input2 = document.querySelector("input[name=input2") as HTMLInputElement;
10 | // const header = document.querySelector("h1") as HTMLHeadingElement;
11 |
12 | // const eventEmitter = new EventEmitter();
13 |
14 | // eventEmitter.on("changeText", (text) => (header.innerHTML = text));
15 |
16 | // input1.addEventListener("keypress", (ev) =>
17 | // eventEmitter.trigger("changeText", (ev.target as HTMLInputElement).value)
18 | // );
19 | // input2.addEventListener("keypress", (ev) =>
20 | // eventEmitter.trigger("changeText", (ev.target as HTMLInputElement).value)
21 | // );
22 |
--------------------------------------------------------------------------------
/lessons/lesson33/code/eventEmitter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["es6", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson33/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Задание нужно для лучшего осмысления подхода на основе событий
3 | ---
4 |
5 | **Задание:**
6 |
7 | В репозитории с приложением "Прогоноз погоды" сделать новую ветку и перееисать приложение, так, чтобы в нем было минимум 3 раздельных блока (форма ввода, блок с историей, отображение погоды), которые будут общаться друг с другом посредством EventBus (который нужно реализовать самостоятельно)
8 |
9 | **Критерии приемки задания:**
10 |
11 | - в приложении выделено минимум 3 раздельных блока (форма ввода, блок с историей, отображение погоды)
12 | - реализован EventBus
13 | - блоки общаются между собой посредством EventBus
14 | - покрытие кода от 60%
15 | - как обычно линтеры, workflow, деплой
16 |
--------------------------------------------------------------------------------
/lessons/lesson33/images/EventBus.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson33/images/EventBus.jpeg
--------------------------------------------------------------------------------
/lessons/lesson33/images/ObservableUML.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson33/images/ObservableUML.png
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Parcel Sandbox
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "combinereducers",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1"
12 | },
13 | "devDependencies": {
14 | "typescript": "4.2.3"
15 | },
16 | "resolutions": {
17 | "@babel/preset-env": "7.13.8"
18 | },
19 | "keywords": []
20 | }
21 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/src/combineReducers.test.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "./combineReducers";
2 |
3 | describe("combineReducers", () => {
4 | it("is a function", () => {
5 | expect(combineReducers).toBeInstanceOf(Function);
6 | });
7 |
8 | it("returns a function", () => {
9 | expect(combineReducers()).toBeInstanceOf(Function);
10 | });
11 |
12 | it("returns a reducer based on the config (initial state)", () => {
13 | const reducer = combineReducers({
14 | a: (state = 2, action) => state,
15 | b: (state = "hop", action) => state,
16 | });
17 | expect(reducer(undefined, { type: "unknown" })).toEqual({
18 | a: 2,
19 | b: "hop",
20 | });
21 | });
22 |
23 | it("calls subreducers with proper values", () => {
24 | type State = { a: number; b: number };
25 | const config = {
26 | a: jest.fn((state: number = 5, action) => state + action.payload),
27 | b: jest.fn((state: number = 6, action) => state - action.payload),
28 | };
29 | const reducer = combineReducers(config);
30 |
31 | const state: State = {
32 | a: 55,
33 | b: 66,
34 | };
35 | const action1 = { payload: 1 };
36 | const newState1 = reducer(state, { payload: 1 });
37 |
38 | expect(config.a).toHaveBeenCalledWith(55, action1);
39 | expect(config.b).toHaveBeenCalledWith(66, action1);
40 |
41 | expect(newState1).toEqual({
42 | a: 56,
43 | b: 65,
44 | });
45 |
46 | const action2 = { payload: 2 };
47 | const newState2 = reducer(newState1, action2);
48 | expect(config.a).toHaveBeenCalledWith(56, action2);
49 | expect(config.b).toHaveBeenCalledWith(65, action2);
50 | expect(newState2).toEqual({
51 | a: 58,
52 | b: 63,
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/src/combineReducers.ts:
--------------------------------------------------------------------------------
1 | // put your code here
2 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/src/index.ts:
--------------------------------------------------------------------------------
1 | document.getElementById("app").innerHTML = "combineReducers";
2 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/combineReducers/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["ES2020", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Parcel Sandbox
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-basic",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html",
9 | "test": "jest"
10 | },
11 | "dependencies": {
12 | "parcel-bundler": "^1.6.1"
13 | },
14 | "devDependencies": {
15 | "@babel/core": "^7.13.10",
16 | "@babel/preset-env": "^7.13.12",
17 | "@babel/preset-typescript": "^7.13.0",
18 | "@types/jest": "^26.0.21",
19 | "babel-jest": "^26.6.3",
20 | "jest": "^26.6.3",
21 | "typescript": "4.2.3"
22 | },
23 | "resolutions": {
24 | "@babel/preset-env": "7.13.8"
25 | },
26 | "keywords": []
27 | }
28 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/src/configureStore.ts:
--------------------------------------------------------------------------------
1 | export type Store = {
2 | getState(): State;
3 | dispatch(action: Action): any;
4 | subscribe(cb: () => void): () => void;
5 | };
6 |
7 | export type Reducer = (
8 | state: State | undefined,
9 | action: Action
10 | ) => State;
11 |
12 | export type Middleware = (
13 | store: Store
14 | ) => (next: (action: Action) => any) => (action: Action) => any;
15 |
16 | export type ConfigureStore = (
17 | reducer: Reducer,
18 | initialState?: State | undefined,
19 | middlewares?: Middleware[]
20 | ) => Store;
21 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/src/index.ts:
--------------------------------------------------------------------------------
1 | // import { configureStore, Reducer } from './configureStore';
2 |
3 | (document.getElementById("app") as HTMLElement).innerHTML = `Redux`;
4 |
5 | // type State = {
6 | // counter: number
7 | // };
8 |
9 | // type Action = {
10 | // type: 'inc'
11 | // } | {
12 | // type: 'dec'
13 | // } | {
14 | // type: 'plus',
15 | // payload: number
16 | // }
17 |
18 | // const reducer: Reducer = (state = { counter: 5}, action)=> {
19 | // switch(action.type) {
20 | // case('inc'): {
21 | // return {
22 | // ...state,
23 | // counter: state.counter + 1
24 | // }
25 | // }
26 | // case('dec'): {
27 | // return {
28 | // ...state,
29 | // counter: state.counter - 1
30 | // }
31 | // }
32 | // case('plus'): {
33 | // return {
34 | // ...state,
35 | // counter: state.counter + action.payload
36 | // }
37 | // }
38 | // }
39 | // return state;
40 | // }
41 |
42 | // const store = configureStore(reducer);
43 |
44 | // (document.getElementById("app") as HTMLElement).innerHTML = `
45 | // Counter: ${store.getState()?.counter}
46 | // inc
47 | // dec
48 | //
49 | // plus
50 | // `;
51 |
52 | // const incButton = document.querySelector('.inc') as HTMLButtonElement;
53 | // const decButton = document.querySelector('.dec') as HTMLButtonElement;
54 | // const plusButton = document.querySelector('.plus') as HTMLButtonElement;
55 | // const numberInput = document.querySelector('input[type="number"]') as HTMLInputElement;
56 | // const header = document.querySelector('h1') as HTMLHeadElement;
57 |
58 | // incButton.addEventListener('click', () => store.dispatch({
59 | // type: 'inc'
60 | // }));
61 |
62 | // decButton.addEventListener('click', () => store.dispatch({
63 | // type: 'dec'
64 | // }));
65 |
66 | // plusButton.addEventListener('click', () => store.dispatch({
67 | // type:'plus',
68 | // payload: Number(numberInput.value)
69 | // }));
70 |
71 | // store.subscribe(() => header.innerHTML = `Counter: ${store.getState()?.counter}`);
72 |
--------------------------------------------------------------------------------
/lessons/lesson34/code/reduxBasic/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["es6", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson34/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Это подводящее задание, чтобы вы могли спроектировать приложение-чат, над которым вы будете работать позже. Ваша задача - разобраться с тем, для чего нужен Redux и как организовывать работу с данными (в т.ч. из сторонних систем) в приложении на базе Redux.
3 | ---
4 |
5 | **Задание:**
6 |
7 | Разработать структуру (структура, экшены, редьюсер) Redux store для приложения Чат. Структура должна позволять выполнять следующие операции:
8 |
9 | - получение списка сообщений
10 | - получение одного сообщения
11 | - отправка сообщения
12 | - получение списка пользователей
13 | - поиск по чату
14 |
15 | **Критерии приемки задания:**
16 |
17 | - структура позволяет выполнять все действия выше
18 | - предусмотрены экшены для асинхронных событий (получение сообщений, отправка сообщений)
19 | - получение данных (список сообщений, список пользователей, поиск сообщений) оформлены в виде селекторов(простых и параметризованных)
20 | - код оформлен в PR
21 | - проходит проверки линтером (и тестами)
22 |
--------------------------------------------------------------------------------
/lessons/lesson34/images/EventBus.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/EventBus.jpeg
--------------------------------------------------------------------------------
/lessons/lesson34/images/middlewares.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/middlewares.gif
--------------------------------------------------------------------------------
/lessons/lesson34/images/redux-data-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/redux-data-flow.png
--------------------------------------------------------------------------------
/lessons/lesson35/code/client.js:
--------------------------------------------------------------------------------
1 | const socket = new WebSocket("ws://localhost:3000");
2 |
3 | const span = document.querySelector("span");
4 | const button = document.querySelector("button");
5 | const input = document.querySelector("input");
6 |
7 | socket.onopen = function () {
8 | span.textContent = "ONLINE";
9 |
10 | socket.onmessage = async function (msg) {
11 | console.log(msg.data);
12 | const data = await msg.data.text();
13 | createMessageEl(data, ["message"]);
14 | };
15 | };
16 |
17 | socket.onclose = function () {
18 | span.textContent = "OFFLINE";
19 | };
20 |
21 | button.addEventListener("click", () => {
22 | if (socket.readyState === socket.OPEN) {
23 | socket.send(input.value);
24 |
25 | createMessageEl(input.value, ["message", "my-message"]);
26 |
27 | input.value = "";
28 | }
29 | });
30 |
31 | function createMessageEl(content, className) {
32 | const message = document.createElement("div");
33 |
34 | message.classList.add(...className);
35 |
36 | message.textContent = content;
37 | document.body.append(message);
38 | }
39 |
--------------------------------------------------------------------------------
/lessons/lesson35/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 | OFFLINE
13 | Send
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/lessons/lesson35/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websockets",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev:server": "nodemon ./server.ts --watch . --ext ts,html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "@types/ws": "^8.5.3",
14 | "nodemon": "^2.0.16",
15 | "ts-node": "^10.8.0",
16 | "typescript": "^4.7.2"
17 | },
18 | "dependencies": {
19 | "ws": "^8.6.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lessons/lesson35/code/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { WebSocketServer } from "ws";
3 |
4 | const wss = new WebSocketServer({ port: 3000 });
5 |
6 | wss.on("connection", function connection(ws, req) {
7 | console.log(req.headers["sec-websocket-key"]);
8 | ws.id = req.headers["sec-websocket-key"];
9 | console.log(wss.clients.size);
10 | ws.on("message", function message(data) {
11 | for (const socket of wss.clients) {
12 | if (socket.readyState === socket.OPEN && socket.id !== ws.id) {
13 | socket.send(data);
14 | }
15 | }
16 |
17 | console.log("received: %s", data);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/lessons/lesson35/code/style.css:
--------------------------------------------------------------------------------
1 | .message {
2 | background-color: violet;
3 | max-width: 500px;
4 | border-radius: 3px;
5 | margin-top: 10px;
6 | padding: 10px;
7 | }
8 |
9 | .message.my-message {
10 | background-color: cyan;
11 | }
12 |
--------------------------------------------------------------------------------
/lessons/lesson35/code/types.d.ts:
--------------------------------------------------------------------------------
1 | import "ws";
2 |
3 | declare module "ws" {
4 | export interface WebSocket {
5 | id?: string;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lessons/lesson35/images/Archive_repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/Archive_repo.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/REST_CRUD_operations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/REST_CRUD_operations.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/comparision1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/comparision1.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/comparision2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/comparision2.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/oauth_example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_example1.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/oauth_example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_example2.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/oauth_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_flow.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/requestScope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/requestScope.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/slack_RPC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/slack_RPC.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/slack_conversation_API.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/slack_conversation_API.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/ta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/ta.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/ta1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/ta1.png
--------------------------------------------------------------------------------
/lessons/lesson35/images/tripAdvisor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/tripAdvisor.png
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Parcel Sandbox
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reduxdataloading",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "parcel index.html --open",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "parcel-bundler": "^1.6.1",
12 | "redux": "4.0.5"
13 | },
14 | "devDependencies": {
15 | "typescript": "4.2.3"
16 | },
17 | "resolutions": {
18 | "@babel/preset-env": "7.13.8"
19 | },
20 | "keywords": []
21 | }
22 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/actions.ts:
--------------------------------------------------------------------------------
1 | export const loading = () => ({
2 | type: "LOADING",
3 | });
4 |
5 | export const success = (payload: any) => ({
6 | type: "SUCCESS",
7 | payload,
8 | });
9 |
10 | export const error = (error: Error) => ({
11 | type: "ERROR",
12 | error,
13 | });
14 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/api.ts:
--------------------------------------------------------------------------------
1 | const todos = [
2 | {
3 | userId: 1,
4 | id: 1,
5 | title: "delectus aut autem",
6 | completed: false,
7 | },
8 | {
9 | userId: 1,
10 | id: 2,
11 | title: "quis ut nam facilis et officia qui",
12 | completed: false,
13 | },
14 | {
15 | userId: 1,
16 | id: 3,
17 | title: "fugiat veniam minus",
18 | completed: false,
19 | },
20 | {
21 | userId: 1,
22 | id: 4,
23 | title: "et porro tempora",
24 | completed: true,
25 | },
26 | {
27 | userId: 1,
28 | id: 5,
29 | title: "laboriosam mollitia et enim quasi adipisci quia provident illum",
30 | completed: false,
31 | },
32 | {
33 | userId: 1,
34 | id: 6,
35 | title: "qui ullam ratione quibusdam voluptatem quia omnis",
36 | completed: false,
37 | },
38 | {
39 | userId: 1,
40 | id: 7,
41 | title: "illo expedita consequatur quia in",
42 | completed: false,
43 | },
44 | {
45 | userId: 1,
46 | id: 8,
47 | title: "quo adipisci enim quam ut ab",
48 | completed: true,
49 | },
50 | {
51 | userId: 1,
52 | id: 9,
53 | title: "molestiae perspiciatis ipsa",
54 | completed: false,
55 | },
56 | {
57 | userId: 1,
58 | id: 10,
59 | title: "illo est ratione doloremque quia maiores aut",
60 | completed: true,
61 | },
62 | {
63 | userId: 1,
64 | id: 11,
65 | title: "vero rerum temporibus dolor",
66 | completed: true,
67 | },
68 | {
69 | userId: 1,
70 | id: 12,
71 | title: "ipsa repellendus fugit nisi",
72 | completed: true,
73 | },
74 | {
75 | userId: 1,
76 | id: 13,
77 | title: "et doloremque nulla",
78 | completed: false,
79 | },
80 | ];
81 |
82 | const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
83 |
84 | export const getTodos = async () => {
85 | await delay(Math.random() * 1000 + 1500);
86 | if (Math.random() > 0.5) {
87 | return todos;
88 | } else {
89 | throw new Error("Something went wrong");
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/index.ts:
--------------------------------------------------------------------------------
1 | import { State } from "./reducer";
2 | import { store } from "./store";
3 | import * as actions from "./actions";
4 | import { getTodos } from "./api";
5 |
6 | const el = document.querySelector("#app") as HTMLElement;
7 |
8 | type RenderData = {
9 | isLoading: boolean;
10 | data: any | undefined;
11 | error: Error | undefined;
12 | };
13 |
14 | async function loadData() {
15 | // put your code here
16 | }
17 |
18 | const render = (props: RenderData) => {
19 | if (props.isLoading) {
20 | return (el.innerHTML = "Loading....");
21 | }
22 | if (props.error) {
23 | return (el.innerHTML = `${props.error.message} `);
24 | }
25 | if (props.data) {
26 | return (el.innerHTML = `${JSON.stringify(props.data, null, 2)} `);
27 | }
28 | el.innerHTML = `Load data `;
29 | el.querySelector("button")?.addEventListener("click", loadData);
30 | };
31 |
32 | const selectData = (state: State): RenderData => ({
33 | isLoading: state.isLoading,
34 | data: state.data,
35 | error: state.error,
36 | });
37 |
38 | render(selectData(store.getState()));
39 | store.subscribe(() => render(selectData(store.getState())));
40 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/logger.ts:
--------------------------------------------------------------------------------
1 | import { Middleware } from "redux";
2 |
3 | // export const loggerMiddleware: Middleware = ({
4 | // dispatch,
5 | // getState
6 | // }) => next => action => {
7 | // // Залогируйте состояние до экшена
8 | // // Пустите экшен по цепочке дальше
9 | // // Залогируйте состояние после
10 | // // Верните экшен полученный из цепочки
11 | // };
12 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/reducer.ts:
--------------------------------------------------------------------------------
1 | import { Reducer } from "redux";
2 |
3 | export type State = {
4 | isLoading: boolean;
5 | data: any | undefined;
6 | error: Error | undefined;
7 | };
8 |
9 | const initialState: State = {
10 | isLoading: false,
11 | data: undefined,
12 | error: undefined,
13 | };
14 |
15 | export const apiReducer: Reducer = (state = initialState, action) => {
16 | switch (action.type) {
17 | case "LOADING":
18 | return { ...state, isLoading: true };
19 | case "SUCCESS":
20 | return {
21 | ...state,
22 | isLoading: false,
23 | data: action.payload,
24 | error: undefined,
25 | };
26 | case "ERROR":
27 | return {
28 | ...state,
29 | isLoading: false,
30 | data: undefined,
31 | error: action.error,
32 | };
33 | default:
34 | return state;
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/src/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import { apiReducer } from "./reducer";
3 | import { loggerMiddleware } from "./logger";
4 |
5 | export const store =
6 | typeof loggerMiddleware === "function"
7 | ? createStore(apiReducer, applyMiddleware(loggerMiddleware))
8 | : createStore(apiReducer);
9 |
--------------------------------------------------------------------------------
/lessons/lesson38/code/reduxDataLoading/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "jsx": "preserve",
6 | "esModuleInterop": true,
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "lib": ["es6", "dom"],
10 | "rootDir": "src",
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lessons/lesson38/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Собираем все вместе. Берем верстку, структуру хля хранения состояния и дорабатываем это до полноценного приложения. Вы поработаете с дополнительным сервисом, который позволит вам в будущем организовывать взаимодействие нескольких пользователей. И на практике поймете, на сколько гибко вы выполнили подводящие задания
3 | ---
4 |
5 | ### "Разработать приложение "чат" на основе redux"
6 |
7 | Необходимо:
8 |
9 | - создать и настроить проект
10 | - реализовать приложение чат - которое позволяет отправлять сообщения и отображает входящие сообщения из канала в firebase (api и формат сообщения будет предоставлено):
11 | - использовать Redux структуру для приложения чат
12 | - создать UI для приложения
13 | - добавить функционал по отображению входящих сообщений
14 | - добавить функционал по отправке сообщений
15 | - добавить функционал по обработке смайликов (в виде картинок)
16 | - подготовить работу к сдаче
17 | - сделать ревью 2 других работ
18 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в чат с преподавателем
19 |
20 | #### Критерии оценки:
21 |
22 | - чат позволяет принимать сообщения - **2** балла
23 | - чат позволяет отправлять сообщения - **2** балла
24 | - чат поддерживает анимацию смайликов - **1** балл
25 | - сделано ревью 2 других проектов - **1** балл
26 |
27 | Задание не проверяется при не соответствии базовым требованиям к заданию!
28 |
29 | #### Статус принято от 5 баллов
30 |
--------------------------------------------------------------------------------
/lessons/lesson38/images/middlewares.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson38/images/middlewares.gif
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/README.md:
--------------------------------------------------------------------------------
1 | # vanilla-client-side-routing-examples
2 |
3 | Created with CodeSandbox
4 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/examples/hash-api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const render = () => {
4 | const route = location.hash.replace("#", "") || "/";
5 | document.getElementById("root").innerHTML = `"${route}" page `;
6 | };
7 |
8 | // 1. Handle initial page load
9 | window.addEventListener("load", () => {
10 | render(); // 👈
11 | });
12 |
13 | // 2. Handle hash changes
14 | window.addEventListener("hashchange", () => {
15 | render(); // 👈
16 | });
17 |
18 | // 3. Catch tag clicks
19 | document.body.addEventListener("click", (event) => {
20 | if (!event.target.matches("a")) {
21 | return;
22 | }
23 | event.preventDefault();
24 | const url = event.target.getAttribute("href");
25 | location.hash = url; // doesn't reload page
26 | // location.href = url; // reloads page
27 | // location.replace(url); // reloads page
28 | });
29 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/examples/history-api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const render = () => {
4 | const route = location.pathname;
5 | document.getElementById("root").innerHTML = `"${route}" page `;
6 | };
7 |
8 | // 1. Handle initial page load
9 | window.addEventListener("load", () => {
10 | render(); // 👈
11 | });
12 |
13 | // 2. Handle history navigations. alternative "window.onpopstate"
14 | window.addEventListener("popstate", (event) => {
15 | render();
16 | });
17 |
18 | // 3. Catch tag clicks + trigger change handler
19 | document.body.addEventListener("click", (event) => {
20 | if (!event.target.matches("a")) {
21 | return;
22 | }
23 | event.preventDefault();
24 | let url = event.target.getAttribute("href");
25 | history.pushState({ foo: "bar" }, document.title, url);
26 | // history.replaceState({ foo: "bar" }, url, url);
27 | render(); // 👈
28 | });
29 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/examples/practice.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | /**
4 | * TODO: modify router.js to support
5 | * 1. unsubscribe function.
6 | * Hint: inside Router.go function return unsubscribe function,
7 | * which will remove listener by id
8 | * 2. onLeave callback
9 | * Hint: Add 3rd 'onLeave' parameter to Router.on + save in listener object
10 | * Check in Router.handleListener if previousPath matches listener
11 | */
12 |
13 | const render = (content) =>
14 | (document.getElementById("root").innerHTML = `${content} `);
15 |
16 | const createLogger =
17 | (content, shouldRender = true) =>
18 | (...args) => {
19 | console.log(`LOGGER: ${content} args=${JSON.stringify(args)}`);
20 | if (shouldRender) {
21 | render(content);
22 | }
23 | };
24 |
25 | const router = Router();
26 |
27 | const unsubscribe = router.on(/.*/, createLogger("/.*"));
28 | router.on(
29 | (path) => path === "/contacts",
30 | createLogger("/contacts"), // onEnter
31 | createLogger("[leaving] /contacts", false) // onLeave
32 | );
33 | router.on("/about", createLogger("/about"));
34 | router.on("/about/us", createLogger("/about/us"));
35 |
36 | document.body.addEventListener("click", (event) => {
37 | if (!event.target.matches("a")) {
38 | return;
39 | }
40 | event.preventDefault();
41 | let url = event.target.getAttribute("href");
42 | router.go(url);
43 | unsubscribe();
44 | });
45 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Client-Side URL change examples
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vanilla-client-side-routing-examples",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "serve",
8 | "build": "echo This is a static template, there is no bundler or bundling involved!"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/codesandbox-app/static-template.git"
13 | },
14 | "keywords": [],
15 | "author": "Ives van Hoorne",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/codesandbox-app/static-template/issues"
19 | },
20 | "homepage": "https://github.com/codesandbox-app/static-template#readme",
21 | "devDependencies": {
22 | "serve": "^11.2.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lessons/lesson39/code/routing-examples/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "static"
3 | }
4 |
--------------------------------------------------------------------------------
/lessons/lesson39/hw.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Во-первых вы разберетесь с тем, как устроен роутинг в современных приложениях (чтобы понимать, как работают готовые библиотеки, которыми вы будете пользоваться). Кроме этого вы потренируетесь оформлять свой код не в виде отдельного файла, а в виде полноценного npm-пакета, с которым смогут работать другие разработчики.
3 | ---
4 |
5 | ### "Разработка библиотеки клиентского роутинга"
6 |
7 | Необходимо:
8 |
9 | 1. создать и настроить проект
10 |
11 | 2. разработать библиотеку клиентского роутинга:
12 | 2.1 конфигурация роутов должна поддерживаться через функции/строки/регулярки
13 | 2.2 должна поддерживаться передача параметров в хуки роутера
14 | 2.3 реализовать поддержку асинхронных onBeforeEnter, onEnter, onLeave
15 | 2.4 добавить настройку для работы с hash/history api
16 | 2.5 опубликовать пакет
17 |
18 | 3. подготовить работу с сдаче:
19 | 3.1 сделать ревью **2** других работ
20 | 3.2 сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в чат с преподавателем
21 |
22 | #### Критерии оценки:
23 |
24 | - роутер поддерживает роуты из строк - **1** балл
25 | - роутер поддерживает роуты из регулярных выражений - **1** балл
26 | - роутер поддерживает роуты из функций - **1** балл
27 | - роутер поддерживает асинхронные хуки - **1** балл
28 | - роутер поддерживает `onLeave` , `onEnter`, `onBeforeEnter` - **2** балла
29 | - роутер поддерживает переключение api - **1** балл
30 | - пакет опубликован - **1** балл
31 | - сделано ревью 2 проектов - **1** балл
32 | - роутер поддерживает проброс параметров в хуки - **2** балла
33 |
34 | #### Статус принято ставится от 8 баллов
35 |
36 | Задание не проверяется при не соответствии базовым требованиям к заданию!
37 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 |
3 | on: [push, pull_request]
4 |
5 | permissions:
6 | contents: write
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout 🛎️
13 | uses: actions/checkout@v3
14 |
15 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
16 | run: |
17 | npm ci
18 | npm run build
19 |
20 | - name: Deploy 🚀
21 | uses: JamesIves/github-pages-deploy-action@v4.3.3
22 | with:
23 | branch: gh-pages # The branch the action should deploy to.
24 | folder: dist # The folder the action should deploy.
25 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .idea
--------------------------------------------------------------------------------
/lessons/lesson40/code/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/README.md:
--------------------------------------------------------------------------------
1 | # webpack-gh-pages
2 |
3 | ###### Boilerplate for SPA deploy
4 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-gh-pages",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "prepare": "husky install",
9 | "start": "webpack serve --node-env development",
10 | "buld:dev": "webapck --node-env development",
11 | "build": "webpack --node-env production"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "@babel/core": "^7.14.3",
18 | "@babel/preset-typescript": "^7.13.0",
19 | "babel-loader": "^8.2.2",
20 | "html-webpack-plugin": "^5.3.1",
21 | "husky": "^6.0.0",
22 | "lint-staged": "^11.0.0",
23 | "prettier": "^2.3.1",
24 | "webpack": "^5.72.1",
25 | "webpack-cli": "^4.9.2",
26 | "webpack-dev-server": "^4.9.1"
27 | },
28 | "lint-staged": {
29 | "*.{js,css,md,html,ts}": "prettier --write"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Single page app
8 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/src/index.ts:
--------------------------------------------------------------------------------
1 | function render(hash?: string) {
2 | const href = window.location.href;
3 |
4 | document.querySelector("#app")!.textContent = hash
5 | ? href.includes("#")
6 | ? href.replace(/#.*/, hash)
7 | : href + hash
8 | : window.location.href;
9 | }
10 |
11 | document.body.addEventListener("click", (ev) => {
12 | if ((ev.target as HTMLElement).matches("a")) {
13 | const hash = (ev.target as HTMLAnchorElement).hash;
14 | render(hash);
15 | }
16 | });
17 |
18 | render();
19 |
--------------------------------------------------------------------------------
/lessons/lesson40/code/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 |
4 | const NODE_ENV = process.env.NODE_ENV;
5 |
6 | module.exports = {
7 | entry: "./src/index.ts",
8 | output: {
9 | filename: "bundle.js",
10 | path: resolve(__dirname, "dist"),
11 | clean: true,
12 | },
13 | mode: NODE_ENV,
14 | resolve: {
15 | extensions: [".js", ".ts"],
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.ts$/,
21 | use: {
22 | loader: "babel-loader",
23 | options: {
24 | presets: ["@babel/preset-typescript"],
25 | },
26 | },
27 | },
28 | ],
29 | },
30 | plugins: [
31 | new HtmlWebpackPlugin({
32 | template: "public/index.html",
33 | }),
34 | ],
35 | devServer: {
36 | compress: true,
37 | port: 9000,
38 | watchFiles: ["public/index.html"],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/lessons/lesson40/hw.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Вы возьмете разработанных роутер, и примените его к существующему приложению. Заодно у вас будет шанс посмотреть по-новому на код, который вы писали ранее. Возможно (почти наверняка), вам придется его улучшить в процессе выполнения задачи.
3 | ---
4 |
5 | **Необходимо**
6 |
7 | - подключить разработанный роутер к приложению "Прогноз погоды"
8 | - использовать параметризованный роутинг для отображения погоды в конкретном городе
9 | - Добавить навигацию при запросе погоды (ввод нового города отображается на URL)
10 | - Обновить обработку навигации по истории (это должно быть сделано средствами роутинга)
11 |
12 | **Критерии приемки задачи**
13 |
14 | - экраны приложения рисуются роутером
15 | - показ погоды в городе обрабатывается параметризованным роутом, где имя города это параметр
16 | - ссылки из истории переведены на параметризованный роутинг (SPA ссылки, где логика загрузки и показа погоды обслуживается по навигации на роут, а не по клику)
17 | - приложение задеплоено на GithubPages, там же можно проверить работу ссылок
18 | - код оформлен в виде PR и проходит проверки (линтинг, тесты)
19 | - В приложении 3 экрана (главная, погода в городе, О проекте)
20 |
--------------------------------------------------------------------------------
/lessons/lesson40/lecture.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Занятие 40
3 | description: Некоторые моменты про деплой одностраничных приложений
4 | ---
5 |
6 | # OTUS
7 |
8 | ## Javascript Basic
9 |
10 |
11 |
12 | Вопросы?
13 |
14 |
15 |
16 | ### Ссылки
17 |
18 | - [devServer.historyApiFallback](https://webpack.js.org/configuration/dev-server/#devserverhistoryapifallback)
19 | - [output.publicPath](https://webpack.js.org/configuration/output/#outputpublicpath)
20 | - [WebpackHTMLPlugin filename option](https://github.com/jantimon/html-webpack-plugin#options)
21 | - [404 page for Github Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-custom-404-page-for-your-github-pages-site)
22 | - [Пример настройки webpack для работы с history API](https://github.com/vvscode/webpack-gh-pages/pulls)
23 | - [Пример работы роутера с параметризацией](https://gzh7s.csb.app/#city=Moscow)
24 |
25 |
26 |
27 | ### [Домашнее задание](https://github.com/vvscode/otus--javascript-basic/blob/master/lessons/lesson40/homework.md)
28 |
29 |
30 |
31 | Вопросы?
32 |
--------------------------------------------------------------------------------
/lessons/lesson42/hw.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Дальнейшая переработка сайта, который вы сделали в начале курса. Теперь вы будете закреплять навыки работы с Redux (работа с сайд-эффектами, persistent storage).
3 | ---
4 |
5 | **Задание:**
6 |
7 | Использовать redux для сайта с прогнозом погоды.
8 |
9 | В репозитории с проектом "прогноз погоды" создать PR, который переводит работу с состоянием приложения на Redux.
10 |
11 | **Критерии выполнения задания:**
12 |
13 | - открыт PR
14 | - код проходит проверки линтером (и тесты)
15 | - все состояние приложения вынесено в Redux state
16 | - работа с API реализована посредством middleware
17 | - работа с хранимым состоянием (история) вынесена в middleware
18 | - код опубликован на GithubPages (и ссылка приложена к PR)
19 | - на странице работают Redux DevTools (можно открыть и посмотреть историю redux)
20 |
--------------------------------------------------------------------------------
/lessons/lesson42/images/calendar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson42/images/calendar.png
--------------------------------------------------------------------------------
/lessons/lesson42/images/folder-structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson42/images/folder-structure.png
--------------------------------------------------------------------------------
/lessons/lesson43/hw.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Знакомимся с современными инструментами разработки UI компонентов. С большой долей вероятности вы с ними столкнетесь в реальных проектах (если нет - это повод принести туда эти инструменты).
3 | ---
4 |
5 | **Необходимо:**
6 |
7 | - создать и настроить проект
8 |
9 | - настроить сторибук
10 | - настроить тесты с локи
11 |
12 | - создать компоненты:
13 |
14 | - заголовок (с поддержкой уровней)
15 | - параграф текста
16 | - пробельный блок (с горизонтальной линией)
17 | - схлопывающийся блок (может сворачиваться в заголовк или показывать контент)
18 | - картинка
19 |
20 | - опубликовать storybook на github pages
21 |
22 | - подготовить работу с сдаче\*
23 | - сделать ревью 2 других работ
24 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем
25 |
26 | **Критерии приемки задания:**
27 |
28 | - задание оформлено в виде PR
29 | - код проходил проверки линтером (и тестами)
30 | - storybook задеплоен на GithubPages
31 | - реализованы все требуемые компоненты
32 | - все компоненты сопровождаются скриншотными тестами (настроена инфраструктура и скриншоты находятся в PR)
33 |
--------------------------------------------------------------------------------
/lessons/lesson43/hw2.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Закрепляем работу с компонентами
3 | ---
4 |
5 | **Необходимо:**
6 |
7 | - в прошлом проекте создать новые компоненты - за основу берем сайт "Прогоноз погоды" и переносим его разметку на компоненты React
8 | - продумать компоненты и их интерфейсы (какие свойства они принимают)
9 | - реализовать компоненты и story описывающие работу этих компонент
10 |
11 | Критерии приемки
12 |
13 | - реализованы все компоненты, необходимые для сборки "Прогноза погоды" (формы, списки, компонент-layout)
14 | - в storybook можно посмотреть все состояния компонент, проверить работу actions
15 | - storybook задеплоен на github pages (в отдельной директории, чтобы не мешать будущему проекту)
16 |
--------------------------------------------------------------------------------
/lessons/lesson43/images/createElement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/createElement.png
--------------------------------------------------------------------------------
/lessons/lesson43/images/dom-reconciliation.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/dom-reconciliation.webp
--------------------------------------------------------------------------------
/lessons/lesson43/images/vdom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/vdom.png
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "controlled-counter",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.tsx",
7 | "dependencies": {
8 | "@testing-library/jest-dom": "5.14.1",
9 | "@testing-library/react": "11.2.7",
10 | "@testing-library/user-event": "13.1.9",
11 | "react": "17.0.2",
12 | "react-dom": "17.0.2",
13 | "react-scripts": "4.0.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "17.0.0",
17 | "@types/react-dom": "17.0.0",
18 | "typescript": "4.1.3"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test --env=jsdom",
24 | "eject": "react-scripts eject"
25 | },
26 | "browserslist": [
27 | ">0.2%",
28 | "not dead",
29 | "not ie <= 11",
30 | "not op_mini all"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
25 | React App
26 |
27 |
28 |
29 | You need to enable JavaScript to run this app.
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/src/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { CounterButton } from "./CounterButton";
4 |
5 | export class Counter extends React.Component<{}, { value: number }> {
6 | state = {
7 | value: 5,
8 | };
9 |
10 | inc = () =>
11 | this.setState((state) => ({
12 | value: state.value + 1,
13 | }));
14 |
15 | dec = () =>
16 | this.setState((state) => ({
17 | value: state.value - 1,
18 | }));
19 |
20 | render() {
21 | return (
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/src/CounterButton.tsx:
--------------------------------------------------------------------------------
1 | export function CounterButton({
2 | value,
3 | inc,
4 | dec,
5 | }: {
6 | value: number;
7 | inc: () => void;
8 | dec: () => void;
9 | }) {
10 | return (
11 | <>
12 | -
13 | {value}
14 | +
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 |
3 | import { Counter } from "./Counter";
4 |
5 | const rootElement = document.getElementById("root");
6 | render( , rootElement);
7 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/controlled-counter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "counter-button",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.tsx",
7 | "dependencies": {
8 | "@testing-library/jest-dom": "5.14.1",
9 | "@testing-library/react": "11.2.7",
10 | "@testing-library/user-event": "13.1.9",
11 | "react": "17.0.2",
12 | "react-dom": "17.0.2",
13 | "react-scripts": "4.0.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "17.0.0",
17 | "@types/react-dom": "17.0.0",
18 | "typescript": "4.1.3"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test --env=jsdom",
24 | "eject": "react-scripts eject"
25 | },
26 | "browserslist": [
27 | ">0.2%",
28 | "not dead",
29 | "not ie <= 11",
30 | "not op_mini all"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
25 | React App
26 |
27 |
28 |
29 | You need to enable JavaScript to run this app.
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/src/CounterButton.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import userEvent from "@testing-library/user-event";
3 | import "@testing-library/jest-dom";
4 |
5 | import { CounterButton } from "./CounterButton";
6 |
7 | describe("CounterButton", () => {
8 | it("is a Component", () => {
9 | expect(CounterButton).toBeInstanceOf(Function);
10 | });
11 |
12 | describe("functionality", () => {
13 | beforeEach(() => {
14 | render( );
15 | });
16 |
17 | it("renders button with 0", () => {
18 | expect(screen.getByTestId("counter-button")).toBeInTheDocument();
19 | expect(screen.getByTestId("counter-button").innerHTML).toBe("0");
20 | });
21 |
22 | it("increments counter on every click", () => {
23 | expect(screen.getByTestId("counter-button").innerHTML).toBe("0");
24 |
25 | userEvent.click(screen.getByTestId("counter-button"));
26 | expect(screen.getByTestId("counter-button").innerHTML).toBe("1");
27 |
28 | userEvent.click(screen.getByTestId("counter-button"));
29 | expect(screen.getByTestId("counter-button").innerHTML).toBe("2");
30 |
31 | userEvent.click(screen.getByTestId("counter-button"));
32 | expect(screen.getByTestId("counter-button").innerHTML).toBe("3");
33 | });
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/src/CounterButton.tsx:
--------------------------------------------------------------------------------
1 | // put your code here
2 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 |
3 | import { CounterButton } from "./CounterButton";
4 |
5 | const rootElement = document.getElementById("root");
6 | render( , rootElement);
7 |
--------------------------------------------------------------------------------
/lessons/lesson44/code/counter-button/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lessons/lesson44/images/unidirectional-data-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson44/images/unidirectional-data-flow.png
--------------------------------------------------------------------------------
/lessons/lesson45/images/tree-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson45/images/tree-state.png
--------------------------------------------------------------------------------
/lessons/lesson45/images/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson45/images/tree.png
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [320, 675],
4 | "Tablet": [1024, 765],
5 | "Desktop": [1400, 800],
6 | "Desktop HD": [1920, 1080]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactredux",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.tsx",
7 | "dependencies": {
8 | "@reduxjs/toolkit": "1.6.0",
9 | "react": "17.0.2",
10 | "react-dom": "17.0.2",
11 | "react-redux": "7.2.4",
12 | "react-scripts": "4.0.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "17.0.0",
16 | "@types/react-dom": "17.0.0",
17 | "typescript": "4.1.3"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test --env=jsdom",
23 | "eject": "react-scripts eject"
24 | },
25 | "browserslist": [
26 | ">0.2%",
27 | "not dead",
28 | "not ie <= 11",
29 | "not op_mini all"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
25 | React App
26 |
27 |
28 |
29 | You need to enable JavaScript to run this app.
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/1_pureReact/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { store } from "../store";
3 | import { counterSlice } from "../store/counter";
4 |
5 | export class App extends React.Component {
6 | private unsubscribe = () => {};
7 |
8 | componentDidMount() {
9 | this.unsubscribe = store.subscribe(() => this.forceUpdate());
10 | }
11 |
12 | componentWillUnmount() {
13 | this.unsubscribe();
14 | }
15 |
16 | getComponentData() {
17 | const state = store.getState();
18 | return {
19 | count: state.counter,
20 | totalCount: state.actionsCounter.count,
21 | };
22 | }
23 |
24 | render() {
25 | const componentData = this.getComponentData();
26 | return (
27 | <>
28 | Total clicks: {componentData.totalCount}
29 |
31 | store.dispatch(counterSlice.actions.incrementByAmount(-2))
32 | }
33 | >
34 | -2
35 |
36 | store.dispatch(counterSlice.actions.decrement())}
38 | >
39 | -1
40 |
41 | {componentData.count}
42 | store.dispatch(counterSlice.actions.increment())}
44 | >
45 | +1
46 |
47 |
49 | store.dispatch(counterSlice.actions.incrementByAmount(2))
50 | }
51 | >
52 | +2
53 |
54 | >
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/2_withHOC/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { store } from "../store";
3 | import { counterSlice } from "../store/counter";
4 | import { withRedux } from "./withRedux";
5 |
6 | const getAppProps = (state) => {
7 | return {
8 | count: state.counter,
9 | totalCount: state.actionsCounter.count,
10 | };
11 | };
12 |
13 | class RawApp extends React.Component<
14 | ReturnType & {
15 | dispatch: (action: any) => void;
16 | }
17 | > {
18 | render() {
19 | return (
20 | <>
21 | Total clicks: {this.props.totalCount}
22 |
24 | this.props.dispatch(counterSlice.actions.incrementByAmount(-2))
25 | }
26 | >
27 | -2
28 |
29 | this.props.dispatch(counterSlice.actions.decrement())}
31 | >
32 | -1
33 |
34 | {this.props.count}
35 | this.props.dispatch(counterSlice.actions.increment())}
37 | >
38 | +1
39 |
40 |
42 | this.props.dispatch(counterSlice.actions.incrementByAmount(2))
43 | }
44 | >
45 | +2
46 |
47 | >
48 | );
49 | }
50 | }
51 |
52 | export const App = withRedux>(
53 | RawApp,
54 | getAppProps
55 | );
56 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/2_withHOC/withRedux.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from "react";
2 | import { AnyAction } from "redux";
3 | import { store } from "../store";
4 |
5 | interface DispatchProp {
6 | dispatch?: Dispatch;
7 | }
8 |
9 | type State = Record;
10 |
11 | // if you want to get more info
12 | // try to check https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e
13 | export function withRedux(
14 | TargetComponent: React.ComponentType,
15 | getPropsFromRedux: (state: State) => ComponentProps
16 | ): React.ComponentType> {
17 | class WrappedComponent extends React.Component<
18 | Omit>,
19 | State
20 | > {
21 | storeSubscription = () => {};
22 |
23 | render() {
24 | return (
25 |
30 | );
31 | }
32 |
33 | componentDidMount() {
34 | this.storeSubscription = store.subscribe(() => this.forceUpdate());
35 | }
36 |
37 | componentWillUnmount() {
38 | this.storeSubscription();
39 | }
40 | }
41 |
42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
43 | (
44 | WrappedComponent as any
45 | ).displayName = `${TargetComponent.displayName}ConnectedToRedux`;
46 |
47 | return WrappedComponent;
48 | }
49 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/3_react-redux/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { AnyAction, bindActionCreators, Dispatch } from "@reduxjs/toolkit";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { store } from "../store";
5 | import { counterSlice } from "../store/counter";
6 |
7 | const mapStateToProps = (state: ReturnType) => ({
8 | count: state.counter,
9 | totalCount: state.actionsCounter.count,
10 | });
11 |
12 | // https://react-redux.js.org/api/connect#mapdispatchtoprops-object--dispatch-ownprops--object
13 | const mapDispatchToProps = (dispatch: Dispatch) => {
14 | return bindActionCreators(
15 | {
16 | incrementByAmount: counterSlice.actions.incrementByAmount,
17 | decrement: counterSlice.actions.decrement,
18 | increment: counterSlice.actions.increment,
19 | },
20 | dispatch
21 | );
22 | };
23 | // const mapDispatchToProps = {
24 | // incrementByAmount: counterSlice.actions.incrementByAmount,
25 | // decrement: counterSlice.actions.decrement,
26 | // increment: counterSlice.actions.increment
27 | // };
28 |
29 | type ConnectedProps<
30 | MapData extends (...args: any) => any,
31 | MapActions extends (...args: any) => any | Record
32 | > = ReturnType &
33 | (MapActions extends Function ? ReturnType : MapActions);
34 |
35 | class RawCounter extends React.Component<
36 | ConnectedProps
37 | > {
38 | render() {
39 | return (
40 | <>
41 | Total clicks: {this.props.totalCount}
42 | this.props.incrementByAmount(-2)}>-2
43 | this.props.decrement()}>-1
44 | {this.props.count}
45 | this.props.increment()}>+1
46 | this.props.incrementByAmount(2)}>+2
47 | >
48 | );
49 | }
50 | }
51 |
52 | export const Counter = connect(mapStateToProps, mapDispatchToProps)(RawCounter);
53 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/3_react-redux/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { store } from "../store";
3 | import { Provider } from "react-redux";
4 | import { Counter } from "./Counter";
5 |
6 | export const App = () => (
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 |
3 | let App = () => React-Redux ;
4 | // import { App } from "./1_pureReact";
5 | // import { App } from "./2_withHOC";
6 | // import { App } from "./3_react-redux";
7 |
8 | const rootElement = document.getElementById("root");
9 | render( , rootElement);
10 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/store/actionsCounter.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const actionsCounterSlice = createSlice({
4 | name: "actionsCounter",
5 | initialState: {
6 | count: 0,
7 | },
8 | reducers: {},
9 | extraReducers: (builder) => {
10 | builder.addDefaultCase((state) => {
11 | state.count++;
12 | });
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/store/counter.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 |
3 | export const counterSlice = createSlice({
4 | name: "counter",
5 | initialState: 0,
6 | reducers: {
7 | increment: (state) => state + 1,
8 | decrement: (state) => state - 1,
9 | incrementByAmount: (state, action: PayloadAction) =>
10 | state + action.payload,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { actionsCounterSlice } from "./actionsCounter";
3 | import { counterSlice } from "./counter";
4 |
5 | export const store = configureStore({
6 | reducer: {
7 | counter: counterSlice.reducer,
8 | actionsCounter: actionsCounterSlice.reducer,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/src/styles.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/lessons/lesson46/code/reactredux/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lessons/lesson48/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Тренируемся писать консольные приложения и публиковать их в Github (вы уже делали это с API, но инструменты командной строки немного отличаются). Учимся работать с файловой системой и запуском команд из nodejs.
3 | ---
4 |
5 | ### "Написать консольное приложение для публикации страниц на github"
6 |
7 | #### Необходимо:
8 |
9 | - создать и настроить проект\*
10 |
11 | - реализовать скрипт командной строки, который поддерживает ввод параметров
12 |
13 | - через аргументы
14 | - через интерфейс с запросами в консоли
15 |
16 | - позволяет опубликовать проект (собрать его при необходимости) на github pages с заданными параметрами
17 | - команда для сборки (если нужно)
18 | - директория для публикации
19 | - репозиторий для публикации
20 | - настройки доступа к репозиторию
21 |
22 | - опубликовать, чтобы можно было использовать через npx
23 | - оформить README с подробным описанием и gif с демонстрацией работы
24 |
25 | - подготовить работу с сдаче\*
26 | - сделать ревью 2 других работ
27 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем
28 |
29 | #### Критерии оценки
30 |
31 | - реализованная команда публикует проект - **2** балла
32 | - поддерживается сборка проекта перед публикацией - **1** балл
33 | - поддерживается настройка репозитория - **1** балл
34 | - поддерживается настройка авторизации - **1** балл
35 | - поддерживается ввод через параметры - **1** балл
36 | - поддерживается ввод через запросы - **1** балл
37 | - проверены 2 задания - **1** балл
38 | - пакет опубликован и доступен через npx - **1** балл
39 |
40 | #### Задание не проверяется при не соответствии базовым требованиям к заданию
41 |
42 | Принято от **6** баллов
43 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/assets/read.txt:
--------------------------------------------------------------------------------
1 | Hey, I'm file and you can read me!
--------------------------------------------------------------------------------
/lessons/lesson49/code/assets/song.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/code/assets/song.mp3
--------------------------------------------------------------------------------
/lessons/lesson49/code/assets/tmp.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "username": "Bob Ross",
4 | "paintings": 359
5 | }
6 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/assets/tmp.txt:
--------------------------------------------------------------------------------
1 | 1 2 3 4 5 6 7 8 9 10
--------------------------------------------------------------------------------
/lessons/lesson49/code/commander/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "commander",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "commander": "^8.1.0"
14 | },
15 | "devDependencies": {
16 | "@types/node": "^16.4.7",
17 | "ts-node": "^10.1.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/commander/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "module": "CommonJS",
5 | "target": "ES2020",
6 | "esModuleInterop": true,
7 | "resolveJsonModule": true
8 | },
9 | "include": ["*.ts"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/1-writeFileSync.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | fs.writeFileSync("../assets/writeFile.txt", "hello! I'm new born file!", {
4 | encoding: "utf-8",
5 | });
6 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/2-readFileSync.ts:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 |
3 | const data = fs.readFileSync("./2-readFileSync.ts", "utf8");
4 | console.log(`Data length: ${data.length}`);
5 | console.log(data);
6 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/3-readWriteFileCb.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | fs.readFile("../assets/song.mp3", (err, data) => {
4 | if (err) console.log(err);
5 |
6 | console.log(data);
7 | console.log(data.length);
8 |
9 | fs.writeFile("../assets/mySong.mp3", data, (err) => {
10 | if (err) console.log(err);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/4-fsWatch.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | const watcher = fs.watch(
4 | "./assets/tmp.txt",
5 | { encoding: "utf-8" },
6 | (eventType, filename) => {
7 | // https://stackoverflow.com/questions/12978924/fs-watch-fired-twice-when-i-change-the-watched-file
8 | console.log(eventType);
9 |
10 | if (filename) {
11 | console.log(filename, "\n");
12 | }
13 | }
14 | );
15 |
16 | watcher.on("change", (event, filename) => {
17 | console.log(event, filename, "\n");
18 | });
19 |
20 | watcher.on("close", () => {
21 | console.log("closed!");
22 | });
23 |
24 | setTimeout(() => watcher.close(), 10000);
25 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/5-fsStat.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | (async function () {
4 | const stats = [];
5 |
6 | const filenames = ["../assets/tmp.json", "../assets/fileThatNotExists.txt"];
7 |
8 | filenames.forEach((file) => {
9 | stats.push(fs.promises.stat(file, { bigint: true }));
10 | });
11 |
12 | for (let i = 0; i < stats.length; i++) {
13 | try {
14 | await stats[i];
15 | } catch (e) {
16 | console.log("error!");
17 | }
18 | }
19 |
20 | console.log(stats, "stats");
21 | })();
22 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/6-dirRead.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | async function print(path: string) {
4 | const dir = await fs.promises.opendir(path);
5 |
6 | console.log(dir);
7 |
8 | for await (const dirent of dir) {
9 | console.log(dirent.name);
10 | console.log(dirent.name, dirent.isFile());
11 | // console.log(dirent.isDirectory());
12 | }
13 | }
14 |
15 | print("./").catch(console.error);
16 |
17 | function printCb(path) {
18 | fs.opendir(path, (err, dir) => {
19 | if (err) console.log(err);
20 |
21 | console.log(dir);
22 | });
23 | }
24 |
25 | // printCb('./');
26 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/7-openAndReadFile.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import { Buffer } from "buffer";
3 |
4 | fs.open(
5 | new URL(
6 | "file:///D:/JS/otus--javascript-basic/lessons/lesson49/code/assets/tmp.txt"
7 | ),
8 | "w",
9 | (err, fd) => {
10 | if (err) console.log(err);
11 |
12 | const buffer = Buffer.alloc(20);
13 | const offset = 0;
14 | const length = 20;
15 | const position = null;
16 |
17 | console.log(buffer, "before read");
18 |
19 | fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
20 | if (err) console.log(err);
21 |
22 | console.log(bytesRead, "number of bytes read");
23 | console.log(buffer, "after read");
24 | console.log(buffer.toString("hex"));
25 | });
26 | }
27 | );
28 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/8-fsWriteStream.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import { Buffer } from "buffer";
3 |
4 | // if (fs.existsSync("./files/write.txt")) {
5 | // fs.unlink("./files/write.txt", () => console.log("file deleted!"));
6 | // }
7 |
8 | const stream = fs.createWriteStream("../assets/write.txt");
9 |
10 | stream.on("ready", () => {
11 | stream.write("Hello I'm newborn file!:)", (err) => console.log(err));
12 |
13 | const buffer = Buffer.alloc(40);
14 | buffer.write("\nI'm writing buffer right now!");
15 |
16 | console.log(buffer);
17 |
18 | stream.write(buffer, (err) => {
19 | if (err) console.log(err);
20 | stream.close();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/9-fsReadStream.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | // const stream = fs.createReadStream("../assets/read.txt");
4 | const stream = fs.createReadStream("../assets/read.txt", {
5 | highWaterMark: 1000,
6 | });
7 |
8 | // stream.on("readable", () => {
9 | // console.log("readable");
10 | // // const data = stream.read();
11 | // const data = stream.read(1);
12 |
13 | // if (data) console.log(data.toString());
14 | // });
15 |
16 | // stream.on('data', (chunk: Buffer) => {
17 | // console.log('data');
18 | // console.log(chunk.toString())
19 | // })
20 |
21 | // stream.on('end', () => console.log('done'));
22 |
23 | (async function () {
24 | const stream = fs.createReadStream("../assets/read.txt");
25 |
26 | for await (const chunk of stream) {
27 | console.log(chunk.toString());
28 | }
29 | })();
30 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/examples/os.ts:
--------------------------------------------------------------------------------
1 | import os from "os";
2 |
3 | //Example-1
4 | // if (os.EOL === "\n") {
5 | // console.log(String.raw`You are working with line \n delimeter. Posix`);
6 | // } else if (os.EOL === `\r\n`) {
7 | // console.log(String.raw`You are working with line \r\n delimeter. Windows`);
8 | // }
9 |
10 | // Example-2
11 | //https://nodejs.org/dist/latest-v16.x/docs/api/os.html#os_os_constants_1
12 | // console.log(os.constants);
13 |
14 | // Example-3
15 | // 'arm', 'arm64', 'ia32', 'mips', ..., 'x32', 'x64'
16 | // console.log(os.arch());
17 |
18 | // Example-4
19 | // console.log(os.cpus());
20 |
21 | // Example-5
22 | // console.log(os.endianness());
23 |
24 | // Examples-6
25 | // console.table({
26 | // 'Memory in bytes': os.freemem(),
27 | // 'Mermoy in Kb': Math.round(os.freemem() / 1024),
28 | // 'Mermoy in Mb': Math.round(os.freemem() / 1024 / 1024),
29 | // 'Mermoy in Gb': Math.round(os.freemem() / 1024 / 1024 / 1024),
30 | // });
31 |
32 | // console.table({
33 | // 'Memory in bytes': os.totalmem(),
34 | // 'Mermoy in Kb': Math.round(os.totalmem() / 1024),
35 | // 'Mermoy in Mb': Math.round(os.totalmem() / 1024 / 1024),
36 | // 'Mermoy in Gb': Math.round(os.totalmem() / 1024 / 1024 / 1024),
37 | // });
38 |
39 | // Example-7
40 | // console.log(os.homedir());
41 |
42 | // Exammple-8
43 | // console.log(os.hostname());
44 |
45 | // Example-9
46 | // Unix-specific
47 | // console.log(os.loadavg());
48 |
49 | // Example-10
50 | // console.log(os.networkInterfaces());
51 |
52 | // Example-11
53 | // console.log(os.platform());
54 |
55 | // Example-12
56 | // console.log(os.release());
57 |
58 | // Example-13
59 | // console.log(os.tmpdir())
60 |
61 | // Example-14
62 | // console.log(os.type());
63 |
64 | // Example-15
65 | // console.table({
66 | // 'seconds': os.uptime(),
67 | // 'minutes': os.uptime() / 60,
68 | // 'hours': os.uptime() / 60 / 60,
69 | // 'days': os.uptime() / 60 / 60 / 60,
70 | // })
71 |
72 | // Example-15
73 | // console.log(os.userInfo());
74 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/cli.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import inquirer from "inquirer";
3 |
4 | import * as node from "./configs/tsconfig.node.json";
5 | import * as react from "./configs/tsconfig.react.json";
6 | import * as recommended from "./configs/tsconfig.recommended.json";
7 |
8 | const prompt = inquirer.createPromptModule();
9 |
10 | prompt([
11 | {
12 | type: "confirm",
13 | message: `Your current working directory is ${process.cwd()} do you want to proceed?`,
14 | name: "proceed",
15 | default: true,
16 | },
17 | ])
18 | .then(({ proceed }) => {
19 | if (!proceed) {
20 | console.log("\x1b[33m%s", "Bye!");
21 |
22 | process.exit(0);
23 | }
24 |
25 | prompt([
26 | {
27 | type: "list",
28 | message: "Pick the typescript configuration file for your needs:",
29 | name: "config",
30 | choices: ["recommended", "react", "node"],
31 | },
32 | {
33 | type: "input",
34 | message:
35 | "Enter the path to the directory you want the configuration file to be copied to:\n",
36 | name: "path",
37 | default: process.cwd(),
38 | validate: async (value) => {
39 | try {
40 | const stats = await fs.promises.stat(value);
41 |
42 | if (!stats.isDirectory()) {
43 | return "Not a directory!";
44 | }
45 | } catch (e) {
46 | return e;
47 | }
48 |
49 | return true;
50 | },
51 | },
52 | ]).then(({ path, config }) => {
53 | let fileToWrite = "";
54 | if (config === "node") {
55 | fileToWrite = JSON.stringify(node);
56 | } else if (config === "react") {
57 | fileToWrite = JSON.stringify(react);
58 | } else {
59 | fileToWrite = JSON.stringify(recommended);
60 | }
61 |
62 | fs.writeFile(path + "\\tsconfig.json", fileToWrite, () => {
63 | console.log("\x1b[32m%s\x1b[0m", "Config file succesfully copied!");
64 | });
65 | });
66 | })
67 | .catch((error) => {
68 | if (error.isTtyError) {
69 | console.log("Prompt couldn't be rendered in the current environment");
70 | } else {
71 | console.log("Something else went wrong");
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/configs/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Node 16",
4 |
5 | "compilerOptions": {
6 | "lib": ["es2021"],
7 | "module": "commonjs",
8 | "target": "es2021",
9 |
10 | "strict": true,
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 | "forceConsistentCasingInFileNames": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/configs/tsconfig.react.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Create React App",
4 |
5 | "compilerOptions": {
6 | "lib": ["dom", "dom.iterable", "esnext"],
7 | "module": "esnext",
8 | "target": "es5",
9 |
10 | "allowJs": true,
11 | "allowSyntheticDefaultImports": true,
12 | "esModuleInterop": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "isolatedModules": true,
15 | "jsx": "react-jsx",
16 | "moduleResolution": "node",
17 | "noEmit": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "resolveJsonModule": true,
20 | "skipLibCheck": true,
21 | "strict": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/configs/tsconfig.recommended.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2015",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true
9 | },
10 | "$schema": "https://json.schemastore.org/tsconfig",
11 | "display": "Recommended"
12 | }
13 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inquirer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "ts-node cli.ts",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "inquirer": "^8.1.2"
15 | },
16 | "devDependencies": {
17 | "@types/inquirer": "^7.3.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lessons/lesson49/code/inquirer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "module": "CommonJS",
5 | "target": "ES2020",
6 | "esModuleInterop": true,
7 | "resolveJsonModule": true
8 | },
9 | "include": ["*.ts"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/lessons/lesson49/images/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/images/arch.png
--------------------------------------------------------------------------------
/lessons/lesson49/images/nodejs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/images/nodejs.png
--------------------------------------------------------------------------------
/lessons/lesson50/homework.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Создаете многопользовательское приложение (уже сложнее, чем просто чат), включая серверную часть. Учитесь разворачивать приложение так, чтобы оно было доступно в сети. Тренируете навык работы с документацией незнакомых инструментов. Собираете вместе то, что вы изучили за время курса.
3 | ---
4 |
5 | ### "Создать приложение-игру крестики-нолики с поддержкой многопользовательской игры"
6 |
7 | В результате выполнения ДЗ вы создадите базовый WS-сервер, с поддержкой подключения клиентов, узнаете как общаться через WS и как настраивать выполнение серверного javascript. Потренируете навыки разработки в контексте многопользовательской работы и используете redux не только на клиенте
8 |
9 | #### Необходимо:
10 |
11 | - создать и настроить проект\*
12 | - создать приложение сервер, которое поддерживает подключение 2 пользователей на игру, и любого числа пользователей на просмотр
13 | - создать клиентское приложение, которое может работать с сервером, отображать ход игры и позволяет активным игрокам делать ходы
14 | - обработать ситуации, когда зрители пытаются сделать ход, или активный игрок ходит вне своей очереди
15 | - опубликовать работу с использованием любого сервиса публикации (например, Netlify или Render)
16 | - подготовить работу с сдаче\*
17 | - сделать ревью 2 других работ
18 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем
19 |
20 | #### Критерии оценки:
21 |
22 | - создано клиентское приложение, которое отображает ход игры - **1** балл
23 | - клиентское приложение поддерживает ввод ходов - **1** балл
24 | - клиентское приложение позволяет менять статус активный игрок/зритель - **1** балл
25 | - серверное приложение использует redux для управления логикой - **1** балл
26 | - серверное приложение обрабатывает и публикует ходы - **1** балл
27 | - серверное приложение корректно обрабатывает нестандартные ситуации - **1** балл
28 | - проверены 2 работы - **1** балл
29 |
30 | #### Задание не проверяется при не соответствии базовым требованиям к заданию!
31 |
32 | #### Принято - от 5 баллов
33 |
--------------------------------------------------------------------------------
/lessons/lesson50/images/bob_martin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/bob_martin.jpg
--------------------------------------------------------------------------------
/lessons/lesson50/images/exorcism.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/exorcism.gif
--------------------------------------------------------------------------------
/lessons/lesson50/images/jsguy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/jsguy.gif
--------------------------------------------------------------------------------
/lessons/lesson50/images/polnomochiya.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/polnomochiya.jpg
--------------------------------------------------------------------------------
/lessons/lesson50/images/ron_jeffries.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/ron_jeffries.jpg
--------------------------------------------------------------------------------
/lessons/lesson51/images/http.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/http.png
--------------------------------------------------------------------------------
/lessons/lesson51/images/middleware.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/middleware.jpg
--------------------------------------------------------------------------------
/lessons/lesson51/images/osi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/osi.jpeg
--------------------------------------------------------------------------------
/lessons/lesson52/code/ls.js:
--------------------------------------------------------------------------------
1 | // import fs from "fs";
2 | // const logger = console.log;
3 | // const [directory, pattern] = process.argv.slice(2);
4 |
5 | // if (["--help", "-h"].includes(directory)) {
6 | // logger("Usage: node ls.mjs [pattern]");
7 | // process.exit(0);
8 | // }
9 |
10 | // fs.readdir(directory, (err, files) => {
11 | // const regex = new RegExp(pattern, "g");
12 | // files.forEach((file) => {
13 | // if (!pattern || regex.test(file)) {
14 | // logger(file);
15 | // }
16 | // });
17 | // });
18 |
--------------------------------------------------------------------------------
/lessons/lesson52/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Sample node.js program",
3 | "private": true,
4 | "version": "0.1.0",
5 | "scripts": {
6 | "start": "node index.js",
7 | "dev": "nodemon index.js"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lessons/lesson52/code/practice/ls.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require("fs");
4 |
5 | const args = process.argv.slice(2);
6 | const logger = console.log;
7 |
8 | /**
9 | * Prints help message and aborts program
10 | */
11 | function help() {
12 | logger("Usage: node ls.js [filter-regex]");
13 | process.exit(0);
14 | }
15 |
16 | /**
17 | * Prints list of files in a directory
18 | * @param {string} dir
19 | * @param {string} pattern
20 | */
21 | function listFiles(dir, pattern) {
22 | fs.readdir(directory, (err, files) => {
23 | const regex = new RegExp(pattern, "g");
24 | files.forEach((file) => {
25 | if (!pattern || regex.test(file)) {
26 | logger(file);
27 | }
28 | });
29 | });
30 | }
31 |
32 | if (["--help", "-h"].includes(args[0])) {
33 | help();
34 | }
35 |
36 | const [directory, pattern] = args;
37 |
38 | listFiles(directory, pattern);
39 |
40 | /**
41 | * Example: ./ls.js . '.+json'
42 | * Example: ./ls.js . '.+js$'
43 | */
44 |
--------------------------------------------------------------------------------
/lessons/lesson52/code/practice/server.js:
--------------------------------------------------------------------------------
1 | const http = require("http");
2 | const url = require("url");
3 |
4 | function getTime(timeZone) {
5 | return new Date().toLocaleString("en-US", { timeZone });
6 | }
7 |
8 | // Create a server object
9 | const server = http.createServer((req, res) => {
10 | const parts = url.parse(req.url, true);
11 | const query = parts.query;
12 | res.write(`Current date is "${getTime(query.timezone)}"`); // Write a response to the client
13 | res.end(); // End the response
14 | });
15 |
16 | server.listen(8080);
17 |
18 | /**
19 | * Example: http://localhost:8080
20 | * Example: http://localhost:8080?timezone=Asia/Jakarta
21 | * Example: http://localhost:8080?timezone=America/New_York
22 | */
23 |
--------------------------------------------------------------------------------
/lessons/lesson52/code/sample.js:
--------------------------------------------------------------------------------
1 | const { createServer } = require("http");
2 | const fs = require("fs");
3 |
4 | // Create a server object
5 | const server = createServer((req, res) => {
6 | res.write("Hello World ??"); // Write a response to the client
7 | res.end(); // End the response
8 | });
9 |
10 | server.listen(8080);
11 |
--------------------------------------------------------------------------------
/lessons/lesson52/hw1.md:
--------------------------------------------------------------------------------
1 | ---
2 | Зачем: Тренируемся писать консольные приложения и публиковать их в Github (вы уже делали это с API, но инструменты командной строки немного отличаются). Учимся работать с файловой системой и запуском команд из nodejs.
3 | ---
4 |
5 | **Необходимо:**
6 |
7 | - создать и настроить проект
8 |
9 | - реализовать скрипт командной строки, который поддерживает ввод параметров
10 | -- через аргументы
11 | -- через интерфейс с запросами в консоли
12 |
13 | - позволяет опубликовать проект (собрать его при необходимости) на github pages с заданными параметрами
14 | - команда для сборки (если нужно)
15 | - директория для публикации
16 | - репозиторий для публикации
17 | - настройки доступа к репозиторию
18 |
19 | - опубликовать, чтобы можно было использовать через npx
20 | - оформить README с подробным описанием и gif с демонстрацией работы, списком команд для использования
21 |
22 | - подготовить работу с сдаче
23 | - сделать ревью 2 других работ
24 |
25 | В чат с преподавателем сбросить ссылку на репозиторий и на опубликованный пакет. Задание принято если выполнены все пункты:
26 |
27 | - создан пакет, реализующий нужный функционал
28 | - открыт PR
29 | - код прошел линтинг (и тесты)
30 | - проект опубликован на npmjs
31 | - пакет работает без установки (через npx)
32 | - вместе со ссылками сброшены ссылки на 2 PR других студентов, на которые сделано ревью (если ссылки не удается найти в чате, обратиться к преподавателям)
33 |
--------------------------------------------------------------------------------
/lessons/lesson52/hw2.md:
--------------------------------------------------------------------------------
1 | **Задание:**
2 |
3 | Привести в порядок репозитории с заданиями курса:
4 |
5 | - Добавить описание README, которое должно содержать
6 | - описание проекта (что, зачем)
7 | - инструкция по локальному запуску (как склонировать, установить и запустить)
8 | - ссылки на задеплоенное приложение или на codesandbox
9 | - бэдж со статусом workflow по проверке линтерами и тестами
10 |
11 | В чат с преподавателем сбросить ссылки на репозитории. Задание принято от 4х репозиториев.
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "otus--javascript-basic",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "reveal-md",
9 | "lint:prettier": "prettier --check .",
10 | "lint:spell": "yaspeller .",
11 | "lint": "yarn lint:prettier && yarn lint:code",
12 | "lint:code": "eslint --ext .js,.ts,.md .",
13 | "prepare": "husky install"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "ISC",
18 | "devDependencies": {
19 | "@typescript-eslint/eslint-plugin": "^5.40.1",
20 | "@typescript-eslint/parser": "^5.40.1",
21 | "eslint": "^8.26.0",
22 | "eslint-plugin-markdown": "^3.0.0",
23 | "eslint-plugin-react": "^7.31.10",
24 | "husky": "^8.0.1",
25 | "lint-staged": "^13.0.3",
26 | "prettier": "^2.7.1",
27 | "reveal-md": "^5.3.4",
28 | "typescript": "^4.8.4",
29 | "yaspeller": "^9.1.0"
30 | },
31 | "lint-staged": {
32 | "*.{js,json,html,css,md,yml,yaml,ts,tsx}": "prettier --write",
33 | "{*js,*.md}": "eslint --cache --fix",
34 | "*.md": "echo 'yaspeller is disabled'"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/plugins.js:
--------------------------------------------------------------------------------
1 | options.dependencies = options.dependencies || [];
2 |
3 | options.dependencies.push(
4 | // https://github.com/naamor/reveal.js-tableofcontents
5 | {
6 | src: window.location.href.includes("vvscode")
7 | ? "../_assets/plugins/tableofcontents.js"
8 | : "_assets/plugins/tableofcontents.js",
9 | }
10 | );
11 |
--------------------------------------------------------------------------------
/preparation/tasks.md:
--------------------------------------------------------------------------------
1 | ## Линейные алгоритмы
2 |
3 | 1. В переменных a и b хранятся числа. Написать программу, которая выводит в консоль произведение и сумму этих чисел.
4 |
5 | 2. В двух переменных хранятся строки символов. Написать программу, которая выведет в консоль суммарное количество символов в обоих строках.
6 |
7 | 3. Написать программу, которая запрашивает у пользователя ввод трёхзначного числа, а потом выводит в консоль сумму цифр введённого числа.
8 |
9 | ## Условия
10 |
11 | 1. В переменных a и b хранятся числа. Вывести в консоль наибольшее из них.
12 |
13 | 2. Запросить у пользователя ввод числа от 1 до 12. Вывести в консоль название месяца, соответствующее этому числу (1 — январь, 2 — февраль и т.д.).
14 |
15 | 3. В переменных circle и square хранятся площади круга и квадрата соответственно. Написать программу, которая определяет, поместится ли круг в квадрат.
16 |
17 | ## Циклы
18 |
19 | 1. Вывести в консоль сумму всех целых чисел от 50 до 100.
20 |
21 | 2. Вывести в консоль таблицу умножения на 7.
22 |
23 | ```
24 | 7 x 1 = 7
25 | 7 x 2 = 14
26 | …
27 | 7 x 9 = 63
28 | ```
29 |
30 | 3. Запросить у пользователя ввод числа N. Вывести в консоль среднее арифметическое всех нечётных чисел от 1 до N.
31 |
32 | ## Объекты
33 |
34 | Создайте объект user, содержащий поле name со
35 | значением ‘John’.
36 |
37 | 1. Запросить у пользователя ввод числа. Записать введенное значение в поле age объекта user.
38 |
39 | 2. Создать копию объекта user с именем admin. Добавить новому объекту поле role со значением ‘admin’.
40 |
41 | 3. Записать все значения полей объекта admin в
42 | отдельные переменные. Имена переменных
43 | должны совпадать с названиями полей
44 |
45 | ## Массивы
46 |
47 | Создайте массив целых чисел из 10 элементов.
48 |
49 | 1. Выведите в консоль сумму всех элементов массива.
50 |
51 | 2. Создайте новый массив на основе исходного, в котором каждый элемент будет вдвое больше элемента исходного массива с таким же индексом. (a[1] = 3, b[1] = 6, где a — исходный массив, b — новый массив).
52 |
53 | 3. Найдите и выведите в консоль наибольший и
54 | наименьший элементы исходного массива.
55 |
56 | ## Функции
57 |
58 | 1. Напишите функцию diff, которая получает в качестве
59 | параметров 2 числа и возвращает разницу между
60 | наибольшим и наименьшим.
61 |
62 | 2. Напишите функцию isWord, которая принимает на
63 | вход текстовую строку. Функция возвращает true, если
64 | строка состоит из одного слова и false, если из
65 | нескольких.
66 |
67 | 3. Напишите функцию pow(a, x), которая вернёт
68 | значение числа a, возведённого в степень x.
69 |
--------------------------------------------------------------------------------
/reveal-md.json:
--------------------------------------------------------------------------------
1 | {
2 | "separator": "",
3 | "verticalSeparator": "",
4 | "theme": "white",
5 | "watch": true,
6 | "css": ".reveal-md/styles.css",
7 | "listingTemplate": ".reveal-md/listing.html",
8 | "scripts": ["plugins.js"]
9 | }
10 |
--------------------------------------------------------------------------------
/reveal.json:
--------------------------------------------------------------------------------
1 | {
2 | "slideNumber": true,
3 | "hash": true,
4 | "history": false,
5 | "backgroundTransition": "fade",
6 | "width": "80%",
7 | "tableofcontents": {
8 | "title": "План",
9 | "position": 1000
10 | }
11 | }
12 |
--------------------------------------------------------------------------------