├── .babelrc
├── .github
├── dependabot.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .husky
└── pre-commit
├── .nvmrc
├── .prettierrc
├── README.md
├── TPs
├── TP-01
│ ├── Hello.js
│ ├── index.js
│ └── readme.txt
├── TP-02
│ ├── ShoppingList.js
│ └── index.js
├── TP-03
│ ├── Student.js
│ ├── StudentDetails.js
│ ├── StudentFilter.js
│ ├── StudentsApp.js
│ ├── StudentsTable.js
│ └── index.js
├── TP-04
│ ├── Item.js
│ ├── ShoppingItem.js
│ ├── ShoppingList.js
│ ├── actions
│ │ └── items.js
│ ├── index.js
│ └── reducers
│ │ ├── index.js
│ │ └── items.js
├── TP-05
│ ├── Item.js
│ ├── ShoppingItem.js
│ ├── ShoppingList.js
│ ├── apis
│ │ └── items.js
│ └── index.js
├── TP-06
│ ├── App.js
│ ├── Context.js
│ ├── User.js
│ └── index.js
├── TP-07
│ ├── Student.js
│ ├── StudentDetails.js
│ ├── StudentDetails.test.js
│ ├── StudentFilter.js
│ ├── StudentFilter.test.js
│ ├── StudentsApp.js
│ ├── StudentsApp.test.js
│ ├── StudentsTable.js
│ ├── StudentsTable.test.js
│ └── index.js
├── TP-08
│ ├── About.js
│ ├── App.js
│ ├── Home.js
│ ├── index.js
│ └── users
│ │ ├── UserDetails.js
│ │ └── Users.js
├── TP-09
│ ├── App.js
│ ├── Button.js
│ └── index.js
├── demos
│ ├── controlled
│ │ ├── NameForm.js
│ │ └── index.js
│ ├── fetch
│ │ ├── ShoppingItem.js
│ │ ├── ShoppingList.js
│ │ └── index.js
│ ├── futur
│ │ ├── index.html
│ │ ├── index.txt
│ │ └── lazy-suspense
│ │ │ ├── Goodbye.js
│ │ │ └── index.js
│ ├── indexAsKeyBug
│ │ ├── ProblemWithIndexAsKey.js
│ │ └── index.js
│ ├── middleware
│ │ ├── Item.js
│ │ ├── ShoppingItem.js
│ │ ├── ShoppingList.js
│ │ ├── actions
│ │ │ └── items.js
│ │ ├── index.js
│ │ ├── middleware.js
│ │ └── reducers
│ │ │ ├── index.js
│ │ │ └── items.js
│ ├── optimisation-key
│ │ ├── App.js
│ │ └── index.js
│ ├── portal
│ │ ├── App.js
│ │ └── index.js
│ ├── redux toolkit
│ │ ├── Item.js
│ │ ├── ShoppingItem.js
│ │ ├── ShoppingList.js
│ │ ├── index.js
│ │ ├── slice
│ │ │ └── items.js
│ │ └── store
│ │ │ └── index.js
│ └── useEffect
│ │ ├── App.js
│ │ ├── Menu.js
│ │ ├── Routes
│ │ ├── Home.js
│ │ ├── Route1.js
│ │ └── Route2.js
│ │ └── index.js
├── index.html
└── solutions
│ ├── TP-01
│ ├── Hello.js
│ └── index.js
│ ├── TP-02
│ ├── ShoppingItem.js
│ ├── ShoppingList.js
│ └── index.js
│ ├── TP-03
│ ├── Student.js
│ ├── StudentDetails.js
│ ├── StudentFilter.js
│ ├── StudentsApp.js
│ ├── StudentsTable.js
│ └── index.js
│ ├── TP-04
│ ├── Item.js
│ ├── ShoppingItem.js
│ ├── ShoppingList.js
│ ├── actions
│ │ └── items.js
│ ├── index.js
│ └── reducers
│ │ ├── index.js
│ │ └── items.js
│ ├── TP-05
│ ├── Item.js
│ ├── ShoppingItem.js
│ ├── ShoppingList.js
│ ├── apis
│ │ └── items.js
│ └── index.js
│ ├── TP-06
│ ├── App.js
│ ├── Context.js
│ ├── User.js
│ └── index.js
│ ├── TP-07
│ ├── Student.js
│ ├── StudentDetails.js
│ ├── StudentDetails.test.js
│ ├── StudentFilter.js
│ ├── StudentFilter.test.js
│ ├── StudentsApp.js
│ ├── StudentsApp.test.js
│ ├── StudentsTable.js
│ ├── StudentsTable.test.js
│ ├── __snapshots__
│ │ └── StudentsApp.test.js.snap
│ └── index.js
│ ├── TP-08
│ ├── About.js
│ ├── App.js
│ ├── Home.js
│ ├── index.js
│ └── users
│ │ ├── UserDetails.js
│ │ └── Users.js
│ └── TP-09
│ ├── App.js
│ ├── Button.js
│ └── index.js
├── cypress
├── cypress.config.js
└── e2e
│ └── tps.cy.js
├── images
├── coffee.gif
├── components.png
├── flux-bestpractice.png
├── flux.png
├── mynameis.jpg
├── postits.jpg
├── react.svg
├── redux.png
├── should-component-update.png
└── ts.svg
├── index.html
├── itest
├── index.html
└── webpack.config.itest.js
├── package-lock.json
├── package.json
├── public
├── anna.jpeg
├── elsa.jpeg
├── favicon.ico
├── items.json
└── students.json
├── reveal
├── plugin
│ ├── highlight
│ │ ├── highlight.esm.js
│ │ ├── highlight.js
│ │ ├── monokai.css
│ │ ├── plugin.js
│ │ └── zenburn.css
│ ├── markdown
│ │ ├── markdown.esm.js
│ │ ├── markdown.js
│ │ └── plugin.js
│ ├── math
│ │ ├── katex.js
│ │ ├── math.esm.js
│ │ ├── math.js
│ │ ├── mathjax2.js
│ │ ├── mathjax3.js
│ │ └── plugin.js
│ ├── notes
│ │ ├── notes.esm.js
│ │ ├── notes.js
│ │ ├── plugin.js
│ │ └── speaker-view.html
│ ├── search
│ │ ├── plugin.js
│ │ ├── search.esm.js
│ │ └── search.js
│ └── zoom
│ │ ├── plugin.js
│ │ ├── zoom.esm.js
│ │ └── zoom.js
├── reset.css
├── reveal.css
├── reveal.esm.js
├── reveal.js
└── theme
│ ├── beige.css
│ ├── black.css
│ ├── blood.css
│ ├── fonts
│ ├── league-gothic
│ │ ├── LICENSE
│ │ ├── league-gothic.css
│ │ ├── league-gothic.eot
│ │ ├── league-gothic.ttf
│ │ └── league-gothic.woff
│ └── source-sans-pro
│ │ ├── LICENSE
│ │ ├── source-sans-pro-italic.eot
│ │ ├── source-sans-pro-italic.ttf
│ │ ├── source-sans-pro-italic.woff
│ │ ├── source-sans-pro-regular.eot
│ │ ├── source-sans-pro-regular.ttf
│ │ ├── source-sans-pro-regular.woff
│ │ ├── source-sans-pro-semibold.eot
│ │ ├── source-sans-pro-semibold.ttf
│ │ ├── source-sans-pro-semibold.woff
│ │ ├── source-sans-pro-semibolditalic.eot
│ │ ├── source-sans-pro-semibolditalic.ttf
│ │ ├── source-sans-pro-semibolditalic.woff
│ │ └── source-sans-pro.css
│ ├── league.css
│ ├── moon.css
│ ├── night.css
│ ├── serif.css
│ ├── simple.css
│ ├── sky.css
│ ├── solarized.css
│ └── white.css
├── src
└── .gitignore
├── typescript.html
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "chrome": "70"
8 | }
9 | }
10 | ],
11 | [
12 | "@babel/preset-react",
13 | {
14 | "runtime": "automatic"
15 | }
16 | ]
17 | ],
18 | "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-syntax-dynamic-import"]
19 | }
20 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | time: "06:00"
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches:
8 | - master
9 |
10 | env:
11 | CYPRESS_CACHE_FOLDER: ~/cache/Cypress npm install
12 |
13 | jobs:
14 | test:
15 | name: Tests
16 | runs-on: ubuntu-latest
17 | container: cypress/browsers:node18.12.0-chrome107
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: actions/setup-node@v3
22 | with:
23 | node-version: '18'
24 | cache: 'npm'
25 | - name: Cache node modules
26 | id: cache-node-modules
27 | uses: actions/cache@v3
28 | with:
29 | path: node_modules
30 | key: npm-cache-${{ hashFiles('package-lock.json') }}
31 | - name: Cache Cypress Binary
32 | id: cache-cypress-binary
33 | uses: actions/cache@v3
34 | with:
35 | path: ~/cache/Cypress
36 | key: cypress-cache-${{ hashFiles('package-lock.json') }}
37 | - name: Install Dependencies
38 | if: steps.cache-node-modules.outputs.cache-hit != 'true'
39 | run: npm ci -f --no-audit --ignore-scripts
40 | - name: Run unit tests of TP07
41 | run: ./node_modules/.bin/jest --roots TPs/solutions/TP-07
42 | - name: Cypress
43 | uses: cypress-io/github-action@v5
44 | with:
45 | config-file: cypress/cypress.config.js
46 | install-command: npx cypress install
47 | start: npm run starti
48 | wait-on: 'http://localhost:8080'
49 | browser: chrome
50 | - name: Archive test screenshots
51 | uses: actions/upload-artifact@v2.2.4
52 | with:
53 | name: screenshots
54 | path: cypress/screenshots
55 | if: ${{ failure() }}
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | coverage
8 |
9 | # production
10 | build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log
16 | .idea/
17 | yarn.lock
18 | yarn-error.log
19 | *.iml
20 | /cypress/videos/
21 | /cypress/screenshots/
22 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.12.0
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "endOfLine": "auto",
5 | "jsxBracketSameLine": false,
6 | "printWidth": 120,
7 | "proseWrap": "preserve",
8 | "semi": false,
9 | "singleQuote": true,
10 | "tabWidth": 2,
11 | "trailingComma": "es5",
12 | "useTabs": false,
13 | "requirePragma": false
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Formation react js avec ES6
2 |
3 | Les slides: [https://formation-reactjs.fr](https://formation-reactjs.fr)
4 |
5 | La formation se déroule sur 3 jours, n'hésitez pas à me contacter: jbcazaux@gmail.com !
6 |
7 | 
8 |
9 | ## Objectifs de la formation
10 |
11 | - Savoir développer et tester des fonctionnalités sur une application ReactJS.
12 | - Connaître l'état de l'art et les bonnes pratiques React et javascript, notamment savoir utiliser les hooks.
13 | - Avoir les connaissances suffisantes pour démarrer le développement d'une application ReactJS.
14 | - Comprendre et anticiper les évolutions de ReactJS qui vont arriver dans les prochains mois.
15 |
16 | ## Prérequis
17 |
18 | - Avoir déjà développé sur un projet en JS (Angular, Vue, jQuery, ...).
19 | - Connaissances de base de HTML / CSS.
20 |
21 | ## Plan de formation:
22 |
23 | #### Rappels
24 |
25 | objectif : Avoir un vocabulaire commun et connaitre les éléments du langage les plus couramment utilisés.
26 | - ES7-ES2022 (nouveautés du langage).
27 | - Programmation fonctionnelle (High order function, ...).
28 |
29 | ### React
30 |
31 | #### Principes de base
32 |
33 | objectif : Comprendre l'intérêt de react par rapport à ses concurrents et la façon dont il a été pensé.
34 |
35 | #### Composants (functionnal components, hooks et cycle de vie)
36 |
37 | objectif : Les composants sont les éléments de base de la librairie. Apprendre à les écrire et découper sa page en composants réutilisables. Savoir écrire les composants sous forme de fonctions en utilisant les hooks.
38 |
39 | #### Etats des composants
40 |
41 | objectif : Comment garder de la donnée au sein d'un composant ou l'échanger entre composants.
42 |
43 | #### Redux
44 |
45 | objectifs :
46 | - Comprendre les limites de l'utilisation des états des composants,
47 | - Échanger de la donnée entre plusieurs composants,
48 | - Comprendre l intérêt du pattern redux par rapport aux concurrents (two-way binding par exemple)
49 | - Principes du pattern (flux unidirectionnel, store, reducer, actions, ...)
50 |
51 | #### React-query
52 |
53 | objectif : Utiliser ReactQuery plutôt que Redux pour gérer les données de l'application.
54 |
55 | #### React-router
56 |
57 | objectif : Construire une Single Page Application.
58 |
59 | #### Tests / debugging
60 |
61 | objectif : Produire une application de qualité avec des tests unitaires et des tests de composants.
62 |
63 | #### Optimisations des applications (vitesse, mémoire, ...)
64 |
65 | objectif : Avoir en tête le fonctionnement de ReactJS et les premières choses à mettre en place pour éviter des ralentissements. Utiliser des outils pour voir où intervenir dans le code en priorité.
66 |
67 | ## Licence
68 |
69 | [](http://creativecommons.org/licenses/by-sa/3.0/deed.fr)
70 |
71 | Ce(tte) œuvre est mise à disposition selon les termes de la [Licence Creative Commons. Attribution - Partage dans les Mêmes Conditions 3.0 non transposé](http://creativecommons.org/licenses/by-sa/3.0/deed.fr).
72 |
73 | Copyright (C) 2016-2022 Jean-Baptiste CAZAUX.
74 |
75 | ### Explications licence CC BY-SA 3.0
76 |
77 | Cette licence permet aux autres de remixer, arranger, et adapter votre œuvre, même à des fins commerciales, tant qu’on vous accorde le mérite en citant votre nom et qu’on diffuse les nouvelles créations selon des conditions identiques.
78 |
79 | Cette licence est souvent comparée aux licences de logiciels libres, “open source” ou “copyleft”.
80 |
81 | Toutes les nouvelles œuvres basées sur les vôtres auront la même licence, et toute œuvre dérivée pourra être utilisée même à des fins commerciales.
82 |
83 | C’est la licence utilisée par Wikipédia ; elle est recommandée pour des œuvres qui pourraient bénéficier de l’incorporation de contenu depuis Wikipédia et d’autres projets sous licence similaire.
84 |
--------------------------------------------------------------------------------
/TPs/TP-01/Hello.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/TPs/TP-01/Hello.js
--------------------------------------------------------------------------------
/TPs/TP-01/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/TPs/TP-01/index.js
--------------------------------------------------------------------------------
/TPs/TP-01/readme.txt:
--------------------------------------------------------------------------------
1 | installer:
2 |
3 | - git bash
4 | - node [9 | 10 | 11]
5 | - webstorm EAP / Visual Studio Code
--------------------------------------------------------------------------------
/TPs/TP-02/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 |
4 | const ShoppingList = props => {
5 | // TODO : créer un state pour stocker la liste d items
6 |
7 | useEffect(() => {
8 | axios
9 | .get('./items.json')
10 | .then(resp => resp.data)
11 | .then(fetchedItems => {
12 | // TODO: enregistrer les items dans le state
13 | })
14 | }, [])
15 |
16 | return (
17 |
18 |
TODO: Title
19 |
22 |
23 | )
24 | }
25 |
26 | export default ShoppingList
27 |
--------------------------------------------------------------------------------
/TPs/TP-02/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import ShoppingList from './ShoppingList'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/TP-03/Student.js:
--------------------------------------------------------------------------------
1 | class Student {
2 | constructor(id, lastname, firstname, grades) {
3 | this.id = id
4 | this.lastname = lastname
5 | this.firstname = firstname
6 | this.grades = grades
7 | }
8 | }
9 |
10 | export default Student
11 |
--------------------------------------------------------------------------------
/TPs/TP-03/StudentDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const StudentDetails = ({ student }) => {"Détail de l'étudiant"}
4 |
5 | export default StudentDetails
6 |
--------------------------------------------------------------------------------
/TPs/TP-03/StudentFilter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Input, InputLabel } from '@mui/material'
4 |
5 | const StudentFilter = ({ onChange }) => {
6 | const onFilterChange = e => {
7 | //TODO call the callback function provided by the parent
8 | }
9 |
10 | return (
11 |
24 | )
25 | }
26 |
27 | export default StudentFilter
28 |
--------------------------------------------------------------------------------
/TPs/TP-03/StudentsApp.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import StudentFilter from './StudentFilter'
4 | import StudentsTable from './StudentsTable'
5 | import StudentDetails from './StudentDetails'
6 | import Student from './Student'
7 |
8 | const filterStudents = (students, filter) => {
9 | // TODO: retourner une liste filrée des étudiants, plutot que la liste entière
10 | return students
11 | }
12 |
13 | const StudentsApp = () => {
14 | // TODO créer un state pour stocker la liste des étudiants
15 | // TODO créer un state pour stocker le filtre (c'est une chaine de caractères)
16 | // TODO créer un state pour stocker l'étudiant actuellement sélectionné (null par défaut)
17 |
18 | useEffect(() => {
19 | axios.get('students.json').then(({ data }) => {
20 | // TODO: mettre le résultat de la requete dans le state 'students'
21 | })
22 | }, [])
23 |
24 | const handleFilterChange = newFilter => {
25 | // TODO stocker la nouvelle valeur du filtre
26 | }
27 |
28 | const handleSelectStudent = newSelectedStudent => {
29 | // TODO stocker le nouvel étudiant sélectionné
30 | }
31 |
32 | return (
33 | <>
34 | TODO: Décommenter les composants enfants au fur et à mesure : StudentsTable, StudentFilter puis StudentDetails
35 | {/* */}
36 | {/* */}
37 | {/* */}
38 | >
39 | )
40 | }
41 |
42 | export default StudentsApp
43 |
--------------------------------------------------------------------------------
/TPs/TP-03/StudentsTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const StudentsTable = ({ students, onSelectStudent }) => {
4 | // TODO: 1 - Faire la table en html
5 | // TODO: 2 - Remplacer la table html par le composant Table de material-ui
6 | return
7 | }
8 |
9 | export default StudentsTable
10 |
--------------------------------------------------------------------------------
/TPs/TP-03/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import StudentsApp from './StudentsApp'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/TP-04/Item.js:
--------------------------------------------------------------------------------
1 | export default class Item {
2 | constructor(id, label, price) {
3 | this.id = id
4 | this.label = label
5 | this.price = price
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/TPs/TP-04/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | export default ShoppingItem
10 |
--------------------------------------------------------------------------------
/TPs/TP-04/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Item from './Item'
3 | import ShoppingItem from './ShoppingItem'
4 | import { setItems, addItem } from './actions/items'
5 |
6 | const ShoppingList = ({ title }) => {
7 | const [newItemLabel, setNewItemLabel] = useState('')
8 | const [newItemPrice, setNewItemPrice] = useState(0)
9 |
10 | useEffect(() => {
11 | // TODO: Créer une action grace à l'action creator 'setItems' puis la dispatcher pour que le store soit mis à jour
12 | const action = setItems([
13 | new Item(1, 'pain', 0.95),
14 | new Item(2, 'gel douche', 2.85),
15 | new Item(3, 'cahier à spirales', 1.2),
16 | ])
17 | dispatch(action)
18 | }, [])
19 |
20 | const handleAddItem = event => {
21 | //TODO dispatcher l'action créée par l'action creator 'addItem'
22 | }
23 |
24 | return (
25 |
26 |
{title}
27 |
28 | {/*items.map(item => (
29 |
30 | ))*/}
31 |
32 |
37 |
38 | )
39 | }
40 |
41 | export default ShoppingList
42 |
--------------------------------------------------------------------------------
/TPs/TP-04/actions/items.js:
--------------------------------------------------------------------------------
1 | // TODO: Ecrire un 'action creator' qui prend en parametre la liste des items et qui retourne
2 | // une action redux (objet avec type et un payload)
3 | export const setItems = null
4 |
--------------------------------------------------------------------------------
/TPs/TP-04/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import { Provider } from 'react-redux'
3 | import { createStore } from 'redux'
4 | import { reducer } from './reducers/index'
5 | import ShoppingList from './ShoppingList'
6 |
7 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
8 |
9 | const container = document.getElementById('root')
10 | const root = createRoot(container)
11 | root.render(
12 |
13 |
14 |
15 | )
16 |
--------------------------------------------------------------------------------
/TPs/TP-04/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | // TODO Ecrire un reducer qui gère le type d'action SET_ITEMS, puis ajouter ce reducer au 'global reducer'
4 | export const reducer = combineReducers({})
5 |
--------------------------------------------------------------------------------
/TPs/TP-04/reducers/items.js:
--------------------------------------------------------------------------------
1 |
2 | // TODO Ecrire un reducer qui gère le type d'action SET_ITEMS, puis ajouter ce reducer au 'global reducer'
3 |
4 | const items = null // TODO
--------------------------------------------------------------------------------
/TPs/TP-05/Item.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default class Item {
4 | constructor(id, label, price) {
5 | this.id = id
6 | this.label = label
7 | this.price = price
8 | }
9 | }
10 |
11 | Item.propTypes = PropTypes.shape({
12 | id: PropTypes.number,
13 | label: PropTypes.string.isRequired,
14 | price: PropTypes.number.isRequired,
15 | })
16 |
--------------------------------------------------------------------------------
/TPs/TP-05/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import Item from './Item'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | ShoppingItem.propTypes = {
10 | item: Item.propTypes.isRequired,
11 | }
12 |
13 | export default ShoppingItem
14 |
--------------------------------------------------------------------------------
/TPs/TP-05/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import Item from './Item'
3 | import ShoppingItem from './ShoppingItem'
4 | import PropTypes from 'prop-types'
5 | import { useMutation, useQuery } from '@tanstack/react-query'
6 | import itemsApi from './apis/items'
7 | import { CircularProgress } from '@mui/material'
8 |
9 | export const ShoppingList = ({ title }) => {
10 | const [newItemLabel, setNewItemLabel] = useState('')
11 | const [newItemPrice, setNewItemPrice] = useState(0)
12 |
13 | const items = [] // TODO 1: Utiliser useQuery pour récupérer la liste des items
14 | // TODO 2: rajouter une mutation avec useMutation
15 |
16 | const createNewItem = async e => {
17 | e.preventDefault()
18 | setNewItemLabel('')
19 | setNewItemPrice(0)
20 |
21 | // TODO 3: appeler ici la mutation qui a été créée
22 | }
23 |
24 | return (
25 |
26 |
{title}
27 |
28 | {items.map(item => (
29 |
30 | ))}
31 |
32 |
37 |
38 | )
39 | }
40 |
41 | ShoppingList.propTypes = {
42 | title: PropTypes.string.isRequired,
43 | }
44 |
--------------------------------------------------------------------------------
/TPs/TP-05/apis/items.js:
--------------------------------------------------------------------------------
1 | import itemsArray from 'public/items.json'
2 |
3 | const delay = async (value, duration) => new Promise(resolve => setTimeout(resolve, duration, value))
4 |
5 | let allItems = itemsArray
6 |
7 | const items = {
8 | get: async () => {
9 | //const { data } = await axios.get('items.json')
10 | // return data
11 |
12 | // fake implementation :
13 | return await delay(allItems, 100)
14 | },
15 | create: async item => {
16 | //await axios.post('/items')
17 |
18 | // fake implementation :
19 | allItems = allItems.concat({ id: item.id, label: item.label, price: item.price })
20 | await delay(null, 100)
21 | },
22 | }
23 |
24 | export default items
25 |
--------------------------------------------------------------------------------
/TPs/TP-05/index.js:
--------------------------------------------------------------------------------
1 | import { ShoppingList } from './ShoppingList'
2 | import { createRoot } from 'react-dom/client'
3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4 |
5 | const queryClient = new QueryClient()
6 |
7 | const container = document.getElementById('root')
8 | const root = createRoot(container)
9 | root.render(
10 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/TPs/TP-06/App.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import User from './User'
3 |
4 | const Small = () => {
5 | const { color, user } = useContext() //TODO: get the context !
6 |
7 | return (
8 |
9 | Small, user = {user.id}-{user.login}
10 |
11 | )
12 | }
13 |
14 | const Medium = () => (
15 |
16 | Medium
17 |
18 |
19 | )
20 |
21 | const Large = () => (
22 |
23 | Large
24 |
25 |
26 | )
27 |
28 | const App = () => (
29 | /* TODO: Set a context around 'Large'*/
30 |
31 | )
32 |
33 | export default App
34 |
--------------------------------------------------------------------------------
/TPs/TP-06/Context.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default React.createContext({})
4 |
--------------------------------------------------------------------------------
/TPs/TP-06/User.js:
--------------------------------------------------------------------------------
1 | export default class User {
2 | constructor(id, login) {
3 | this.id = id
4 | this.login = login
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TPs/TP-06/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/TP-07/Student.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | class Student {
4 | constructor(id, lastname, firstname, grades) {
5 | this.id = id
6 | this.lastname = lastname
7 | this.firstname = firstname
8 | this.grades = grades
9 | }
10 | }
11 |
12 | Student.propTypes = PropTypes.shape({
13 | firstname: PropTypes.string.isRequired,
14 | grades: PropTypes.arrayOf(PropTypes.number).isRequired,
15 | id: PropTypes.number.isRequired,
16 | lastname: PropTypes.string.isRequired,
17 | })
18 |
19 | export default Student
20 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentDetails.js:
--------------------------------------------------------------------------------
1 | import Student from './Student'
2 | import FormLabel from '@mui/material/FormLabel'
3 |
4 | const StudentDetails = ({ student }) => (
5 |
6 | {student ? (
7 | {[student.firstname, ' ', student.lastname]}
8 | ) : (
9 | Aucun étudiant sélectionné !
10 | )}
11 |
12 | )
13 |
14 | StudentDetails.propTypes = {
15 | student: Student.propTypes,
16 | }
17 |
18 | export default StudentDetails
19 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentDetails.test.js:
--------------------------------------------------------------------------------
1 | import StudentDetails from './StudentDetails'
2 | import { render, screen } from '@testing-library/react'
3 | import Student from './Student'
4 |
5 | describe('StudentDetails', () => {
6 | it('renders with no Student', () => {
7 | // Given
8 | // Then (snapshot ou getByText)
9 | })
10 |
11 | it('renders with a Student', () => {
12 | // Given
13 | // Then (snapshot ou getByText)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentFilter.js:
--------------------------------------------------------------------------------
1 | import Input from '@mui/material/Input'
2 | import PropTypes from 'prop-types'
3 |
4 | const StudentFilter = ({ onChange }) => {
5 | const onFilterChange = e => {
6 | onChange(e.target.value)
7 | }
8 |
9 | return (
10 |
20 | )
21 | }
22 |
23 | StudentFilter.propTypes = {
24 | onChange: PropTypes.func.isRequired,
25 | }
26 |
27 | export default StudentFilter
28 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentFilter.test.js:
--------------------------------------------------------------------------------
1 | import Filter from './StudentFilter'
2 | import { render, fireEvent } from '@testing-library/react'
3 |
4 | describe('Filter', () => {
5 | it('renders Filter and calls passed function when filter text has changed', () => {
6 | // Given
7 | const mockOnChange = jest.fn()
8 | const { container } = render( )
9 | const input = null // TODO : comment récupérer l'élément input ?
10 |
11 | // When
12 | // TODO : enovoyer un événement change sur l'élément input
13 |
14 | // Then
15 | // TODO : s'assurer que le callback onChange a été appelé avec le bon paramètre
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentsApp.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import StudentFilter from './StudentFilter'
4 | import StudentsTable from './StudentsTable'
5 | import StudentDetails from './StudentDetails'
6 |
7 | export const filterStudents = (students, filter) => {
8 | return students.filter(s => s.firstname.includes(filter) || s.lastname.includes(filter))
9 | }
10 |
11 | const StudentsApp = () => {
12 | const [students, setStudents] = useState([])
13 | const [filter, setFilter] = useState('')
14 | const [student, setStudent] = useState(null)
15 |
16 | useEffect(() => {
17 | axios.get('students.json').then(({ data }) => {
18 | setStudents(data)
19 | })
20 | }, [])
21 |
22 | const handleFilterChange = newFilter => {
23 | setFilter(newFilter)
24 | }
25 |
26 | const handleSelectStudent = newSelectedStudent => {
27 | setStudent(newSelectedStudent)
28 | }
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | >
36 | )
37 | }
38 |
39 | export default StudentsApp
40 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentsApp.test.js:
--------------------------------------------------------------------------------
1 | import StudentsApp, { filterStudents } from './StudentsApp'
2 | import { render, screen } from '@testing-library/react'
3 | import axios from 'axios'
4 | import Student from './Student'
5 |
6 | describe('StudentsApp', () => {
7 | beforeEach(() => {
8 | axios.get = jest.fn().mockImplementationOnce(() => Promise.resolve({ data: [new Student(1, 'last', 'first', [])] }))
9 | })
10 |
11 | it('renders studentsApp and its sub components', async () => {
12 | // Given
13 | const { container } = render( )
14 |
15 | // Then
16 | // TODO: snapshot
17 | })
18 |
19 | it('filters students', () => {
20 | //Given
21 | const students = [new Student(1, 'last1', 'first1', []), new Student(2, 'last2', 'first2', [])]
22 |
23 | //when
24 |
25 | //then
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentsTable.js:
--------------------------------------------------------------------------------
1 | import Table from '@mui/material/Table'
2 | import TableBody from '@mui/material/TableBody'
3 | import TableCell from '@mui/material/TableCell'
4 | import TableHead from '@mui/material/TableHead'
5 | import TableRow from '@mui/material/TableRow'
6 | import Student from './Student'
7 | import PropTypes from 'prop-types'
8 |
9 | const StudentsTable = ({ students, onSelectStudent }) => (
10 |
11 |
12 |
13 | #
14 | Firstname
15 | Lastname
16 |
17 |
18 |
19 | {students.map(student => (
20 | onSelectStudent(student)} data-testid="student-row">
21 | {student.id}
22 | {student.firstname}
23 | {student.lastname}
24 |
25 | ))}
26 |
27 |
28 | )
29 |
30 | StudentsTable.propTypes = {
31 | students: PropTypes.arrayOf(Student.propTypes).isRequired,
32 | onSelectStudent: PropTypes.func.isRequired,
33 | }
34 |
35 | export default StudentsTable
36 |
--------------------------------------------------------------------------------
/TPs/TP-07/StudentsTable.test.js:
--------------------------------------------------------------------------------
1 | import StudentsTable from './StudentsTable'
2 | import Student from './Student'
3 | import { fireEvent, render, screen } from '@testing-library/react'
4 |
5 | describe('StudenstTable', () => {
6 | it('renders studentsTable with 2 names', () => {
7 | // Given
8 | // render( )
9 | // Then
10 | // expect() ... (use data-testid)
11 | })
12 |
13 | it('callbacks with clicked user', () => {
14 | // Given
15 | // render( )
16 | // When
17 | // click
18 | // Then
19 | // expect() ...
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/TPs/TP-07/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import StudentsApp from './StudentsApp'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/TP-08/About.js:
--------------------------------------------------------------------------------
1 | const About = () => (
2 |
3 |
About
4 | About this application...
5 |
6 | )
7 |
8 | export default About
9 |
--------------------------------------------------------------------------------
/TPs/TP-08/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter as Router, Link, Route, Routes } from 'react-router-dom'
3 | import Home from "./Home";
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | Home
11 |
12 | {/*TODO: créer les */}
13 | About
14 | Users
15 |
16 |
17 |
18 |
19 | } />
20 | {/*TODO: créer les */}
21 |
22 |
23 |
24 | )
25 |
26 | export default App
27 |
--------------------------------------------------------------------------------
/TPs/TP-08/Home.js:
--------------------------------------------------------------------------------
1 | const Home = () => (
2 |
3 |
Home
4 | Welcome !
5 |
6 | )
7 |
8 | export default Home
--------------------------------------------------------------------------------
/TPs/TP-08/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/TP-08/users/UserDetails.js:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router-dom'
2 | import React from 'react'
3 |
4 | const UserDetails = () => {
5 | const name = 'name' // TODO: Récupérer le nom
6 | return (
7 |
8 |
9 | Details of {name}
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default UserDetails
17 |
--------------------------------------------------------------------------------
/TPs/TP-08/users/Users.js:
--------------------------------------------------------------------------------
1 | import { Link, Outlet } from 'react-router-dom'
2 | import React from 'react'
3 |
4 | const Users = () => (
5 |
6 |
Users
7 |
8 | {/*TODO: créer les */}
9 | anna
10 | elsa
11 |
12 |
13 |
14 | )
15 | export default Users
16 |
--------------------------------------------------------------------------------
/TPs/TP-09/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useCallback } from 'react'
2 | import PropTypes from 'prop-types'
3 | import Button from './Button'
4 |
5 | const add = (x, y) => {
6 | console.log('une loooongue addition')
7 | return x + y
8 | }
9 |
10 | const Calculator = ({ a, b }) => {
11 | console.log('render Calculator')
12 | // TODO : Utiliser useMemo pour éviter de refaire le calcul si a et b ne changent pas
13 | // TODO : OU utiliser React.memo pour memoizer le composant Calculator
14 | const result = add(a, b)
15 |
16 | return {result}
17 | }
18 | Calculator.propTypes = {
19 | a: PropTypes.number.isRequired,
20 | b: PropTypes.number.isRequired,
21 | }
22 |
23 | const App = () => {
24 | const date = new Date()
25 | const [sel, setSel] = useState(0)
26 |
27 | return (
28 |
29 | Sel : {sel}
30 | {/* TODO : Utiliser useCallback pour ne pas que Button se réaffiche */}
31 | setSel(date.getMilliseconds())} />
32 |
33 |
34 | )
35 | }
36 |
37 | export default App
38 |
--------------------------------------------------------------------------------
/TPs/TP-09/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const Button = ({ onClick }) => {
5 | console.log('render Button')
6 | return re-render App
7 | }
8 |
9 | Button.propTypes = {
10 | onClick: PropTypes.func.isRequired,
11 | }
12 |
13 | export default Button
14 | // Memoizer Button __SI BESOIN__
15 |
--------------------------------------------------------------------------------
/TPs/TP-09/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/demos/controlled/NameForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class NameForm extends React.Component {
4 | constructor(props) {
5 | super(props)
6 | this.state = { firstname: '' }
7 | this.handleSubmit = this.handleSubmit.bind(this)
8 | }
9 |
10 | handleSubmit(event) {
11 | alert('form was submitted: name: ' + this.input.value + ', firstname ' + this.state.firstname)
12 | event.preventDefault()
13 | }
14 |
15 | render() {
16 | return (
17 |
32 | )
33 | }
34 | }
35 |
36 | export default NameForm
37 |
--------------------------------------------------------------------------------
/TPs/demos/controlled/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import NameForm from './NameForm'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
6 |
--------------------------------------------------------------------------------
/TPs/demos/fetch/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const ShoppingItem = ({ item }) => (
5 |
6 | {item.label} :{item.price}€
7 |
8 | )
9 |
10 | ShoppingItem.propTypes = {
11 | item: PropTypes.shape({ label: PropTypes.string.isRequired, price: PropTypes.number.isRequired }).isRequired,
12 | }
13 |
14 | export default ShoppingItem
15 |
--------------------------------------------------------------------------------
/TPs/demos/fetch/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import ShoppingItem from './ShoppingItem'
4 | import PropTypes from 'prop-types'
5 |
6 | const postData = [
7 | {
8 | id: 1,
9 | label: 'pain',
10 | price: 0.95,
11 | },
12 | {
13 | id: 2,
14 | label: 'gel douche',
15 | price: 2.85,
16 | },
17 | {
18 | id: 3,
19 | label: 'cahier à spirales',
20 | price: 1.2,
21 | },
22 | ]
23 |
24 | const controller = new AbortController()
25 | const signal = controller.signal
26 |
27 | const ShoppingList = ({ title }) => {
28 | const [items, setItems] = useState([])
29 |
30 | useEffect(async () => {
31 | try {
32 | const resp = await fetch('https://httpbin.org/delay/1', {
33 | method: 'post',
34 | headers: {
35 | Accept: 'application/json',
36 | 'Content-type': 'application/json; charset=UTF-8',
37 | },
38 | body: postData,
39 | signal: signal,
40 | })
41 | const data = await resp.json()
42 | const items = JSON.parse(data.data)
43 | setItems(postData)
44 | } catch (e) {
45 | console.error(e)
46 | }
47 | }, [])
48 |
49 | const abort = () => {
50 | controller.abort()
51 | }
52 |
53 | return (
54 |
55 |
{title}
56 |
abort request
57 |
58 | {items.map(item => (
59 |
60 | ))}
61 |
62 |
63 | )
64 | }
65 |
66 | ShoppingList.propTypes = {
67 | title: PropTypes.string.isRequired,
68 | }
69 |
70 | export default ShoppingList
71 |
--------------------------------------------------------------------------------
/TPs/demos/fetch/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import ShoppingList from './ShoppingList'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
6 |
--------------------------------------------------------------------------------
/TPs/demos/futur/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 | Formation react js ES6
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
49 |
50 |
51 |
52 |
53 |
60 |
61 | Versions
62 |
63 | "react": "16.7.0-alpha.2"
64 |
65 |
66 |
67 | Mixins ou HOC => poubelle !
68 |
69 |
70 |
71 |
72 | React Hooks
73 |
74 | Ajouter des comportements...
75 | ... Sans les problèmes des HOC
76 | Sans l'overhead de la classe JS
77 |
78 |
79 |
80 | useState - Demo !
81 |
82 | import ReactDOM from 'react-dom'
83 | import React, { useState } from 'react'
84 |
85 | const Counter = () => {
86 | const [count, setCount] = useState(10)
87 | return (
88 | <>
89 | <div>Compteur : {count} </div>
90 | <button onClick={() => setCount(c => ++c)}>PLUS</button>
91 | <button onClick={() => setCount(c => --c)}>MOINS</button>
92 | </>
93 | )
94 | }
95 |
96 | ReactDOM.render(<Counter />, document.getElementById('root'))
97 |
98 |
99 |
108 |
109 | useContext
110 |
111 | const MyContext = React.createContext(defaultValue);
112 |
113 |
114 | const context = useContext(MyContext);
115 |
116 |
117 |
118 | custom Hooks
119 |
120 | use existing hooks !
121 | use existing hooks !
122 |
123 |
124 |
125 | Concurrent React
126 |
127 | Suspense
128 | Time slicing
129 |
130 |
131 |
132 | Suspense
133 | React Suspense is a generic way for components to suspend rendering while they load data from a
134 | cache.
135 | Suspense is available without React Concurrent.
136 |
137 |
138 | Suspense
139 | React-cache.
140 | React-lazy. Demo !
141 |
142 |
143 | Time slicing
144 | Time Slicing is a generic way to ensure that high-priority updates don’t get blocked by a low-priority
145 | update.
146 |
147 |
148 | Time slicing
149 |
150 | High Priority: Updates involving user input (e.g. text inputs)
151 | Low Priority: Updates involving data loading or expensive calculation
152 | Concurrent React sets priorities for you by default (onKeyDown vs onMouseOver).
153 |
154 |
155 |
156 | What's next ?
157 |
158 |
159 | Let's do it !
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/TPs/demos/futur/index.txt:
--------------------------------------------------------------------------------
1 | Hooks:
2 | Mixins ou HOC => poubelle ! https://cdn-images-1.medium.com/max/1000/1*SU5_ws88Kh_Oio_L4Myhvg.png
3 | useState https://reactjs.org/docs/hooks-state.html#recap
4 | useEffect
5 | - componentDidMount
6 | - componentDidUpdate
7 | - (componentWillUnmount) https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1
8 | - appelé avant useEffect
9 | - appelé avant unmount
10 | useContext https://reactjs.org/docs/hooks-reference.html#usecontext
11 |
12 | Concurrent React includes features like Time Slicing and React Suspense
13 |
14 | React Suspense is a generic way for components to suspend rendering while they load data from a cache.
15 | Cache implementations are independent of React Suspense;
16 | - the React team maintains a reference implementation called react-cache that also supports key-based invalidation and preloading but they are not strictly necessary for React Suspense to work.
17 | - react lazy
18 |
19 | Time Slicing is a generic way to ensure that high-priority updates don’t get blocked by a low-priority update.
20 | - High Priority: Updates involving user input (e.g. text inputs)
21 | - Low Priority: Updates involving data loading or expensive calculation
22 | - Concurrent React sets priorities for you by default.
23 | - If you use React to listen to events (e.g on*), it will already pick the appropriate priority when you enqueue a state change (e.g a onKeyDown will be high priority and a onMouseOver low pri)
24 |
25 |
--------------------------------------------------------------------------------
/TPs/demos/futur/lazy-suspense/Goodbye.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Goodbye = ({ name }) => Bye {name}
4 |
5 | export default Goodbye
6 |
--------------------------------------------------------------------------------
/TPs/demos/futur/lazy-suspense/index.js:
--------------------------------------------------------------------------------
1 | import React, { ConcurrentMode, Suspense, useState } from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | const LazyGoodbye = React.lazy(() => import(/* webpackChunkName: "LazyGoodbye" */ './Goodbye'))
5 |
6 | const Button = () => {
7 | const [display, setDisplay] = useState(false)
8 |
9 | return (
10 | <>
11 | setDisplay(true)}>click !
12 |
13 | {display && (
14 | Loading... !} maxDuration={10000}>
15 |
16 |
17 | )}
18 | >
19 | )
20 | }
21 |
22 | const Hello = ({ name }) => Hello {name}
23 |
24 | ReactDOM.createRoot(document.getElementById('root')).render(
25 | <>
26 |
27 |
28 | >
29 | )
30 |
--------------------------------------------------------------------------------
/TPs/demos/indexAsKeyBug/ProblemWithIndexAsKey.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { useState } from 'react'
3 |
4 | const Input = ({ v }) => {
5 | const [value, setValue] = useState(v)
6 |
7 | return setValue(e.target.value)} />
8 | }
9 | Input.propTypes = {
10 | v: PropTypes.string.isRequired,
11 | }
12 |
13 | const ProblemWithIndexAsKey = () => {
14 | const [letters, setLetters] = useState(['b', 'c', 'd'])
15 |
16 | const addA = () => setLetters(prev => ['a'].concat(prev))
17 |
18 | /*DO NOT USE index as key !!*/
19 | return (
20 | <>
21 | add A
22 | {letters.map((letter, index) => (
23 |
24 | ))}
25 | >
26 | )
27 | /*try key={index + Math.random()} ?*/
28 | }
29 |
30 | export default ProblemWithIndexAsKey
31 |
--------------------------------------------------------------------------------
/TPs/demos/indexAsKeyBug/index.js:
--------------------------------------------------------------------------------
1 | import ProblemWithIndexAsKey from './ProblemWithIndexAsKey'
2 | import { createRoot } from 'react-dom/client'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/Item.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default class Item {
4 | constructor(id, label, price) {
5 | this.id = id
6 | this.label = label
7 | this.price = price
8 | }
9 | }
10 |
11 | export const ItemPropTypes = PropTypes.shape({ label: PropTypes.string.isRequired, price: PropTypes.number.isRequired })
12 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import { ItemPropTypes } from './Item'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | ShoppingItem.propTypes = {
10 | item: ItemPropTypes.isRequired,
11 | }
12 |
13 | export default ShoppingItem
14 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Item from './Item'
3 | import ShoppingItem from './ShoppingItem'
4 | import { fetchItems, addItemWithVTA } from './actions/items'
5 | import { useDispatch, useSelector } from 'react-redux'
6 | import PropTypes from 'prop-types'
7 |
8 | export const ShoppingList = ({ title }) => {
9 | const [newItemLabel, setNewItemLabel] = useState('')
10 | const [newItemPrice, setNewItemPrice] = useState(0)
11 | const dispatch = useDispatch()
12 | const items = useSelector(state => state.items)
13 |
14 | useEffect(() => {
15 | dispatch(fetchItems())
16 | }, [])
17 |
18 | const createNewItem = e => {
19 | e.preventDefault()
20 | dispatch(addItemWithVTA(new Item(Date.now(), newItemLabel, newItemPrice)))
21 | setNewItemLabel('')
22 | setNewItemPrice(0)
23 | }
24 |
25 | return (
26 |
27 |
{title}
28 |
29 | {items.map(item => (
30 |
31 | ))}
32 |
33 |
38 |
39 | )
40 | }
41 |
42 | ShoppingList.propTypes = {
43 | title: PropTypes.string.isRequired,
44 | }
45 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/actions/items.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import Item from '../Item'
3 |
4 | export const ADD_ITEM = 'ADD_ITEM'
5 | export const SET_ITEMS = 'SET_ITEMS'
6 |
7 | export const setItems = items => ({
8 | type: SET_ITEMS,
9 | items,
10 | })
11 |
12 | export const addItem = item => ({
13 | type: ADD_ITEM,
14 | item,
15 | })
16 |
17 | const getItems = () => axios.get('items.json').then(resp => resp.data)
18 |
19 | export const fetchItems = () => dispatch =>
20 | getItems()
21 | .then(items => dispatch(setItems(items)))
22 | .catch(error => {
23 | console.log(error)
24 | })
25 |
26 | export const addItemWithVTA = item => dispatch => {
27 | const newItem = new Item(item.id, item.label, item.price * 1.2)
28 | return dispatch(addItem(newItem))
29 | }
30 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/index.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom'
2 | import { Provider } from 'react-redux'
3 | import { applyMiddleware, createStore, compose } from 'redux'
4 | import { reducer } from './reducers/index'
5 | import { ShoppingList } from './ShoppingList'
6 | import thunk from 'redux-thunk'
7 | import middlewareLogger from './middleware'
8 |
9 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
10 | const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk, middlewareLogger)))
11 |
12 |
13 | ReactDOM.render(
14 |
15 |
16 | ,
17 | document.getElementById('root')
18 | )
19 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/middleware.js:
--------------------------------------------------------------------------------
1 | const middlewareLogger = store => next => action => {
2 | console.log('state:', store.getState())
3 | console.log('action:', action)
4 | const result = next(action)
5 | console.log('new state:', store.getState())
6 | return result
7 | }
8 |
9 | export default middlewareLogger
10 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import items from './items'
3 |
4 | export const reducer = combineReducers({
5 | items,
6 | })
7 |
--------------------------------------------------------------------------------
/TPs/demos/middleware/reducers/items.js:
--------------------------------------------------------------------------------
1 | import { ADD_ITEM, SET_ITEMS } from '../actions/items'
2 |
3 | const items = (state = [], action) => {
4 | switch (action.type) {
5 | case SET_ITEMS:
6 | return action.items
7 | case ADD_ITEM:
8 | return state.concat(action.item)
9 | default:
10 | return state
11 | }
12 | }
13 |
14 | export default items
15 |
--------------------------------------------------------------------------------
/TPs/demos/optimisation-key/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | const App = () => {
4 | const [counter, setCounter] = useState(0)
5 |
6 | return (
7 |
8 | setCounter(c => c + 1)}>Click me !
9 |
10 |
11 | )
12 | }
13 |
14 | const Component1 = () => {
15 | useEffect(() => {
16 | console.log('Component1 did mount')
17 | return () => console.log('Component1 will unmount')
18 | }, [])
19 |
20 | return (
21 | Component1
22 |
)
23 | }
24 |
25 | const Component2 = () => {
26 | useEffect(() => {
27 | console.log('Component2 did mount')
28 | return () => console.log('Component2 will unmount')
29 | }, [])
30 |
31 | return (
32 | Component2
33 |
)
34 | }
35 |
36 | export default App
37 |
38 | const ComponentToOptimize = ({ counter }) => {
39 |
40 | const renderNotOptimized = () => {
41 | if ( counter % 2 === 0 ) {
42 | return (
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | return (
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | const renderWithNull = () => {
58 | return (
59 |
60 | {counter % 2 === 0 ? : null}
61 |
62 |
63 | )
64 | }
65 |
66 | const renderWithKey = () => {
67 | if ( counter % 2 === 0 ) {
68 | return (
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
76 | return (
77 |
78 |
79 |
80 | )
81 | }
82 |
83 | //return renderNotOptimized()
84 | // return renderWithNull();
85 | return renderWithKey();
86 | }
87 |
--------------------------------------------------------------------------------
/TPs/demos/optimisation-key/index.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom'
2 | import React from 'react'
3 | import App from './App'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
6 |
--------------------------------------------------------------------------------
/TPs/demos/portal/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | class App extends React.Component {
5 | render() {
6 | return (
7 |
8 | App
9 |
10 |
11 | )
12 | }
13 | }
14 |
15 | class Modale extends React.Component {
16 | render() {
17 | return ReactDOM.createPortal(Modale!
, document.getElementById('secret-demo'))
18 | }
19 | }
20 |
21 | export default App
22 |
--------------------------------------------------------------------------------
/TPs/demos/portal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
6 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/Item.js:
--------------------------------------------------------------------------------
1 | export default class Item {
2 | constructor(id, label, price) {
3 | this.id = id
4 | this.label = label
5 | this.price = price
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | export default ShoppingItem
10 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Item from './Item'
3 | import { setItems, addItem } from './slice/items'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import ShoppingItem from './ShoppingItem'
6 |
7 | const ShoppingList = ({ title }) => {
8 | const [newItemLabel, setNewItemLabel] = useState('')
9 | const [newItemPrice, setNewItemPrice] = useState(0)
10 | const dispatch = useDispatch()
11 | const items = useSelector(state => state.items.value)
12 |
13 | useEffect(() => {
14 | const action = setItems([
15 | new Item(1, 'pain', 0.95),
16 | new Item(2, 'gel douche', 2.85),
17 | new Item(3, 'cahier à spirales', 1.2),
18 | ])
19 | dispatch(action)
20 | }, [])
21 |
22 | const handleAddItem = event => {
23 | event.preventDefault()
24 | const action = addItem(new Item(Date.now(), newItemLabel, newItemPrice))
25 | dispatch(action)
26 | setNewItemLabel('')
27 | setNewItemPrice(0)
28 | }
29 |
30 | return (
31 |
32 |
{title}
33 |
34 | {items.map(item => (
35 |
36 | ))}
37 |
38 |
43 |
44 | )
45 | }
46 |
47 | export default ShoppingList
48 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import { Provider } from 'react-redux'
3 | import store from './store/index'
4 | import ShoppingList from './ShoppingList'
5 |
6 | const container = document.getElementById('root')
7 | const root = createRoot(container)
8 | root.render(
9 |
10 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/slice/items.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | export const itemsSlice = createSlice({
4 | name: 'items',
5 | initialState: { value: [] },
6 | reducers: {
7 | setItems: (state, action) => {
8 | state.value = action.payload
9 | },
10 | addItem: (state, action) => {
11 | state.value = state.value.concat(action.payload)
12 | },
13 | },
14 | })
15 |
16 | export const { setItems, addItem } = itemsSlice.actions
17 |
18 | export default itemsSlice.reducer
19 |
--------------------------------------------------------------------------------
/TPs/demos/redux toolkit/store/index.js:
--------------------------------------------------------------------------------
1 | import itemsReducer from '../slice/items'
2 | import { configureStore } from '@reduxjs/toolkit'
3 |
4 | const store = configureStore({
5 | reducer: {
6 | items: itemsReducer,
7 | },
8 | middleware: getDefaultMiddleware =>
9 | getDefaultMiddleware({
10 | serializableCheck: false,
11 | }),
12 | })
13 | export default store
14 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter, Route, Routes } from 'react-router-dom'
3 | import Home from './Routes/Home'
4 | import Route1 from './Routes/Route1'
5 | import Route2 from './Routes/Route2'
6 | import Menu from './Menu'
7 |
8 | const App = () => (
9 |
10 |
11 | } />
12 | } />
13 | } />
14 |
15 |
16 | )
17 |
18 | export default App
19 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/Menu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | const Menu = () => (
5 |
6 |
7 | Home
8 |
9 |
10 | Route 1
11 |
12 |
13 | Route 2
14 |
15 |
16 | )
17 |
18 | export default Menu
19 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/Routes/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Menu from '../Menu'
3 |
4 | const Home = () => {
5 | const [title, setTitle] = useState('Loading ...')
6 |
7 | useEffect(() => {
8 | getTitle()
9 | }, [title])
10 |
11 | const getTitle = () => {
12 | setTimeout(() => {
13 | setTitle('HOMEPAGE')
14 | }, 2000)
15 | }
16 |
17 | return (
18 |
19 |
20 | {title}
21 |
22 | )
23 | }
24 |
25 | export default Home
26 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/Routes/Route1.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react'
2 | import Menu from '../Menu'
3 |
4 | const Route1 = () => {
5 | const [title, setTitle] = useState('Loading Route1...')
6 |
7 | useEffect(() => {
8 | getTitle()
9 | }, [])
10 |
11 | const getTitle = () => {
12 | setTimeout(() => {
13 | setTitle('Route1')
14 | }, 2000)
15 | }
16 |
17 | return (
18 |
19 |
20 | {title}
21 |
22 | )
23 | }
24 |
25 | export default Route1
26 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/Routes/Route2.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react'
2 | import Menu from '../Menu'
3 |
4 | const Route2 = () => {
5 | const [title, setTitle] = useState('Loading Route 2...')
6 |
7 | useEffect(() => {
8 | getTitle()
9 | }, [])
10 |
11 | const getTitle = () => {
12 | setTimeout(() => {
13 | setTitle('Route2')
14 | }, 2000)
15 | }
16 |
17 | return (
18 |
19 |
20 | {title}
21 |
22 | )
23 | }
24 |
25 | export default Route2
26 |
--------------------------------------------------------------------------------
/TPs/demos/useEffect/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
6 |
--------------------------------------------------------------------------------
/TPs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React !
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-01/Hello.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | const Hello = ({ name }) => Hello {name} !
4 |
5 | export default Hello
6 |
7 | Hello.propTypes = {
8 | name: PropTypes.string.isRequired,
9 | truc: PropTypes.bool,
10 | }
11 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-01/index.js:
--------------------------------------------------------------------------------
1 | import Hello from './Hello'
2 | import { createRoot } from 'react-dom/client'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-02/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | ShoppingItem.propTypes = {
10 | item: PropTypes.shape({ label: PropTypes.string.isRequired, price: PropTypes.number.isRequired }).isRequired,
11 | }
12 |
13 | export default ShoppingItem
14 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-02/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import ShoppingItem from './ShoppingItem'
4 | import PropTypes from 'prop-types'
5 |
6 | const ShoppingList = ({ title }) => {
7 | const [items, setItems] = useState([])
8 |
9 | useEffect(() => {
10 | axios
11 | .get('./items.json')
12 | .then(resp => resp.data)
13 | .then(setItems)
14 | }, [])
15 |
16 | return (
17 |
18 |
{title}
19 |
20 | {items.map(item => (
21 |
22 | ))}
23 |
24 |
25 | )
26 | }
27 |
28 | ShoppingList.propTypes = {
29 | title: PropTypes.string.isRequired,
30 | }
31 |
32 | export default ShoppingList
33 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-02/index.js:
--------------------------------------------------------------------------------
1 | import ShoppingList from './ShoppingList'
2 | import { createRoot } from 'react-dom/client'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/Student.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | class Student {
4 | constructor(id, lastname, firstname, grades) {
5 | this.id = id
6 | this.lastname = lastname
7 | this.firstname = firstname
8 | this.grades = grades
9 | }
10 | }
11 |
12 | Student.propTypes = PropTypes.shape({
13 | firstname: PropTypes.string.isRequired,
14 | grades: PropTypes.arrayOf(PropTypes.number).isRequired,
15 | id: PropTypes.number.isRequired,
16 | lastname: PropTypes.string.isRequired,
17 | })
18 |
19 | export default Student
20 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/StudentDetails.js:
--------------------------------------------------------------------------------
1 | import Student from './Student'
2 | import FormLabel from '@mui/material/FormLabel'
3 |
4 | const StudentDetails = ({ student }) => (
5 |
6 | {student ? (
7 |
8 | {student.firstname} {student.lastname}
9 |
10 | ) : (
11 | Aucun étudiant sélectionné !
12 | )}
13 |
14 | )
15 |
16 | StudentDetails.propTypes = {
17 | student: Student.propTypes,
18 | }
19 |
20 | export default StudentDetails
21 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/StudentFilter.js:
--------------------------------------------------------------------------------
1 | import Input from '@mui/material/Input';
2 | import PropTypes from 'prop-types'
3 |
4 | const StudentFilter = ({ onChange }) => {
5 | const onFilterChange = e => {
6 | onChange(e.target.value)
7 | }
8 |
9 | return (
10 |
20 | )
21 | }
22 |
23 | StudentFilter.propTypes = {
24 | onChange: PropTypes.func.isRequired,
25 | }
26 |
27 | export default StudentFilter
28 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/StudentsApp.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import StudentFilter from './StudentFilter'
4 | import StudentsTable from './StudentsTable'
5 | import StudentDetails from './StudentDetails'
6 |
7 | const filterStudents = (students, filter) => {
8 | return students.filter(s => s.firstname.includes(filter) || s.lastname.includes(filter))
9 | }
10 |
11 | const StudentsApp = () => {
12 | const [students, setStudents] = useState([])
13 | const [filter, setFilter] = useState('')
14 | const [student, setStudent] = useState(null)
15 |
16 | useEffect(() => {
17 | axios.get('students.json').then(({ data }) => {
18 | setStudents(data)
19 | })
20 | }, [])
21 |
22 | const handleFilterChange = newFilter => {
23 | setFilter(newFilter)
24 | }
25 |
26 | const handleSelectStudent = newSelectedStudent => {
27 | setStudent(newSelectedStudent)
28 | }
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | >
36 | )
37 | }
38 |
39 | export default StudentsApp
40 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/StudentsTable.js:
--------------------------------------------------------------------------------
1 | import Table from '@mui/material/Table'
2 | import TableBody from '@mui/material/TableBody'
3 | import TableCell from '@mui/material/TableCell'
4 | import TableHead from '@mui/material/TableHead'
5 | import TableRow from '@mui/material/TableRow'
6 | import Student from './Student'
7 | import PropTypes from 'prop-types'
8 |
9 | const StudentsTable = ({ students, onSelectStudent }) => (
10 |
11 |
12 |
13 | #
14 | Firstname
15 | Lastname
16 |
17 |
18 |
19 | {students.map(student => (
20 | onSelectStudent(student)}>
21 | {student.id}
22 | {student.firstname}
23 | {student.lastname}
24 |
25 | ))}
26 |
27 |
28 | )
29 |
30 | StudentsTable.propTypes = {
31 | students: PropTypes.arrayOf(Student.propTypes).isRequired,
32 | onSelectStudent: PropTypes.func.isRequired,
33 | }
34 |
35 | export default StudentsTable
36 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-03/index.js:
--------------------------------------------------------------------------------
1 | import StudentsApp from './StudentsApp'
2 | import { createRoot } from 'react-dom/client'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/Item.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default class Item {
4 | constructor(id, label, price) {
5 | this.id = id
6 | this.label = label
7 | this.price = price
8 | }
9 | }
10 |
11 | export const ItemPropTypes = PropTypes.shape({ label: PropTypes.string.isRequired, price: PropTypes.number.isRequired })
12 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import { ItemPropTypes } from './Item'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | ShoppingItem.propTypes = {
10 | item: ItemPropTypes.isRequired,
11 | }
12 |
13 | export default ShoppingItem
14 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Item from './Item'
3 | import ShoppingItem from './ShoppingItem'
4 | import PropTypes from 'prop-types'
5 | import { setItems, addItem } from './actions/items'
6 | import { useDispatch, useSelector } from 'react-redux'
7 |
8 | const ShoppingList = ({ title }) => {
9 | const [newItemLabel, setNewItemLabel] = useState('')
10 | const [newItemPrice, setNewItemPrice] = useState(0)
11 | const dispatch = useDispatch()
12 | const items = useSelector(state => state.items)
13 |
14 | const handleAddItem = event => {
15 | event.preventDefault()
16 | const addItemAction = addItem(new Item(Date.now(), newItemLabel, newItemPrice))
17 | dispatch(addItemAction)
18 | setNewItemLabel('')
19 | setNewItemPrice(0)
20 | }
21 |
22 | useEffect(() => {
23 | const setItemsAction = setItems([
24 | new Item(1, 'pain', 0.95),
25 | new Item(2, 'gel douche', 2.85),
26 | new Item(3, 'cahier à spirales', 1.2),
27 | ])
28 | dispatch(setItemsAction)
29 | }, [])
30 |
31 | return (
32 |
33 |
{title}
34 |
35 | {items.map(item => (
36 |
37 | ))}
38 |
39 |
44 |
45 | )
46 | }
47 |
48 | export default ShoppingList
49 |
50 | ShoppingList.propTypes = {
51 | title: PropTypes.string.isRequired,
52 | }
53 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/actions/items.js:
--------------------------------------------------------------------------------
1 | export const ADD_ITEM = 'ADD_ITEM'
2 | export const SET_ITEMS = 'SET_ITEMS'
3 |
4 | export const setItems = items => ({
5 | type: SET_ITEMS,
6 | items,
7 | })
8 |
9 | export const addItem = item => ({
10 | type: ADD_ITEM,
11 | item,
12 | })
13 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import { Provider } from 'react-redux'
3 | import { createStore } from 'redux'
4 | import { reducer } from './reducers/index'
5 | import ShoppingList from './ShoppingList'
6 |
7 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
8 |
9 | const container = document.getElementById('root')
10 | const root = createRoot(container)
11 | root.render(
12 |
13 |
14 |
15 | )
16 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import items from './items'
3 |
4 | export const reducer = combineReducers({
5 | items,
6 | })
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-04/reducers/items.js:
--------------------------------------------------------------------------------
1 | import { ADD_ITEM, SET_ITEMS } from '../actions/items'
2 |
3 | const items = (state = [], action) => {
4 | switch (action.type) {
5 | case SET_ITEMS:
6 | return action.items
7 | case ADD_ITEM:
8 | return state.concat(action.item)
9 | default:
10 | return state
11 | }
12 | }
13 |
14 | export default items
15 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-05/Item.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default class Item {
4 | constructor(id, label, price) {
5 | this.id = id
6 | this.label = label
7 | this.price = price
8 | }
9 | }
10 |
11 | Item.propTypes = PropTypes.shape({
12 | id: PropTypes.number,
13 | label: PropTypes.string.isRequired,
14 | price: PropTypes.number.isRequired,
15 | })
16 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-05/ShoppingItem.js:
--------------------------------------------------------------------------------
1 | import Item from './Item'
2 |
3 | const ShoppingItem = ({ item }) => (
4 |
5 | {item.label} :{item.price}€
6 |
7 | )
8 |
9 | ShoppingItem.propTypes = {
10 | item: Item.propTypes.isRequired,
11 | }
12 |
13 | export default ShoppingItem
14 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-05/ShoppingList.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import Item from './Item'
3 | import ShoppingItem from './ShoppingItem'
4 | import PropTypes from 'prop-types'
5 | import itemsApi from './apis/items'
6 | import { CircularProgress } from '@mui/material'
7 | import { useMutation, useQuery } from '@tanstack/react-query'
8 |
9 | export const ShoppingList = ({ title }) => {
10 | const [newItemLabel, setNewItemLabel] = useState('')
11 | const [newItemPrice, setNewItemPrice] = useState(0)
12 |
13 | const {
14 | data: items = [],
15 | refetch,
16 | isLoading: isLoadingGet,
17 | } = useQuery({
18 | queryKey: ['items'],
19 | queryFn: itemsApi.get,
20 | })
21 | const { mutateAsync: addItem, isLoading: isLoadingAdd } = useMutation({
22 | mutationFn: itemsApi.create,
23 | onSuccess: refetch,
24 | })
25 |
26 | const createNewItem = async e => {
27 | e.preventDefault()
28 | await addItem(new Item(new Date().getTime(), newItemLabel, newItemPrice))
29 | setNewItemLabel('')
30 | setNewItemPrice(0)
31 | }
32 |
33 | return (
34 |
35 |
{title}
36 | {isLoadingGet &&
}
37 |
38 | {items.map(item => (
39 |
40 | ))}
41 | {isLoadingAdd && (
42 |
43 |
44 |
45 | )}
46 |
47 |
52 |
53 | )
54 | }
55 |
56 | ShoppingList.propTypes = {
57 | title: PropTypes.string.isRequired,
58 | }
59 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-05/apis/items.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import itemsArray from 'public/items.json'
3 |
4 | const delay = async (value, duration) => new Promise(resolve => setTimeout(resolve, duration, value))
5 |
6 | let allItems = itemsArray
7 |
8 | const items = {
9 | get: async () => {
10 | //const { data } = await axios.get('items.json')
11 | // return data
12 |
13 | // fake implementation :
14 | return await delay(allItems, 2000)
15 | },
16 | create: async item => {
17 | // await axios.post('/items', {item})
18 |
19 | // fake implementation :
20 | allItems = allItems.concat({ id: item.id, label: item.label, price: item.price })
21 | await delay(null, 2000)
22 | },
23 | }
24 |
25 | export default items
26 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-05/index.js:
--------------------------------------------------------------------------------
1 | import { ShoppingList } from './ShoppingList'
2 | import { createRoot } from 'react-dom/client'
3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4 |
5 | const queryClient = new QueryClient()
6 |
7 | const container = document.getElementById('root')
8 | const root = createRoot(container)
9 | root.render(
10 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-06/App.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { InfosContext } from './Context'
3 | import User from './User'
4 |
5 | const Small = () => {
6 | const { color, user } = useContext(InfosContext)
7 |
8 | return (
9 |
10 | Small, user = {user.id}-{user.login}
11 |
12 | )
13 | }
14 |
15 | const Medium = () => (
16 |
17 | Medium
18 |
19 |
20 | )
21 |
22 | const Large = () => (
23 |
24 | Large
25 |
26 |
27 | )
28 |
29 | const App = () => (
30 |
31 |
32 |
33 | )
34 |
35 | export default App
36 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-06/Context.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const InfosContext = React.createContext({ color: 'yellow', user: null })
4 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-06/User.js:
--------------------------------------------------------------------------------
1 | export default class User {
2 | constructor(id, login) {
3 | this.id = id
4 | this.login = login
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-06/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/Student.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | class Student {
4 | constructor(id, lastname, firstname, grades) {
5 | this.id = id
6 | this.lastname = lastname
7 | this.firstname = firstname
8 | this.grades = grades
9 | }
10 | }
11 |
12 | Student.propTypes = PropTypes.shape({
13 | firstname: PropTypes.string.isRequired,
14 | grades: PropTypes.arrayOf(PropTypes.number).isRequired,
15 | id: PropTypes.number.isRequired,
16 | lastname: PropTypes.string.isRequired,
17 | })
18 |
19 | export default Student
20 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentDetails.js:
--------------------------------------------------------------------------------
1 | import Student from './Student'
2 | import FormLabel from '@mui/material/FormLabel'
3 |
4 | const StudentDetails = ({ student }) => (
5 |
6 | {student ? (
7 | {[student.firstname, ' ', student.lastname]}
8 | ) : (
9 | Aucun étudiant sélectionné !
10 | )}
11 |
12 | )
13 |
14 | StudentDetails.propTypes = {
15 | student: Student.propTypes,
16 | }
17 |
18 | export default StudentDetails
19 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentDetails.test.js:
--------------------------------------------------------------------------------
1 | import StudentDetails from './StudentDetails'
2 | import { render, screen } from '@testing-library/react'
3 | import Student from './Student'
4 |
5 | describe('StudentDetails', () => {
6 | it('renders with no Student', () => {
7 | // Given
8 | render( )
9 |
10 | // Then
11 | expect(screen.getByText('Aucun étudiant sélectionné !')).toBeTruthy()
12 | })
13 |
14 | it('renders with a Student', () => {
15 | // Given
16 | render( )
17 |
18 | // Then
19 | expect(screen.getByText('first last')).toBeTruthy()
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentFilter.js:
--------------------------------------------------------------------------------
1 | import Input from '@mui/material/Input'
2 | import PropTypes from 'prop-types'
3 |
4 | const StudentFilter = ({ onChange }) => {
5 | const onFilterChange = e => {
6 | onChange(e.target.value)
7 | }
8 |
9 | return (
10 |
20 | )
21 | }
22 |
23 | StudentFilter.propTypes = {
24 | onChange: PropTypes.func.isRequired,
25 | }
26 |
27 | export default StudentFilter
28 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentFilter.test.js:
--------------------------------------------------------------------------------
1 | import Filter from './StudentFilter'
2 | import { render, fireEvent } from '@testing-library/react'
3 |
4 | describe('Filter', () => {
5 | it('renders Filter and calls passed function when filter text has changed', () => {
6 | // Given
7 | const mockOnChange = jest.fn()
8 | const { container } = render( )
9 | const input = container.querySelector('input')
10 |
11 | // When
12 | fireEvent.change(input, { target: { value: 'name' } })
13 |
14 | // Then
15 | expect(mockOnChange).toHaveBeenCalledWith('name')
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentsApp.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import axios from 'axios'
3 | import StudentFilter from './StudentFilter'
4 | import StudentsTable from './StudentsTable'
5 | import StudentDetails from './StudentDetails'
6 |
7 | export const filterStudents = (students, filter) => {
8 | return students.filter(s => s.firstname.includes(filter) || s.lastname.includes(filter))
9 | }
10 |
11 | const StudentsApp = () => {
12 | const [students, setStudents] = useState([])
13 | const [filter, setFilter] = useState('')
14 | const [student, setStudent] = useState(null)
15 |
16 | useEffect(() => {
17 | axios.get('students.json').then(({ data }) => {
18 | setStudents(data)
19 | })
20 | }, [])
21 |
22 | const handleFilterChange = newFilter => {
23 | setFilter(newFilter)
24 | }
25 |
26 | const handleSelectStudent = newSelectedStudent => {
27 | setStudent(newSelectedStudent)
28 | }
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | >
36 | )
37 | }
38 |
39 | export default StudentsApp
40 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentsApp.test.js:
--------------------------------------------------------------------------------
1 | import StudentsApp, { filterStudents } from './StudentsApp'
2 | import { render, screen } from '@testing-library/react'
3 | import axios from 'axios'
4 | import Student from './Student'
5 |
6 | describe('StudentsApp', () => {
7 | beforeEach(() => {
8 | axios.get = jest.fn().mockImplementationOnce(() => Promise.resolve({ data: [new Student(1, 'last', 'first', [])] }))
9 | })
10 |
11 | it('renders studentsApp and its sub components', async () => {
12 | // Given
13 | const { container } = render( )
14 | await screen.findByTestId('student-row')
15 |
16 | // Then
17 | expect(container).toMatchSnapshot()
18 | })
19 |
20 | it('filters students', () => {
21 | //Given
22 | const students = [new Student(1, 'last1', 'first1', []), new Student(2, 'last2', 'first2', [])]
23 |
24 | //when
25 | const result = filterStudents(students, '2')
26 |
27 | //then
28 | expect(result).toHaveLength(1)
29 | expect(result[0]).toEqual(students[1])
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentsTable.js:
--------------------------------------------------------------------------------
1 | import Table from '@mui/material/Table'
2 | import TableBody from '@mui/material/TableBody'
3 | import TableCell from '@mui/material/TableCell'
4 | import TableHead from '@mui/material/TableHead'
5 | import TableRow from '@mui/material/TableRow'
6 | import Student from './Student'
7 | import PropTypes from 'prop-types'
8 |
9 | const StudentsTable = ({ students, onSelectStudent }) => (
10 |
11 |
12 |
13 | #
14 | Firstname
15 | Lastname
16 |
17 |
18 |
19 | {students.map(student => (
20 | onSelectStudent(student)} data-testid="student-row">
21 | {student.id}
22 | {student.firstname}
23 | {student.lastname}
24 |
25 | ))}
26 |
27 |
28 | )
29 |
30 | StudentsTable.propTypes = {
31 | students: PropTypes.arrayOf(Student.propTypes).isRequired,
32 | onSelectStudent: PropTypes.func.isRequired,
33 | }
34 |
35 | export default StudentsTable
36 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/StudentsTable.test.js:
--------------------------------------------------------------------------------
1 | import StudentsTable from './StudentsTable'
2 | import Student from './Student'
3 | import { fireEvent, render, screen } from '@testing-library/react'
4 |
5 | describe('StudenstTable', () => {
6 | it('renders studentsTable with 2 names', () => {
7 | // Given
8 | const onSelectStudent = jest.fn()
9 | const students = [new Student(1, 'last1', 'first1', []), new Student(2, 'last2', 'first2', [])]
10 | render( )
11 |
12 | // Then
13 | expect(screen.getAllByTestId('student-row')).toHaveLength(2)
14 | })
15 |
16 | it('callbacks with clicked user', () => {
17 | // Given
18 | const onSelectStudent = jest.fn()
19 | const student = new Student(1, 'last1', 'first1', [])
20 | const students = [student]
21 | render( )
22 |
23 | // When
24 | fireEvent.click(screen.getByTestId('student-row'))
25 |
26 | // Then
27 | expect(onSelectStudent).toBeCalledWith(student)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/__snapshots__/StudentsApp.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`StudentsApp renders studentsApp and its sub components 1`] = `
4 |
5 |
19 |
22 |
25 |
28 |
32 | #
33 |
34 |
38 | Firstname
39 |
40 |
44 | Lastname
45 |
46 |
47 |
48 |
51 |
55 |
58 | 1
59 |
60 |
63 | first
64 |
65 |
68 | last
69 |
70 |
71 |
72 |
73 |
76 |
79 | Aucun étudiant sélectionné !
80 |
81 |
82 |
83 | `;
84 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-07/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import StudentsApp from './StudentsApp'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/About.js:
--------------------------------------------------------------------------------
1 | const About = () => (
2 |
3 |
About
4 | About this application...
5 |
6 | )
7 |
8 | export default About
9 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Link, NavLink, Route, useParams, Routes, Outlet } from 'react-router-dom'
2 | import Home from "./Home";
3 | import About from "./About";
4 | import Users from "./users/Users";
5 | import UserDetails from "./users/UserDetails";
6 |
7 | const App = () => (
8 |
9 |
10 |
11 |
12 | (isActive ? { fontWeight: 'bolder', backgroundColor: 'cyan' } : {})}
15 | end
16 | >
17 | Home
18 |
19 |
20 |
21 | (isActive ? { fontWeight: 'bolder', backgroundColor: 'pink' } : {})}
24 | >
25 | About
26 |
27 |
28 |
29 | (isActive ? { fontWeight: 'bolder', backgroundColor: 'yellow' } : {})}
32 | >
33 | Users
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | } />
42 | } />
43 | }>
44 | } />
45 | Please select a user.} />
46 |
47 |
51 | Page non trouvée (404) !
52 |
53 | }
54 | />
55 |
56 |
57 |
58 | )
59 |
60 | export default App
61 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/Home.js:
--------------------------------------------------------------------------------
1 | const Home = () => (
2 |
3 |
Home
4 | Welcome !
5 |
6 | )
7 |
8 | export default Home
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/users/UserDetails.js:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router-dom'
2 |
3 | const UserDetails = () => {
4 | const { name } = useParams()
5 |
6 | return (
7 |
8 |
9 | Details of {name}
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default UserDetails
--------------------------------------------------------------------------------
/TPs/solutions/TP-08/users/Users.js:
--------------------------------------------------------------------------------
1 | import { Link, Outlet } from 'react-router-dom'
2 |
3 | const Users = () => {
4 | return (
5 |
6 |
Users
7 |
8 |
9 | Anna
10 |
11 |
12 | Elsa
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export default Users
22 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-09/App.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useState } from 'react'
2 | import PropTypes from 'prop-types'
3 | import Button from './Button'
4 |
5 | const add = (x, y) => {
6 | console.log('une loooongue addition')
7 | return x + y
8 | }
9 |
10 | const Calculator = ({ a, b }) => {
11 | console.log('render Calculator')
12 | const result = useMemo(() => add(a, b), [a, b])
13 |
14 | return {result}
15 | }
16 | Calculator.propTypes = {
17 | a: PropTypes.number.isRequired,
18 | b: PropTypes.number.isRequired,
19 | }
20 | const Calc = React.memo(Calculator)
21 |
22 | const App = () => {
23 | const [sel, setSel] = useState(0)
24 |
25 | const updateSel = useCallback(() => {
26 | const date = new Date()
27 | setSel(date.getMilliseconds())
28 | }, [])
29 |
30 | return (
31 |
32 | Sel : {sel}
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default App
40 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-09/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const Button = ({ onClick }) => {
5 | console.log('render Button')
6 | return re-render App
7 | }
8 |
9 | Button.propTypes = {
10 | onClick: PropTypes.func.isRequired,
11 | }
12 |
13 | export default React.memo(Button)
14 | // Memoizer Button __SI BESOIN__
15 |
--------------------------------------------------------------------------------
/TPs/solutions/TP-09/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | const container = document.getElementById('root')
5 | const root = createRoot(container)
6 | root.render( )
7 |
--------------------------------------------------------------------------------
/cypress/cypress.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('cypress')
2 | const webpackConfig = require('../webpack.config.js')
3 |
4 | module.exports = defineConfig({
5 | viewportWidth: 1480,
6 | viewportHeight: 768,
7 | video: false,
8 | e2e: {
9 | baseUrl: 'http://localhost:8080',
10 | supportFile: false,
11 | },
12 | component: {
13 | devServer: {
14 | framework: 'react',
15 | bundler: 'webpack',
16 | webpackConfig,
17 | },
18 | },
19 |
20 | })
21 |
--------------------------------------------------------------------------------
/cypress/e2e/tps.cy.js:
--------------------------------------------------------------------------------
1 | describe('Tps', () => {
2 | it('loads TP01', () => {
3 | cy.visit('/tp01.html')
4 | cy.contains('Hello world !')
5 | })
6 |
7 | it('loads TP02', () => {
8 | cy.visit('/tp02.html')
9 | cy.contains('liste de courses')
10 | cy.contains('pain:0.95€')
11 | cy.contains('gel douche:2.85€')
12 | cy.contains('cahier à spirales:1.2€')
13 | cy.get('ul li').should('have.length', 3)
14 | })
15 |
16 | it('loads TP03', () => {
17 | cy.visit('/tp03.html')
18 |
19 | cy.get('table thead').should('exist')
20 | cy.get('table tbody tr').should('have.length', 4)
21 | cy.contains('Aucun étudiant sélectionné !')
22 | cy.should('not.contain', 'ethan yellow')
23 |
24 | cy.get('table tbody tr').eq(2).click()
25 | cy.contains('ethan yellow')
26 |
27 | cy.get('input').type('mary')
28 | cy.get('table tbody tr').should('have.length', 1)
29 | })
30 |
31 | it('loads TP04', () => {
32 | cy.visit('/tp04.html')
33 | cy.contains('liste de courses')
34 | cy.contains('pain:0.95€')
35 | cy.contains('gel douche:2.85€')
36 | cy.contains('cahier à spirales:1.2€')
37 | cy.get('ul li').should('have.length', 3)
38 |
39 | cy.get('input').eq(0).type('foo')
40 | cy.get('input').eq(1).type('42')
41 | cy.get('button').click()
42 | cy.contains('foo:42€')
43 | cy.get('ul li').should('have.length', 4)
44 | })
45 |
46 | it('loads TP05', () => {
47 | cy.visit('/tp05.html')
48 | cy.contains('liste de courses')
49 | cy.contains('pain:0.95€')
50 | cy.contains('gel douche:2.85€')
51 | cy.contains('cahier à spirales:1.2€')
52 | cy.get('ul li').should('have.length', 3)
53 |
54 | cy.get('input').eq(0).type('foo')
55 | cy.get('input').eq(1).type('100')
56 | cy.get('button').click()
57 | cy.contains('foo:100€')
58 | cy.get('ul li').should('have.length', 4)
59 | })
60 |
61 | it('loads TP06', () => {
62 | cy.visit('/tp06.html')
63 | cy.contains('Small, user = 1-admin')
64 | })
65 |
66 | it('loads TP07', () => {
67 | cy.visit('/tp07.html')
68 |
69 | cy.get('table thead').should('exist')
70 | cy.get('tr[data-testid=student-row]').should('have.length', 4)
71 | cy.contains('Aucun étudiant sélectionné !')
72 | cy.should('not.contain', 'ethan yellow')
73 |
74 | cy.get('table tbody tr').eq(2).click()
75 | cy.contains('ethan yellow')
76 |
77 | cy.get('input').type('mary')
78 | cy.get('tr[data-testid=student-row]').should('have.length', 1)
79 | })
80 |
81 | it('loads TP08', () => {
82 | cy.visit('/tp08.html')
83 |
84 | cy.contains('Home').click()
85 | cy.url().should('eq', 'http://localhost:8080/')
86 | cy.contains('Welcome !')
87 |
88 | cy.contains('About').click()
89 | cy.url().should('eq', 'http://localhost:8080/about')
90 | cy.contains('About this application...')
91 |
92 | cy.contains('Users').click()
93 | cy.url().should('eq', 'http://localhost:8080/users')
94 | cy.contains('Please select a user.')
95 | cy.contains('Anna').click()
96 | cy.url().should('eq', 'http://localhost:8080/users/anna')
97 | cy.contains('Details of anna')
98 | cy.get('img[src="/anna.jpeg"]').should('exist')
99 | cy.contains('Elsa').click()
100 | cy.url().should('eq', 'http://localhost:8080/users/elsa')
101 | cy.contains('Details of elsa')
102 | cy.get('img[src="/elsa.jpeg"]').should('exist')
103 | })
104 |
105 | it('loads TP09', () => {
106 | cy.visit('/tp09.html')
107 |
108 | cy.get('button').click()
109 | cy.contains(/^Sel : \d{1,3}/g)
110 | cy.contains('div', '6')
111 | })
112 | })
113 |
--------------------------------------------------------------------------------
/images/coffee.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/coffee.gif
--------------------------------------------------------------------------------
/images/components.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/components.png
--------------------------------------------------------------------------------
/images/flux-bestpractice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/flux-bestpractice.png
--------------------------------------------------------------------------------
/images/flux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/flux.png
--------------------------------------------------------------------------------
/images/mynameis.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/mynameis.jpg
--------------------------------------------------------------------------------
/images/postits.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/postits.jpg
--------------------------------------------------------------------------------
/images/react.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
11 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/images/redux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/redux.png
--------------------------------------------------------------------------------
/images/should-component-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/images/should-component-update.png
--------------------------------------------------------------------------------
/images/ts.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/itest/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React !
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/itest/webpack.config.itest.js:
--------------------------------------------------------------------------------
1 | const HtmlWebPackPlugin = require('html-webpack-plugin')
2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
3 | const CopyPlugin = require('copy-webpack-plugin')
4 | const path = require('path')
5 |
6 | module.exports = (env, argv = {}) => ({
7 | entry: {
8 | tp01: './TPs/solutions/TP-01/index.js',
9 | tp02: './TPs/solutions/TP-02/index.js',
10 | tp03: './TPs/solutions/TP-03/index.js',
11 | tp04: './TPs/solutions/TP-04/index.js',
12 | tp05: './TPs/solutions/TP-05/index.js',
13 | tp06: './TPs/solutions/TP-06/index.js',
14 | tp07: './TPs/solutions/TP-07/index.js',
15 | tp08: './TPs/solutions/TP-08/index.js',
16 | tp09: './TPs/solutions/TP-09/index.js',
17 | },
18 | output: {
19 | path: path.resolve(__dirname, 'build'),
20 | filename: '[name].js',
21 | publicPath: '/',
22 | },
23 |
24 | plugins: [
25 | new HtmlWebPackPlugin({
26 | inject: false,
27 | template: './itest/index.html',
28 | chunks: 'tp01',
29 | filename: './tp01.html',
30 | favicon: './public/favicon.ico',
31 | }),
32 | new HtmlWebPackPlugin({
33 | inject: false,
34 | template: './itest/index.html',
35 | chunks: 'tp02',
36 | filename: './tp02.html',
37 | favicon: './public/favicon.ico',
38 | }),
39 | new HtmlWebPackPlugin({
40 | inject: false,
41 | template: './itest/index.html',
42 | chunks: 'tp03',
43 | filename: './tp03.html',
44 | favicon: './public/favicon.ico',
45 | }),
46 | new HtmlWebPackPlugin({
47 | inject: false,
48 | template: './itest/index.html',
49 | chunks: 'tp04',
50 | filename: './tp04.html',
51 | favicon: './public/favicon.ico',
52 | }),
53 | new HtmlWebPackPlugin({
54 | inject: false,
55 | template: './itest/index.html',
56 | chunks: 'tp05',
57 | filename: './tp05.html',
58 | favicon: './public/favicon.ico',
59 | }),
60 | new HtmlWebPackPlugin({
61 | inject: false,
62 | template: './itest/index.html',
63 | chunks: 'tp06',
64 | filename: './tp06.html',
65 | favicon: './public/favicon.ico',
66 | }),
67 | new HtmlWebPackPlugin({
68 | inject: false,
69 | template: './itest/index.html',
70 | chunks: 'tp07',
71 | filename: './tp07.html',
72 | favicon: './public/favicon.ico',
73 | }),
74 | new HtmlWebPackPlugin({
75 | inject: false,
76 | template: './itest/index.html',
77 | chunks: 'tp08',
78 | filename: './tp08.html',
79 | favicon: './public/favicon.ico',
80 | }),
81 | new HtmlWebPackPlugin({
82 | inject: false,
83 | template: './itest/index.html',
84 | chunks: 'tp09',
85 | filename: './tp09.html',
86 | favicon: './public/favicon.ico',
87 | }),
88 | new CleanWebpackPlugin({ verbose: true }),
89 | new CopyPlugin({
90 | patterns: [{ from: 'public' }],
91 | }),
92 | ],
93 | resolve: {
94 | extensions: ['.js'],
95 | alias: {
96 | public: path.resolve(__dirname, '../public'),
97 | },
98 | },
99 | module: {
100 | rules: [
101 | {
102 | test: /\.js$/,
103 | exclude: /node_modules/,
104 | loader: 'babel-loader',
105 | },
106 | ],
107 | },
108 | devtool: false,
109 | })
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-es6-formation",
3 | "version": "1.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "@babel/core": "7.21.3",
7 | "@babel/plugin-proposal-class-properties": "7.18.6",
8 | "@babel/plugin-syntax-dynamic-import": "7.8.3",
9 | "@babel/preset-env": "7.20.2",
10 | "@babel/preset-react": "7.18.6",
11 | "@babel/runtime": "7.21.0",
12 | "@cypress/react": "7.0.3",
13 | "@testing-library/react": "14.0.0",
14 | "@types/react": "18.0.30",
15 | "babel-jest": "29.5.0",
16 | "babel-loader": "9.1.2",
17 | "clean-webpack-plugin": "4.0.0",
18 | "copy-webpack-plugin": "11.0.0",
19 | "cypress": "12.8.1",
20 | "html-webpack-plugin": "5.5.0",
21 | "husky": "8.0.3",
22 | "jest": "29.5.0",
23 | "jest-environment-jsdom": "29.5.0",
24 | "lint-staged": "13.2.0",
25 | "mini-css-extract-plugin": "2.7.5",
26 | "npm-check-updates": "16.8.0",
27 | "prettier": "2.8.7",
28 | "prop-types": "15.8.1",
29 | "react-test-renderer": "18.2.0",
30 | "source-map-loader": "4.0.1",
31 | "webpack": "5.76.3",
32 | "webpack-cli": "5.0.1",
33 | "webpack-dev-server": "4.13.1"
34 | },
35 | "dependencies": {
36 | "@emotion/react": "11.10.6",
37 | "@emotion/styled": "11.10.6",
38 | "@mui/material": "5.11.14",
39 | "@reduxjs/toolkit": "1.9.3",
40 | "@tanstack/react-query": "4.28.0",
41 | "axios": "1.3.4",
42 | "react": "18.2.0",
43 | "react-dom": "18.2.0",
44 | "react-redux": "8.0.5",
45 | "react-router": "6.9.0",
46 | "react-router-dom": "6.9.0",
47 | "redux": "4.2.1",
48 | "redux-thunk": "2.4.2",
49 | "typeface-roboto": "1.1.13"
50 | },
51 | "scripts": {
52 | "build": "webpack --mode production",
53 | "dev": "webpack --mode development",
54 | "itest-debug": "cypress open --config-file cypress/cypress.config.js",
55 | "itest": "cypress run --browser chrome --config-file cypress/cypress.config.js",
56 | "ncuu": "ncu -u",
57 | "prepare": "husky install",
58 | "start": "webpack serve --mode development",
59 | "starti": "webpack serve --mode development --config=./itest/webpack.config.itest.js",
60 | "test": "jest src",
61 | "test-coverage": "jest src --coverage"
62 | },
63 | "jest": {
64 | "transform": {
65 | "^.+\\.js$": "babel-jest"
66 | },
67 | "testEnvironment": "jsdom"
68 | },
69 | "lint-staged": {
70 | "*.{js,json}": [
71 | "prettier --write",
72 | "git add"
73 | ]
74 | },
75 | "engines" : {
76 | "node" : ">=16.0.0"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/public/anna.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/public/anna.jpeg
--------------------------------------------------------------------------------
/public/elsa.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/public/elsa.jpeg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/public/favicon.ico
--------------------------------------------------------------------------------
/public/items.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "label": "pain",
5 | "price": 0.95
6 | },
7 | {
8 | "id": 2,
9 | "label": "gel douche",
10 | "price": 2.85
11 | },
12 | {
13 | "id": 3,
14 | "label": "cahier à spirales",
15 | "price": 1.20
16 | }
17 | ]
--------------------------------------------------------------------------------
/public/students.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "lastname": "red",
5 | "firstname": "john",
6 | "grades": [4,5,8,3]
7 | },
8 | {
9 | "id": 2,
10 | "lastname": "red",
11 | "firstname": "mary",
12 | "grades": [7,6,8,3]
13 | },
14 | {
15 | "id": 3,
16 | "lastname": "yellow",
17 | "firstname": "ethan",
18 | "grades": [1,4,3,3]
19 | },
20 | {
21 | "id": 4,
22 | "lastname": "rose",
23 | "firstname": "ashley",
24 | "grades": [7,9,8,8]
25 | }
26 | ]
--------------------------------------------------------------------------------
/reveal/plugin/highlight/monokai.css:
--------------------------------------------------------------------------------
1 | /*
2 | Monokai style - ported by Luigi Maselli - http://grigio.org
3 | */
4 |
5 | .hljs {
6 | display: block;
7 | overflow-x: auto;
8 | padding: 0.5em;
9 | background: #272822;
10 | color: #ddd;
11 | }
12 |
13 | .hljs-tag,
14 | .hljs-keyword,
15 | .hljs-selector-tag,
16 | .hljs-literal,
17 | .hljs-strong,
18 | .hljs-name {
19 | color: #f92672;
20 | }
21 |
22 | .hljs-code {
23 | color: #66d9ef;
24 | }
25 |
26 | .hljs-class .hljs-title {
27 | color: white;
28 | }
29 |
30 | .hljs-attribute,
31 | .hljs-symbol,
32 | .hljs-regexp,
33 | .hljs-link {
34 | color: #bf79db;
35 | }
36 |
37 | .hljs-string,
38 | .hljs-bullet,
39 | .hljs-subst,
40 | .hljs-title,
41 | .hljs-section,
42 | .hljs-emphasis,
43 | .hljs-type,
44 | .hljs-built_in,
45 | .hljs-builtin-name,
46 | .hljs-selector-attr,
47 | .hljs-selector-pseudo,
48 | .hljs-addition,
49 | .hljs-variable,
50 | .hljs-template-tag,
51 | .hljs-template-variable {
52 | color: #a6e22e;
53 | }
54 |
55 | .hljs-comment,
56 | .hljs-quote,
57 | .hljs-deletion,
58 | .hljs-meta {
59 | color: #75715e;
60 | }
61 |
62 | .hljs-keyword,
63 | .hljs-selector-tag,
64 | .hljs-literal,
65 | .hljs-doctag,
66 | .hljs-title,
67 | .hljs-section,
68 | .hljs-type,
69 | .hljs-selector-id {
70 | font-weight: bold;
71 | }
72 |
--------------------------------------------------------------------------------
/reveal/plugin/highlight/zenburn.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | background: #3f3f3f;
13 | color: #dcdcdc;
14 | }
15 |
16 | .hljs-keyword,
17 | .hljs-selector-tag,
18 | .hljs-tag {
19 | color: #e3ceab;
20 | }
21 |
22 | .hljs-template-tag {
23 | color: #dcdcdc;
24 | }
25 |
26 | .hljs-number {
27 | color: #8cd0d3;
28 | }
29 |
30 | .hljs-variable,
31 | .hljs-template-variable,
32 | .hljs-attribute {
33 | color: #efdcbc;
34 | }
35 |
36 | .hljs-literal {
37 | color: #efefaf;
38 | }
39 |
40 | .hljs-subst {
41 | color: #8f8f8f;
42 | }
43 |
44 | .hljs-title,
45 | .hljs-name,
46 | .hljs-selector-id,
47 | .hljs-selector-class,
48 | .hljs-section,
49 | .hljs-type {
50 | color: #efef8f;
51 | }
52 |
53 | .hljs-symbol,
54 | .hljs-bullet,
55 | .hljs-link {
56 | color: #dca3a3;
57 | }
58 |
59 | .hljs-deletion,
60 | .hljs-string,
61 | .hljs-built_in,
62 | .hljs-builtin-name {
63 | color: #cc9393;
64 | }
65 |
66 | .hljs-addition,
67 | .hljs-comment,
68 | .hljs-quote,
69 | .hljs-meta {
70 | color: #7f9f7f;
71 | }
72 |
73 |
74 | .hljs-emphasis {
75 | font-style: italic;
76 | }
77 |
78 | .hljs-strong {
79 | font-weight: bold;
80 | }
81 |
--------------------------------------------------------------------------------
/reveal/plugin/math/katex.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for KaTeX.
4 | *
5 | * @author Hakim El Hattab
6 | * @author Gerhard Burger
7 | */
8 | export const KaTeX = () => {
9 | let deck
10 |
11 | let defaultOptions = {
12 | version: 'latest',
13 | delimiters: [
14 | { left: '$$', right: '$$', display: true }, // Note: $$ has to come before $
15 | { left: '$', right: '$', display: false },
16 | { left: '\\(', right: '\\)', display: false },
17 | { left: '\\[', right: '\\]', display: true },
18 | ],
19 | ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
20 | }
21 |
22 | const loadCss = src => {
23 | let link = document.createElement('link')
24 | link.rel = 'stylesheet'
25 | link.href = src
26 | document.head.appendChild(link)
27 | }
28 |
29 | /**
30 | * Loads a JavaScript file and returns a Promise for when it is loaded
31 | * Credits: https://aaronsmith.online/easily-load-an-external-script-using-javascript/
32 | */
33 | const loadScript = src => {
34 | return new Promise((resolve, reject) => {
35 | const script = document.createElement('script')
36 | script.type = 'text/javascript'
37 | script.onload = resolve
38 | script.onerror = reject
39 | script.src = src
40 | document.head.append(script)
41 | })
42 | }
43 |
44 | async function loadScripts(urls) {
45 | for (const url of urls) {
46 | await loadScript(url)
47 | }
48 | }
49 |
50 | return {
51 | id: 'katex',
52 |
53 | init: function (reveal) {
54 | deck = reveal
55 |
56 | let revealOptions = deck.getConfig().katex || {}
57 |
58 | let options = { ...defaultOptions, ...revealOptions }
59 | const { local, version, extensions, ...katexOptions } = options
60 |
61 | let baseUrl = options.local || 'https://cdn.jsdelivr.net/npm/katex'
62 | let versionString = options.local ? '' : '@' + options.version
63 |
64 | let cssUrl = baseUrl + versionString + '/dist/katex.min.css'
65 | let katexUrl = baseUrl + versionString + '/dist/katex.min.js'
66 | let mhchemUrl = baseUrl + versionString + '/dist/contrib/mhchem.min.js'
67 | let karUrl = baseUrl + versionString + '/dist/contrib/auto-render.min.js'
68 |
69 | let katexScripts = [katexUrl]
70 | if (options.extensions && options.extensions.includes('mhchem')) {
71 | katexScripts.push(mhchemUrl)
72 | }
73 | katexScripts.push(karUrl)
74 |
75 | const renderMath = () => {
76 | renderMathInElement(reveal.getSlidesElement(), katexOptions)
77 | deck.layout()
78 | }
79 |
80 | loadCss(cssUrl)
81 |
82 | // For some reason dynamically loading with defer attribute doesn't result in the expected behavior, the below code does
83 | loadScripts(katexScripts).then(() => {
84 | if (deck.isReady()) {
85 | renderMath()
86 | } else {
87 | deck.on('ready', renderMath.bind(this))
88 | }
89 | })
90 | },
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/reveal/plugin/math/mathjax2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for MathJax.
4 | *
5 | * @author Hakim El Hattab
6 | */
7 | export const MathJax2 = () => {
8 | // The reveal.js instance this plugin is attached to
9 | let deck
10 |
11 | let defaultOptions = {
12 | messageStyle: 'none',
13 | tex2jax: {
14 | inlineMath: [
15 | ['$', '$'],
16 | ['\\(', '\\)'],
17 | ],
18 | skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
19 | },
20 | skipStartupTypeset: true,
21 | }
22 |
23 | function loadScript(url, callback) {
24 | let head = document.querySelector('head')
25 | let script = document.createElement('script')
26 | script.type = 'text/javascript'
27 | script.src = url
28 |
29 | // Wrapper for callback to make sure it only fires once
30 | let finish = () => {
31 | if (typeof callback === 'function') {
32 | callback.call()
33 | callback = null
34 | }
35 | }
36 |
37 | script.onload = finish
38 |
39 | // IE
40 | script.onreadystatechange = () => {
41 | if (this.readyState === 'loaded') {
42 | finish()
43 | }
44 | }
45 |
46 | // Normal browsers
47 | head.appendChild(script)
48 | }
49 |
50 | return {
51 | id: 'mathjax2',
52 |
53 | init: function (reveal) {
54 | deck = reveal
55 |
56 | let revealOptions = deck.getConfig().mathjax2 || deck.getConfig().math || {}
57 |
58 | let options = { ...defaultOptions, ...revealOptions }
59 | let mathjax = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js'
60 | let config = options.config || 'TeX-AMS_HTML-full'
61 | let url = mathjax + '?config=' + config
62 |
63 | options.tex2jax = { ...defaultOptions.tex2jax, ...revealOptions.tex2jax }
64 |
65 | options.mathjax = options.config = null
66 |
67 | loadScript(url, function () {
68 | MathJax.Hub.Config(options)
69 |
70 | // Typeset followed by an immediate reveal.js layout since
71 | // the typesetting process could affect slide height
72 | MathJax.Hub.Queue(['Typeset', MathJax.Hub, deck.getRevealElement()])
73 | MathJax.Hub.Queue(deck.layout)
74 |
75 | // Reprocess equations in slides when they turn visible
76 | deck.on('slidechanged', function (event) {
77 | MathJax.Hub.Queue(['Typeset', MathJax.Hub, event.currentSlide])
78 | })
79 | })
80 | },
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/reveal/plugin/math/mathjax3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for MathJax 3
4 | *
5 | * @author Hakim El Hattab
6 | * @author Gerhard Burger
7 | */
8 | export const MathJax3 = () => {
9 | // The reveal.js instance this plugin is attached to
10 | let deck
11 |
12 | let defaultOptions = {
13 | tex: {
14 | inlineMath: [
15 | ['$', '$'],
16 | ['\\(', '\\)'],
17 | ],
18 | },
19 | options: {
20 | skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
21 | },
22 | startup: {
23 | ready: () => {
24 | MathJax.startup.defaultReady()
25 | MathJax.startup.promise.then(() => {
26 | Reveal.layout()
27 | })
28 | },
29 | },
30 | }
31 |
32 | function loadScript(url, callback) {
33 | let script = document.createElement('script')
34 | script.type = 'text/javascript'
35 | script.id = 'MathJax-script'
36 | script.src = url
37 | script.async = true
38 |
39 | // Wrapper for callback to make sure it only fires once
40 | script.onload = () => {
41 | if (typeof callback === 'function') {
42 | callback.call()
43 | callback = null
44 | }
45 | }
46 |
47 | document.head.appendChild(script)
48 | }
49 |
50 | return {
51 | id: 'mathjax3',
52 | init: function (reveal) {
53 | deck = reveal
54 |
55 | let revealOptions = deck.getConfig().mathjax3 || {}
56 | let options = { ...defaultOptions, ...revealOptions }
57 | options.tex = { ...defaultOptions.tex, ...revealOptions.tex }
58 | options.options = { ...options.options, ...defaultOptions.options }
59 | options.startup = { ...defaultOptions.startup, ...revealOptions.startup }
60 |
61 | let url = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'
62 | options.mathjax = null
63 |
64 | window.MathJax = options
65 |
66 | loadScript(url, function () {
67 | // Reprocess equations in slides when they turn visible
68 | Reveal.addEventListener('slidechanged', function (event) {
69 | MathJax.typeset()
70 | })
71 | })
72 | },
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/reveal/plugin/math/plugin.js:
--------------------------------------------------------------------------------
1 | import { KaTeX } from './katex'
2 | import { MathJax2 } from './mathjax2'
3 | import { MathJax3 } from './mathjax3'
4 |
5 | const defaultTypesetter = MathJax2
6 |
7 | /*!
8 | * This plugin is a wrapper for the MathJax2,
9 | * MathJax3 and KaTeX typesetter plugins.
10 | */
11 | export default Plugin = Object.assign(defaultTypesetter(), {
12 | KaTeX,
13 | MathJax2,
14 | MathJax3,
15 | })
16 |
--------------------------------------------------------------------------------
/reveal/plugin/notes/plugin.js:
--------------------------------------------------------------------------------
1 | import speakerViewHTML from './speaker-view.html';
2 |
3 | import marked from 'marked';
4 |
5 | /**
6 | * Handles opening of and synchronization with the reveal.js
7 | * notes window.
8 | *
9 | * Handshake process:
10 | * 1. This window posts 'connect' to notes window
11 | * - Includes URL of presentation to show
12 | * 2. Notes window responds with 'connected' when it is available
13 | * 3. This window proceeds to send the current presentation state
14 | * to the notes window
15 | */
16 | const Plugin = () => {
17 |
18 | let popup = null;
19 |
20 | let deck;
21 |
22 | function openNotes() {
23 |
24 | if (popup && !popup.closed) {
25 | popup.focus();
26 | return;
27 | }
28 |
29 | popup = window.open( 'about:blank', 'reveal.js - Notes', 'width=1100,height=700' );
30 | popup.marked = marked;
31 | popup.document.write( speakerViewHTML );
32 |
33 | if( !popup ) {
34 | alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
35 | return;
36 | }
37 |
38 | /**
39 | * Connect to the notes window through a postmessage handshake.
40 | * Using postmessage enables us to work in situations where the
41 | * origins differ, such as a presentation being opened from the
42 | * file system.
43 | */
44 | function connect() {
45 | // Keep trying to connect until we get a 'connected' message back
46 | let connectInterval = setInterval( function() {
47 | popup.postMessage( JSON.stringify( {
48 | namespace: 'reveal-notes',
49 | type: 'connect',
50 | url: window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search,
51 | state: deck.getState()
52 | } ), '*' );
53 | }, 500 );
54 |
55 | window.addEventListener( 'message', function( event ) {
56 | let data = JSON.parse( event.data );
57 | if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
58 | clearInterval( connectInterval );
59 | onConnected();
60 | }
61 | if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
62 | callRevealApi( data.methodName, data.arguments, data.callId );
63 | }
64 | } );
65 | }
66 |
67 | /**
68 | * Calls the specified Reveal.js method with the provided argument
69 | * and then pushes the result to the notes frame.
70 | */
71 | function callRevealApi( methodName, methodArguments, callId ) {
72 |
73 | let result = deck[methodName].apply( deck, methodArguments );
74 | popup.postMessage( JSON.stringify( {
75 | namespace: 'reveal-notes',
76 | type: 'return',
77 | result: result,
78 | callId: callId
79 | } ), '*' );
80 |
81 | }
82 |
83 | /**
84 | * Posts the current slide data to the notes window
85 | */
86 | function post( event ) {
87 |
88 | let slideElement = deck.getCurrentSlide(),
89 | notesElement = slideElement.querySelector( 'aside.notes' ),
90 | fragmentElement = slideElement.querySelector( '.current-fragment' );
91 |
92 | let messageData = {
93 | namespace: 'reveal-notes',
94 | type: 'state',
95 | notes: '',
96 | markdown: false,
97 | whitespace: 'normal',
98 | state: deck.getState()
99 | };
100 |
101 | // Look for notes defined in a slide attribute
102 | if( slideElement.hasAttribute( 'data-notes' ) ) {
103 | messageData.notes = slideElement.getAttribute( 'data-notes' );
104 | messageData.whitespace = 'pre-wrap';
105 | }
106 |
107 | // Look for notes defined in a fragment
108 | if( fragmentElement ) {
109 | let fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
110 | if( fragmentNotes ) {
111 | notesElement = fragmentNotes;
112 | }
113 | else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
114 | messageData.notes = fragmentElement.getAttribute( 'data-notes' );
115 | messageData.whitespace = 'pre-wrap';
116 |
117 | // In case there are slide notes
118 | notesElement = null;
119 | }
120 | }
121 |
122 | // Look for notes defined in an aside element
123 | if( notesElement ) {
124 | messageData.notes = notesElement.innerHTML;
125 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
126 | }
127 |
128 | popup.postMessage( JSON.stringify( messageData ), '*' );
129 |
130 | }
131 |
132 | /**
133 | * Called once we have established a connection to the notes
134 | * window.
135 | */
136 | function onConnected() {
137 |
138 | // Monitor events that trigger a change in state
139 | deck.on( 'slidechanged', post );
140 | deck.on( 'fragmentshown', post );
141 | deck.on( 'fragmenthidden', post );
142 | deck.on( 'overviewhidden', post );
143 | deck.on( 'overviewshown', post );
144 | deck.on( 'paused', post );
145 | deck.on( 'resumed', post );
146 |
147 | // Post the initial state
148 | post();
149 |
150 | }
151 |
152 | connect();
153 |
154 | }
155 |
156 | return {
157 | id: 'notes',
158 |
159 | init: function( reveal ) {
160 |
161 | deck = reveal;
162 |
163 | if( !/receiver/i.test( window.location.search ) ) {
164 |
165 | // If the there's a 'notes' query set, open directly
166 | if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
167 | openNotes();
168 | }
169 |
170 | // Open the notes when the 's' key is hit
171 | deck.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
172 | openNotes();
173 | } );
174 |
175 | }
176 |
177 | },
178 |
179 | open: openNotes
180 | };
181 |
182 | };
183 |
184 | export default Plugin;
185 |
--------------------------------------------------------------------------------
/reveal/plugin/search/plugin.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
3 | * by navigatating to that slide and highlighting it.
4 | *
5 | * @author Jon Snyder , February 2013
6 | */
7 |
8 | const Plugin = () => {
9 |
10 | // The reveal.js instance this plugin is attached to
11 | let deck;
12 |
13 | let searchElement;
14 | let searchButton;
15 | let searchInput;
16 |
17 | let matchedSlides;
18 | let currentMatchedIndex;
19 | let searchboxDirty;
20 | let hilitor;
21 |
22 | function render() {
23 |
24 | searchElement = document.createElement( 'div' );
25 | searchElement.classList.add( 'searchbox' );
26 | searchElement.style.position = 'absolute';
27 | searchElement.style.top = '10px';
28 | searchElement.style.right = '10px';
29 | searchElement.style.zIndex = 10;
30 |
31 | //embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
32 | searchElement.innerHTML = `
33 | `;
34 |
35 | searchInput = searchElement.querySelector( '.searchinput' );
36 | searchInput.style.width = '240px';
37 | searchInput.style.fontSize = '14px';
38 | searchInput.style.padding = '4px 6px';
39 | searchInput.style.color = '#000';
40 | searchInput.style.background = '#fff';
41 | searchInput.style.borderRadius = '2px';
42 | searchInput.style.border = '0';
43 | searchInput.style.outline = '0';
44 | searchInput.style.boxShadow = '0 2px 18px rgba(0, 0, 0, 0.2)';
45 | searchInput.style['-webkit-appearance'] = 'none';
46 |
47 | deck.getRevealElement().appendChild( searchElement );
48 |
49 | // searchButton.addEventListener( 'click', function(event) {
50 | // doSearch();
51 | // }, false );
52 |
53 | searchInput.addEventListener( 'keyup', function( event ) {
54 | switch (event.keyCode) {
55 | case 13:
56 | event.preventDefault();
57 | doSearch();
58 | searchboxDirty = false;
59 | break;
60 | default:
61 | searchboxDirty = true;
62 | }
63 | }, false );
64 |
65 | closeSearch();
66 |
67 | }
68 |
69 | function openSearch() {
70 | if( !searchElement ) render();
71 |
72 | searchElement.style.display = 'inline';
73 | searchInput.focus();
74 | searchInput.select();
75 | }
76 |
77 | function closeSearch() {
78 | if( !searchElement ) render();
79 |
80 | searchElement.style.display = 'none';
81 | if(hilitor) hilitor.remove();
82 | }
83 |
84 | function toggleSearch() {
85 | if( !searchElement ) render();
86 |
87 | if (searchElement.style.display !== 'inline') {
88 | openSearch();
89 | }
90 | else {
91 | closeSearch();
92 | }
93 | }
94 |
95 | function doSearch() {
96 | //if there's been a change in the search term, perform a new search:
97 | if (searchboxDirty) {
98 | var searchstring = searchInput.value;
99 |
100 | if (searchstring === '') {
101 | if(hilitor) hilitor.remove();
102 | matchedSlides = null;
103 | }
104 | else {
105 | //find the keyword amongst the slides
106 | hilitor = new Hilitor("slidecontent");
107 | matchedSlides = hilitor.apply(searchstring);
108 | currentMatchedIndex = 0;
109 | }
110 | }
111 |
112 | if (matchedSlides) {
113 | //navigate to the next slide that has the keyword, wrapping to the first if necessary
114 | if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
115 | currentMatchedIndex = 0;
116 | }
117 | if (matchedSlides.length > currentMatchedIndex) {
118 | deck.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
119 | currentMatchedIndex++;
120 | }
121 | }
122 | }
123 |
124 | // Original JavaScript code by Chirp Internet: www.chirp.com.au
125 | // Please acknowledge use of this code by including this header.
126 | // 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
127 | function Hilitor(id, tag) {
128 |
129 | var targetNode = document.getElementById(id) || document.body;
130 | var hiliteTag = tag || "EM";
131 | var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$");
132 | var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
133 | var wordColor = [];
134 | var colorIdx = 0;
135 | var matchRegex = "";
136 | var matchingSlides = [];
137 |
138 | this.setRegex = function(input)
139 | {
140 | input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
141 | matchRegex = new RegExp("(" + input + ")","i");
142 | }
143 |
144 | this.getRegex = function()
145 | {
146 | return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
147 | }
148 |
149 | // recursively apply word highlighting
150 | this.hiliteWords = function(node)
151 | {
152 | if(node == undefined || !node) return;
153 | if(!matchRegex) return;
154 | if(skipTags.test(node.nodeName)) return;
155 |
156 | if(node.hasChildNodes()) {
157 | for(var i=0; i < node.childNodes.length; i++)
158 | this.hiliteWords(node.childNodes[i]);
159 | }
160 | if(node.nodeType == 3) { // NODE_TEXT
161 | var nv, regs;
162 | if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
163 | //find the slide's section element and save it in our list of matching slides
164 | var secnode = node;
165 | while (secnode != null && secnode.nodeName != 'SECTION') {
166 | secnode = secnode.parentNode;
167 | }
168 |
169 | var slideIndex = deck.getIndices(secnode);
170 | var slidelen = matchingSlides.length;
171 | var alreadyAdded = false;
172 | for (var i=0; i < slidelen; i++) {
173 | if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
174 | alreadyAdded = true;
175 | }
176 | }
177 | if (! alreadyAdded) {
178 | matchingSlides.push(slideIndex);
179 | }
180 |
181 | if(!wordColor[regs[0].toLowerCase()]) {
182 | wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
183 | }
184 |
185 | var match = document.createElement(hiliteTag);
186 | match.appendChild(document.createTextNode(regs[0]));
187 | match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
188 | match.style.fontStyle = "inherit";
189 | match.style.color = "#000";
190 |
191 | var after = node.splitText(regs.index);
192 | after.nodeValue = after.nodeValue.substring(regs[0].length);
193 | node.parentNode.insertBefore(match, after);
194 | }
195 | }
196 | };
197 |
198 | // remove highlighting
199 | this.remove = function()
200 | {
201 | var arr = document.getElementsByTagName(hiliteTag);
202 | var el;
203 | while(arr.length && (el = arr[0])) {
204 | el.parentNode.replaceChild(el.firstChild, el);
205 | }
206 | };
207 |
208 | // start highlighting at target node
209 | this.apply = function(input)
210 | {
211 | if(input == undefined || !input) return;
212 | this.remove();
213 | this.setRegex(input);
214 | this.hiliteWords(targetNode);
215 | return matchingSlides;
216 | };
217 |
218 | }
219 |
220 | return {
221 |
222 | id: 'search',
223 |
224 | init: reveal => {
225 |
226 | deck = reveal;
227 | deck.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' );
228 |
229 | document.addEventListener( 'keydown', function( event ) {
230 | if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f
231 | event.preventDefault();
232 | toggleSearch();
233 | }
234 | }, false );
235 |
236 | },
237 |
238 | open: openSearch
239 |
240 | }
241 | };
242 |
243 | export default Plugin;
--------------------------------------------------------------------------------
/reveal/plugin/zoom/zoom.esm.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js Zoom plugin
3 | */
4 | var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))}},t=function(){var e=1,o=0,n=0,i=-1,d=-1,s="WebkitTransform"in document.body.style||"MozTransform"in document.body.style||"msTransform"in document.body.style||"OTransform"in document.body.style||"transform"in document.body.style;function r(t,o){var n=y();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,s)if(1===o)document.body.style.transform="",document.body.style.OTransform="",document.body.style.msTransform="",document.body.style.MozTransform="",document.body.style.WebkitTransform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.OTransformOrigin=i,document.body.style.msTransformOrigin=i,document.body.style.MozTransformOrigin=i,document.body.style.WebkitTransformOrigin=i,document.body.style.transform=d,document.body.style.OTransform=d,document.body.style.msTransform=d,document.body.style.MozTransform=d,document.body.style.WebkitTransform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function m(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=y();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function y(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return s&&(document.body.style.transition="transform 0.8s ease",document.body.style.OTransition="-o-transform 0.8s ease",document.body.style.msTransition="-ms-transform 0.8s ease",document.body.style.MozTransition="-moz-transform 0.8s ease",document.body.style.WebkitTransition="-webkit-transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,r(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(m,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),r({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();export default function(){return e}
5 |
--------------------------------------------------------------------------------
/reveal/plugin/zoom/zoom.js:
--------------------------------------------------------------------------------
1 | !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o():"function"==typeof define&&define.amd?define(o):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=o()}(this,(function(){"use strict";
2 | /*!
3 | * reveal.js Zoom plugin
4 | */var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(t){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;t[i]&&!e.isOverview()&&(t.preventDefault(),o.to({x:t.clientX,y:t.clientY,scale:d,pan:!1}))}))}},o=function(){var e=1,t=0,n=0,i=-1,d=-1,s="WebkitTransform"in document.body.style||"MozTransform"in document.body.style||"msTransform"in document.body.style||"OTransform"in document.body.style||"transform"in document.body.style;function r(o,t){var n=l();if(o.width=o.width||1,o.height=o.height||1,o.x-=(window.innerWidth-o.width*t)/2,o.y-=(window.innerHeight-o.height*t)/2,s)if(1===t)document.body.style.transform="",document.body.style.OTransform="",document.body.style.msTransform="",document.body.style.MozTransform="",document.body.style.WebkitTransform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-o.x+"px,"+-o.y+"px) scale("+t+")";document.body.style.transformOrigin=i,document.body.style.OTransformOrigin=i,document.body.style.msTransformOrigin=i,document.body.style.MozTransformOrigin=i,document.body.style.WebkitTransformOrigin=i,document.body.style.transform=d,document.body.style.OTransform=d,document.body.style.msTransform=d,document.body.style.MozTransform=d,document.body.style.WebkitTransform=d}else 1===t?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+o.x)/t+"px",document.body.style.top=-(n.y+o.y)/t+"px",document.body.style.width=100*t+"%",document.body.style.height=100*t+"%",document.body.style.zoom=t);e=t,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function m(){var o=.12*window.innerWidth,i=.12*window.innerHeight,d=l();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),twindow.innerWidth-o&&window.scroll(d.x+(1-(window.innerWidth-t)/o)*(14/e),d.y)}function l(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return s&&(document.body.style.transition="transform 0.8s ease",document.body.style.OTransition="-o-transform 0.8s ease",document.body.style.msTransition="-ms-transform 0.8s ease",document.body.style.MozTransition="-moz-transform 0.8s ease",document.body.style.WebkitTransition="-webkit-transform 0.8s ease"),document.addEventListener("keyup",(function(t){1!==e&&27===t.keyCode&&o.out()})),document.addEventListener("mousemove",(function(o){1!==e&&(t=o.clientX,n=o.clientY)})),{to:function(t){if(1!==e)o.out();else{if(t.x=t.x||0,t.y=t.y||0,t.element){var n=t.element.getBoundingClientRect();t.x=n.left-20,t.y=n.top-20,t.width=n.width+40,t.height=n.height+40}void 0!==t.width&&void 0!==t.height&&(t.scale=Math.max(Math.min(window.innerWidth/t.width,window.innerHeight/t.height),1)),t.scale>1&&(t.x*=t.scale,t.y*=t.scale,r(t,t.scale),!1!==t.pan&&(i=setTimeout((function(){d=setInterval(m,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),r({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();return function(){return e}}));
5 |
--------------------------------------------------------------------------------
/reveal/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v4.0 | 20180602
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | main, menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, main, menu, nav, section {
29 | display: block;
30 | }
--------------------------------------------------------------------------------
/reveal/theme/black.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Black theme for reveal.js. This is the opposite of the 'white' theme.
3 | *
4 | * By Hakim El Hattab, http://hakim.se
5 | */
6 | @import url(./fonts/source-sans-pro/source-sans-pro.css);
7 | section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
8 | color: #222;
9 | }
10 |
11 | /*********************************************
12 | * GLOBAL STYLES
13 | *********************************************/
14 | :root {
15 | --r-background-color: #191919;
16 | --r-main-font: Source Sans Pro, Helvetica, sans-serif;
17 | --r-main-font-size: 42px;
18 | --r-main-color: #fff;
19 | --r-block-margin: 20px;
20 | --r-heading-margin: 0 0 20px 0;
21 | --r-heading-font: Source Sans Pro, Helvetica, sans-serif;
22 | --r-heading-color: #fff;
23 | --r-heading-line-height: 1.2;
24 | --r-heading-letter-spacing: normal;
25 | --r-heading-text-transform: uppercase;
26 | --r-heading-text-shadow: none;
27 | --r-heading-font-weight: 600;
28 | --r-heading1-text-shadow: none;
29 | --r-heading1-size: 2.5em;
30 | --r-heading2-size: 1.6em;
31 | --r-heading3-size: 1.3em;
32 | --r-heading4-size: 1em;
33 | --r-code-font: monospace;
34 | --r-link-color: #42affa;
35 | --r-link-color-dark: #068de9;
36 | --r-link-color-hover: #8dcffc;
37 | --r-selection-background-color: #bee4fd;
38 | --r-selection-color: #fff;
39 | }
40 |
41 | .reveal-viewport {
42 | background: #191919;
43 | background-color: var(--r-background-color);
44 | }
45 |
46 | .reveal {
47 | font-family: var(--r-main-font);
48 | font-size: var(--r-main-font-size);
49 | font-weight: normal;
50 | color: var(--r-main-color);
51 | }
52 |
53 | .reveal ::selection {
54 | color: var(--r-selection-color);
55 | background: var(--r-selection-background-color);
56 | text-shadow: none;
57 | }
58 |
59 | .reveal ::-moz-selection {
60 | color: var(--r-selection-color);
61 | background: var(--r-selection-background-color);
62 | text-shadow: none;
63 | }
64 |
65 | .reveal .slides section,
66 | .reveal .slides section > section {
67 | line-height: 1.3;
68 | font-weight: inherit;
69 | }
70 |
71 | /*********************************************
72 | * HEADERS
73 | *********************************************/
74 | .reveal h1,
75 | .reveal h2,
76 | .reveal h3,
77 | .reveal h4,
78 | .reveal h5,
79 | .reveal h6 {
80 | margin: var(--r-heading-margin);
81 | color: var(--r-heading-color);
82 | font-family: var(--r-heading-font);
83 | font-weight: var(--r-heading-font-weight);
84 | line-height: var(--r-heading-line-height);
85 | letter-spacing: var(--r-heading-letter-spacing);
86 | text-transform: var(--r-heading-text-transform);
87 | text-shadow: var(--r-heading-text-shadow);
88 | word-wrap: break-word;
89 | }
90 |
91 | .reveal h1 {
92 | font-size: var(--r-heading1-size);
93 | }
94 |
95 | .reveal h2 {
96 | font-size: var(--r-heading2-size);
97 | }
98 |
99 | .reveal h3 {
100 | font-size: var(--r-heading3-size);
101 | }
102 |
103 | .reveal h4 {
104 | font-size: var(--r-heading4-size);
105 | }
106 |
107 | .reveal h1 {
108 | text-shadow: var(--r-heading1-text-shadow);
109 | }
110 |
111 | /*********************************************
112 | * OTHER
113 | *********************************************/
114 | .reveal p {
115 | margin: var(--r-block-margin) 0;
116 | line-height: 1.3;
117 | }
118 |
119 | /* Remove trailing margins after titles */
120 | .reveal h1:last-child,
121 | .reveal h2:last-child,
122 | .reveal h3:last-child,
123 | .reveal h4:last-child,
124 | .reveal h5:last-child,
125 | .reveal h6:last-child {
126 | margin-bottom: 0;
127 | }
128 |
129 | /* Ensure certain elements are never larger than the slide itself */
130 | .reveal img,
131 | .reveal video,
132 | .reveal iframe {
133 | max-width: 95%;
134 | max-height: 95%;
135 | }
136 |
137 | .reveal strong,
138 | .reveal b {
139 | font-weight: bold;
140 | }
141 |
142 | .reveal em {
143 | font-style: italic;
144 | }
145 |
146 | .reveal ol,
147 | .reveal dl,
148 | .reveal ul {
149 | display: inline-block;
150 | text-align: left;
151 | margin: 0 0 0 1em;
152 | }
153 |
154 | .reveal ol {
155 | list-style-type: decimal;
156 | }
157 |
158 | .reveal ul {
159 | list-style-type: disc;
160 | }
161 |
162 | .reveal ul ul {
163 | list-style-type: square;
164 | }
165 |
166 | .reveal ul ul ul {
167 | list-style-type: circle;
168 | }
169 |
170 | .reveal ul ul,
171 | .reveal ul ol,
172 | .reveal ol ol,
173 | .reveal ol ul {
174 | display: block;
175 | margin-left: 40px;
176 | }
177 |
178 | .reveal dt {
179 | font-weight: bold;
180 | }
181 |
182 | .reveal dd {
183 | margin-left: 40px;
184 | }
185 |
186 | .reveal blockquote {
187 | display: block;
188 | position: relative;
189 | width: 70%;
190 | margin: var(--r-block-margin) auto;
191 | padding: 5px;
192 | font-style: italic;
193 | background: rgba(255, 255, 255, 0.05);
194 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
195 | }
196 |
197 | .reveal blockquote p:first-child,
198 | .reveal blockquote p:last-child {
199 | display: inline-block;
200 | }
201 |
202 | .reveal q {
203 | font-style: italic;
204 | }
205 |
206 | .reveal pre {
207 | display: block;
208 | position: relative;
209 | width: 90%;
210 | margin: var(--r-block-margin) auto;
211 | text-align: left;
212 | font-size: 0.55em;
213 | font-family: var(--r-code-font);
214 | line-height: 1.2em;
215 | word-wrap: break-word;
216 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
217 | }
218 |
219 | .reveal code {
220 | font-family: var(--r-code-font);
221 | text-transform: none;
222 | tab-size: 2;
223 | }
224 |
225 | .reveal pre code {
226 | display: block;
227 | padding: 5px;
228 | overflow: auto;
229 | max-height: 400px;
230 | word-wrap: normal;
231 | }
232 |
233 | .reveal .code-wrapper {
234 | white-space: normal;
235 | }
236 |
237 | .reveal .code-wrapper code {
238 | white-space: pre;
239 | }
240 |
241 | .reveal table {
242 | margin: auto;
243 | border-collapse: collapse;
244 | border-spacing: 0;
245 | }
246 |
247 | .reveal table th {
248 | font-weight: bold;
249 | }
250 |
251 | .reveal table th,
252 | .reveal table td {
253 | text-align: left;
254 | padding: 0.2em 0.5em 0.2em 0.5em;
255 | border-bottom: 1px solid;
256 | }
257 |
258 | .reveal table th[align=center],
259 | .reveal table td[align=center] {
260 | text-align: center;
261 | }
262 |
263 | .reveal table th[align=right],
264 | .reveal table td[align=right] {
265 | text-align: right;
266 | }
267 |
268 | .reveal table tbody tr:last-child th,
269 | .reveal table tbody tr:last-child td {
270 | border-bottom: none;
271 | }
272 |
273 | .reveal sup {
274 | vertical-align: super;
275 | font-size: smaller;
276 | }
277 |
278 | .reveal sub {
279 | vertical-align: sub;
280 | font-size: smaller;
281 | }
282 |
283 | .reveal small {
284 | display: inline-block;
285 | font-size: 0.6em;
286 | line-height: 1.2em;
287 | vertical-align: top;
288 | }
289 |
290 | .reveal small * {
291 | vertical-align: top;
292 | }
293 |
294 | .reveal img {
295 | margin: var(--r-block-margin) 0;
296 | }
297 |
298 | /*********************************************
299 | * LINKS
300 | *********************************************/
301 | .reveal a {
302 | color: var(--r-link-color);
303 | text-decoration: none;
304 | transition: color 0.15s ease;
305 | }
306 |
307 | .reveal a:hover {
308 | color: var(--r-link-color-hover);
309 | text-shadow: none;
310 | border: none;
311 | }
312 |
313 | .reveal .roll span:after {
314 | color: #fff;
315 | background: var(--r-link-color-dark);
316 | }
317 |
318 | /*********************************************
319 | * Frame helper
320 | *********************************************/
321 | .reveal .r-frame {
322 | border: 4px solid var(--r-main-color);
323 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
324 | }
325 |
326 | .reveal a .r-frame {
327 | transition: all 0.15s linear;
328 | }
329 |
330 | .reveal a:hover .r-frame {
331 | border-color: var(--r-link-color);
332 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
333 | }
334 |
335 | /*********************************************
336 | * NAVIGATION CONTROLS
337 | *********************************************/
338 | .reveal .controls {
339 | color: var(--r-link-color);
340 | }
341 |
342 | /*********************************************
343 | * PROGRESS BAR
344 | *********************************************/
345 | .reveal .progress {
346 | background: rgba(0, 0, 0, 0.2);
347 | color: var(--r-link-color);
348 | }
349 |
350 | /*********************************************
351 | * PRINT BACKGROUND
352 | *********************************************/
353 | @media print {
354 | .backgrounds {
355 | background-color: var(--r-background-color);
356 | }
357 | }
--------------------------------------------------------------------------------
/reveal/theme/fonts/league-gothic/LICENSE:
--------------------------------------------------------------------------------
1 | SIL Open Font License (OFL)
2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
3 |
--------------------------------------------------------------------------------
/reveal/theme/fonts/league-gothic/league-gothic.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'League Gothic';
3 | src: url('./league-gothic.eot');
4 | src: url('./league-gothic.eot?#iefix') format('embedded-opentype'),
5 | url('./league-gothic.woff') format('woff'),
6 | url('./league-gothic.ttf') format('truetype');
7 |
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
--------------------------------------------------------------------------------
/reveal/theme/fonts/league-gothic/league-gothic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/league-gothic/league-gothic.eot
--------------------------------------------------------------------------------
/reveal/theme/fonts/league-gothic/league-gothic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/league-gothic/league-gothic.ttf
--------------------------------------------------------------------------------
/reveal/theme/fonts/league-gothic/league-gothic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/league-gothic/league-gothic.woff
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/LICENSE:
--------------------------------------------------------------------------------
1 | SIL Open Font License
2 |
3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
4 |
5 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
7 |
8 | —————————————————————————————-
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | —————————————————————————————-
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
14 |
15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
16 |
17 | DEFINITIONS
18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
19 |
20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s).
21 |
22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
23 |
24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
25 |
26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
27 |
28 | PERMISSION & CONDITIONS
29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
30 |
31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
32 |
33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
34 |
35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
36 |
37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
38 |
39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
40 |
41 | TERMINATION
42 | This license becomes null and void if any of the above conditions are not met.
43 |
44 | DISCLAIMER
45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.eot
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-italic.woff
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.eot
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-regular.woff
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbcazaux/formation-reactjs-es6/8d0b8cb422592e4b96751e59e099b7f97abdc0a9/reveal/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff
--------------------------------------------------------------------------------
/reveal/theme/fonts/source-sans-pro/source-sans-pro.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Source Sans Pro';
3 | src: url('./source-sans-pro-regular.eot');
4 | src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'),
5 | url('./source-sans-pro-regular.woff') format('woff'),
6 | url('./source-sans-pro-regular.ttf') format('truetype');
7 | font-weight: normal;
8 | font-style: normal;
9 | }
10 |
11 | @font-face {
12 | font-family: 'Source Sans Pro';
13 | src: url('./source-sans-pro-italic.eot');
14 | src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'),
15 | url('./source-sans-pro-italic.woff') format('woff'),
16 | url('./source-sans-pro-italic.ttf') format('truetype');
17 | font-weight: normal;
18 | font-style: italic;
19 | }
20 |
21 | @font-face {
22 | font-family: 'Source Sans Pro';
23 | src: url('./source-sans-pro-semibold.eot');
24 | src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'),
25 | url('./source-sans-pro-semibold.woff') format('woff'),
26 | url('./source-sans-pro-semibold.ttf') format('truetype');
27 | font-weight: 600;
28 | font-style: normal;
29 | }
30 |
31 | @font-face {
32 | font-family: 'Source Sans Pro';
33 | src: url('./source-sans-pro-semibolditalic.eot');
34 | src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'),
35 | url('./source-sans-pro-semibolditalic.woff') format('woff'),
36 | url('./source-sans-pro-semibolditalic.ttf') format('truetype');
37 | font-weight: 600;
38 | font-style: italic;
39 | }
40 |
--------------------------------------------------------------------------------
/reveal/theme/moon.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Solarized Dark theme for reveal.js.
3 | * Author: Achim Staebler
4 | */
5 | @import url(./fonts/league-gothic/league-gothic.css);
6 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
7 | /**
8 | * Solarized colors by Ethan Schoonover
9 | */
10 | html * {
11 | color-profile: sRGB;
12 | rendering-intent: auto;
13 | }
14 |
15 | section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
16 | color: #222;
17 | }
18 |
19 | /*********************************************
20 | * GLOBAL STYLES
21 | *********************************************/
22 | :root {
23 | --r-background-color: #002b36;
24 | --r-main-font: Lato, sans-serif;
25 | --r-main-font-size: 40px;
26 | --r-main-color: #93a1a1;
27 | --r-block-margin: 20px;
28 | --r-heading-margin: 0 0 20px 0;
29 | --r-heading-font: League Gothic, Impact, sans-serif;
30 | --r-heading-color: #eee8d5;
31 | --r-heading-line-height: 1.2;
32 | --r-heading-letter-spacing: normal;
33 | --r-heading-text-transform: uppercase;
34 | --r-heading-text-shadow: none;
35 | --r-heading-font-weight: normal;
36 | --r-heading1-text-shadow: none;
37 | --r-heading1-size: 3.77em;
38 | --r-heading2-size: 2.11em;
39 | --r-heading3-size: 1.55em;
40 | --r-heading4-size: 1em;
41 | --r-code-font: monospace;
42 | --r-link-color: #268bd2;
43 | --r-link-color-dark: #1a6091;
44 | --r-link-color-hover: #78b9e6;
45 | --r-selection-background-color: #d33682;
46 | --r-selection-color: #fff;
47 | }
48 |
49 | .reveal-viewport {
50 | background: #002b36;
51 | background-color: var(--r-background-color);
52 | }
53 |
54 | .reveal {
55 | font-family: var(--r-main-font);
56 | font-size: var(--r-main-font-size);
57 | font-weight: normal;
58 | color: var(--r-main-color);
59 | }
60 |
61 | .reveal ::selection {
62 | color: var(--r-selection-color);
63 | background: var(--r-selection-background-color);
64 | text-shadow: none;
65 | }
66 |
67 | .reveal ::-moz-selection {
68 | color: var(--r-selection-color);
69 | background: var(--r-selection-background-color);
70 | text-shadow: none;
71 | }
72 |
73 | .reveal .slides section,
74 | .reveal .slides section > section {
75 | line-height: 1.3;
76 | font-weight: inherit;
77 | }
78 |
79 | /*********************************************
80 | * HEADERS
81 | *********************************************/
82 | .reveal h1,
83 | .reveal h2,
84 | .reveal h3,
85 | .reveal h4,
86 | .reveal h5,
87 | .reveal h6 {
88 | margin: var(--r-heading-margin);
89 | color: var(--r-heading-color);
90 | font-family: var(--r-heading-font);
91 | font-weight: var(--r-heading-font-weight);
92 | line-height: var(--r-heading-line-height);
93 | letter-spacing: var(--r-heading-letter-spacing);
94 | text-transform: var(--r-heading-text-transform);
95 | text-shadow: var(--r-heading-text-shadow);
96 | word-wrap: break-word;
97 | }
98 |
99 | .reveal h1 {
100 | font-size: var(--r-heading1-size);
101 | }
102 |
103 | .reveal h2 {
104 | font-size: var(--r-heading2-size);
105 | }
106 |
107 | .reveal h3 {
108 | font-size: var(--r-heading3-size);
109 | }
110 |
111 | .reveal h4 {
112 | font-size: var(--r-heading4-size);
113 | }
114 |
115 | .reveal h1 {
116 | text-shadow: var(--r-heading1-text-shadow);
117 | }
118 |
119 | /*********************************************
120 | * OTHER
121 | *********************************************/
122 | .reveal p {
123 | margin: var(--r-block-margin) 0;
124 | line-height: 1.3;
125 | }
126 |
127 | /* Remove trailing margins after titles */
128 | .reveal h1:last-child,
129 | .reveal h2:last-child,
130 | .reveal h3:last-child,
131 | .reveal h4:last-child,
132 | .reveal h5:last-child,
133 | .reveal h6:last-child {
134 | margin-bottom: 0;
135 | }
136 |
137 | /* Ensure certain elements are never larger than the slide itself */
138 | .reveal img,
139 | .reveal video,
140 | .reveal iframe {
141 | max-width: 95%;
142 | max-height: 95%;
143 | }
144 |
145 | .reveal strong,
146 | .reveal b {
147 | font-weight: bold;
148 | }
149 |
150 | .reveal em {
151 | font-style: italic;
152 | }
153 |
154 | .reveal ol,
155 | .reveal dl,
156 | .reveal ul {
157 | display: inline-block;
158 | text-align: left;
159 | margin: 0 0 0 1em;
160 | }
161 |
162 | .reveal ol {
163 | list-style-type: decimal;
164 | }
165 |
166 | .reveal ul {
167 | list-style-type: disc;
168 | }
169 |
170 | .reveal ul ul {
171 | list-style-type: square;
172 | }
173 |
174 | .reveal ul ul ul {
175 | list-style-type: circle;
176 | }
177 |
178 | .reveal ul ul,
179 | .reveal ul ol,
180 | .reveal ol ol,
181 | .reveal ol ul {
182 | display: block;
183 | margin-left: 40px;
184 | }
185 |
186 | .reveal dt {
187 | font-weight: bold;
188 | }
189 |
190 | .reveal dd {
191 | margin-left: 40px;
192 | }
193 |
194 | .reveal blockquote {
195 | display: block;
196 | position: relative;
197 | width: 70%;
198 | margin: var(--r-block-margin) auto;
199 | padding: 5px;
200 | font-style: italic;
201 | background: rgba(255, 255, 255, 0.05);
202 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
203 | }
204 |
205 | .reveal blockquote p:first-child,
206 | .reveal blockquote p:last-child {
207 | display: inline-block;
208 | }
209 |
210 | .reveal q {
211 | font-style: italic;
212 | }
213 |
214 | .reveal pre {
215 | display: block;
216 | position: relative;
217 | width: 90%;
218 | margin: var(--r-block-margin) auto;
219 | text-align: left;
220 | font-size: 0.55em;
221 | font-family: var(--r-code-font);
222 | line-height: 1.2em;
223 | word-wrap: break-word;
224 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
225 | }
226 |
227 | .reveal code {
228 | font-family: var(--r-code-font);
229 | text-transform: none;
230 | tab-size: 2;
231 | }
232 |
233 | .reveal pre code {
234 | display: block;
235 | padding: 5px;
236 | overflow: auto;
237 | max-height: 400px;
238 | word-wrap: normal;
239 | }
240 |
241 | .reveal .code-wrapper {
242 | white-space: normal;
243 | }
244 |
245 | .reveal .code-wrapper code {
246 | white-space: pre;
247 | }
248 |
249 | .reveal table {
250 | margin: auto;
251 | border-collapse: collapse;
252 | border-spacing: 0;
253 | }
254 |
255 | .reveal table th {
256 | font-weight: bold;
257 | }
258 |
259 | .reveal table th,
260 | .reveal table td {
261 | text-align: left;
262 | padding: 0.2em 0.5em 0.2em 0.5em;
263 | border-bottom: 1px solid;
264 | }
265 |
266 | .reveal table th[align=center],
267 | .reveal table td[align=center] {
268 | text-align: center;
269 | }
270 |
271 | .reveal table th[align=right],
272 | .reveal table td[align=right] {
273 | text-align: right;
274 | }
275 |
276 | .reveal table tbody tr:last-child th,
277 | .reveal table tbody tr:last-child td {
278 | border-bottom: none;
279 | }
280 |
281 | .reveal sup {
282 | vertical-align: super;
283 | font-size: smaller;
284 | }
285 |
286 | .reveal sub {
287 | vertical-align: sub;
288 | font-size: smaller;
289 | }
290 |
291 | .reveal small {
292 | display: inline-block;
293 | font-size: 0.6em;
294 | line-height: 1.2em;
295 | vertical-align: top;
296 | }
297 |
298 | .reveal small * {
299 | vertical-align: top;
300 | }
301 |
302 | .reveal img {
303 | margin: var(--r-block-margin) 0;
304 | }
305 |
306 | /*********************************************
307 | * LINKS
308 | *********************************************/
309 | .reveal a {
310 | color: var(--r-link-color);
311 | text-decoration: none;
312 | transition: color 0.15s ease;
313 | }
314 |
315 | .reveal a:hover {
316 | color: var(--r-link-color-hover);
317 | text-shadow: none;
318 | border: none;
319 | }
320 |
321 | .reveal .roll span:after {
322 | color: #fff;
323 | background: var(--r-link-color-dark);
324 | }
325 |
326 | /*********************************************
327 | * Frame helper
328 | *********************************************/
329 | .reveal .r-frame {
330 | border: 4px solid var(--r-main-color);
331 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
332 | }
333 |
334 | .reveal a .r-frame {
335 | transition: all 0.15s linear;
336 | }
337 |
338 | .reveal a:hover .r-frame {
339 | border-color: var(--r-link-color);
340 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
341 | }
342 |
343 | /*********************************************
344 | * NAVIGATION CONTROLS
345 | *********************************************/
346 | .reveal .controls {
347 | color: var(--r-link-color);
348 | }
349 |
350 | /*********************************************
351 | * PROGRESS BAR
352 | *********************************************/
353 | .reveal .progress {
354 | background: rgba(0, 0, 0, 0.2);
355 | color: var(--r-link-color);
356 | }
357 |
358 | /*********************************************
359 | * PRINT BACKGROUND
360 | *********************************************/
361 | @media print {
362 | .backgrounds {
363 | background-color: var(--r-background-color);
364 | }
365 | }
--------------------------------------------------------------------------------
/reveal/theme/night.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Black theme for reveal.js.
3 | *
4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
5 | */
6 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700);
7 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic);
8 | section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
9 | color: #222;
10 | }
11 |
12 | /*********************************************
13 | * GLOBAL STYLES
14 | *********************************************/
15 | :root {
16 | --r-background-color: #111;
17 | --r-main-font: Open Sans, sans-serif;
18 | --r-main-font-size: 40px;
19 | --r-main-color: #eee;
20 | --r-block-margin: 20px;
21 | --r-heading-margin: 0 0 20px 0;
22 | --r-heading-font: Montserrat, Impact, sans-serif;
23 | --r-heading-color: #eee;
24 | --r-heading-line-height: 1.2;
25 | --r-heading-letter-spacing: -0.03em;
26 | --r-heading-text-transform: none;
27 | --r-heading-text-shadow: none;
28 | --r-heading-font-weight: normal;
29 | --r-heading1-text-shadow: none;
30 | --r-heading1-size: 3.77em;
31 | --r-heading2-size: 2.11em;
32 | --r-heading3-size: 1.55em;
33 | --r-heading4-size: 1em;
34 | --r-code-font: monospace;
35 | --r-link-color: #e7ad52;
36 | --r-link-color-dark: #d08a1d;
37 | --r-link-color-hover: #f3d7ac;
38 | --r-selection-background-color: #e7ad52;
39 | --r-selection-color: #fff;
40 | }
41 |
42 | .reveal-viewport {
43 | background: #111;
44 | background-color: var(--r-background-color);
45 | }
46 |
47 | .reveal {
48 | font-family: var(--r-main-font);
49 | font-size: var(--r-main-font-size);
50 | font-weight: normal;
51 | color: var(--r-main-color);
52 | }
53 |
54 | .reveal ::selection {
55 | color: var(--r-selection-color);
56 | background: var(--r-selection-background-color);
57 | text-shadow: none;
58 | }
59 |
60 | .reveal ::-moz-selection {
61 | color: var(--r-selection-color);
62 | background: var(--r-selection-background-color);
63 | text-shadow: none;
64 | }
65 |
66 | .reveal .slides section,
67 | .reveal .slides section > section {
68 | line-height: 1.3;
69 | font-weight: inherit;
70 | }
71 |
72 | /*********************************************
73 | * HEADERS
74 | *********************************************/
75 | .reveal h1,
76 | .reveal h2,
77 | .reveal h3,
78 | .reveal h4,
79 | .reveal h5,
80 | .reveal h6 {
81 | margin: var(--r-heading-margin);
82 | color: var(--r-heading-color);
83 | font-family: var(--r-heading-font);
84 | font-weight: var(--r-heading-font-weight);
85 | line-height: var(--r-heading-line-height);
86 | letter-spacing: var(--r-heading-letter-spacing);
87 | text-transform: var(--r-heading-text-transform);
88 | text-shadow: var(--r-heading-text-shadow);
89 | word-wrap: break-word;
90 | }
91 |
92 | .reveal h1 {
93 | font-size: var(--r-heading1-size);
94 | }
95 |
96 | .reveal h2 {
97 | font-size: var(--r-heading2-size);
98 | }
99 |
100 | .reveal h3 {
101 | font-size: var(--r-heading3-size);
102 | }
103 |
104 | .reveal h4 {
105 | font-size: var(--r-heading4-size);
106 | }
107 |
108 | .reveal h1 {
109 | text-shadow: var(--r-heading1-text-shadow);
110 | }
111 |
112 | /*********************************************
113 | * OTHER
114 | *********************************************/
115 | .reveal p {
116 | margin: var(--r-block-margin) 0;
117 | line-height: 1.3;
118 | }
119 |
120 | /* Remove trailing margins after titles */
121 | .reveal h1:last-child,
122 | .reveal h2:last-child,
123 | .reveal h3:last-child,
124 | .reveal h4:last-child,
125 | .reveal h5:last-child,
126 | .reveal h6:last-child {
127 | margin-bottom: 0;
128 | }
129 |
130 | /* Ensure certain elements are never larger than the slide itself */
131 | .reveal img,
132 | .reveal video,
133 | .reveal iframe {
134 | max-width: 95%;
135 | max-height: 95%;
136 | }
137 |
138 | .reveal strong,
139 | .reveal b {
140 | font-weight: bold;
141 | }
142 |
143 | .reveal em {
144 | font-style: italic;
145 | }
146 |
147 | .reveal ol,
148 | .reveal dl,
149 | .reveal ul {
150 | display: inline-block;
151 | text-align: left;
152 | margin: 0 0 0 1em;
153 | }
154 |
155 | .reveal ol {
156 | list-style-type: decimal;
157 | }
158 |
159 | .reveal ul {
160 | list-style-type: disc;
161 | }
162 |
163 | .reveal ul ul {
164 | list-style-type: square;
165 | }
166 |
167 | .reveal ul ul ul {
168 | list-style-type: circle;
169 | }
170 |
171 | .reveal ul ul,
172 | .reveal ul ol,
173 | .reveal ol ol,
174 | .reveal ol ul {
175 | display: block;
176 | margin-left: 40px;
177 | }
178 |
179 | .reveal dt {
180 | font-weight: bold;
181 | }
182 |
183 | .reveal dd {
184 | margin-left: 40px;
185 | }
186 |
187 | .reveal blockquote {
188 | display: block;
189 | position: relative;
190 | width: 70%;
191 | margin: var(--r-block-margin) auto;
192 | padding: 5px;
193 | font-style: italic;
194 | background: rgba(255, 255, 255, 0.05);
195 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
196 | }
197 |
198 | .reveal blockquote p:first-child,
199 | .reveal blockquote p:last-child {
200 | display: inline-block;
201 | }
202 |
203 | .reveal q {
204 | font-style: italic;
205 | }
206 |
207 | .reveal pre {
208 | display: block;
209 | position: relative;
210 | width: 90%;
211 | margin: var(--r-block-margin) auto;
212 | text-align: left;
213 | font-size: 0.55em;
214 | font-family: var(--r-code-font);
215 | line-height: 1.2em;
216 | word-wrap: break-word;
217 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
218 | }
219 |
220 | .reveal code {
221 | font-family: var(--r-code-font);
222 | text-transform: none;
223 | tab-size: 2;
224 | }
225 |
226 | .reveal pre code {
227 | display: block;
228 | padding: 5px;
229 | overflow: auto;
230 | max-height: 400px;
231 | word-wrap: normal;
232 | }
233 |
234 | .reveal .code-wrapper {
235 | white-space: normal;
236 | }
237 |
238 | .reveal .code-wrapper code {
239 | white-space: pre;
240 | }
241 |
242 | .reveal table {
243 | margin: auto;
244 | border-collapse: collapse;
245 | border-spacing: 0;
246 | }
247 |
248 | .reveal table th {
249 | font-weight: bold;
250 | }
251 |
252 | .reveal table th,
253 | .reveal table td {
254 | text-align: left;
255 | padding: 0.2em 0.5em 0.2em 0.5em;
256 | border-bottom: 1px solid;
257 | }
258 |
259 | .reveal table th[align=center],
260 | .reveal table td[align=center] {
261 | text-align: center;
262 | }
263 |
264 | .reveal table th[align=right],
265 | .reveal table td[align=right] {
266 | text-align: right;
267 | }
268 |
269 | .reveal table tbody tr:last-child th,
270 | .reveal table tbody tr:last-child td {
271 | border-bottom: none;
272 | }
273 |
274 | .reveal sup {
275 | vertical-align: super;
276 | font-size: smaller;
277 | }
278 |
279 | .reveal sub {
280 | vertical-align: sub;
281 | font-size: smaller;
282 | }
283 |
284 | .reveal small {
285 | display: inline-block;
286 | font-size: 0.6em;
287 | line-height: 1.2em;
288 | vertical-align: top;
289 | }
290 |
291 | .reveal small * {
292 | vertical-align: top;
293 | }
294 |
295 | .reveal img {
296 | margin: var(--r-block-margin) 0;
297 | }
298 |
299 | /*********************************************
300 | * LINKS
301 | *********************************************/
302 | .reveal a {
303 | color: var(--r-link-color);
304 | text-decoration: none;
305 | transition: color 0.15s ease;
306 | }
307 |
308 | .reveal a:hover {
309 | color: var(--r-link-color-hover);
310 | text-shadow: none;
311 | border: none;
312 | }
313 |
314 | .reveal .roll span:after {
315 | color: #fff;
316 | background: var(--r-link-color-dark);
317 | }
318 |
319 | /*********************************************
320 | * Frame helper
321 | *********************************************/
322 | .reveal .r-frame {
323 | border: 4px solid var(--r-main-color);
324 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
325 | }
326 |
327 | .reveal a .r-frame {
328 | transition: all 0.15s linear;
329 | }
330 |
331 | .reveal a:hover .r-frame {
332 | border-color: var(--r-link-color);
333 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
334 | }
335 |
336 | /*********************************************
337 | * NAVIGATION CONTROLS
338 | *********************************************/
339 | .reveal .controls {
340 | color: var(--r-link-color);
341 | }
342 |
343 | /*********************************************
344 | * PROGRESS BAR
345 | *********************************************/
346 | .reveal .progress {
347 | background: rgba(0, 0, 0, 0.2);
348 | color: var(--r-link-color);
349 | }
350 |
351 | /*********************************************
352 | * PRINT BACKGROUND
353 | *********************************************/
354 | @media print {
355 | .backgrounds {
356 | background-color: var(--r-background-color);
357 | }
358 | }
--------------------------------------------------------------------------------
/reveal/theme/serif.css:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple theme for reveal.js presentations, similar
3 | * to the default theme. The accent color is brown.
4 | *
5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed.
6 | */
7 | .reveal a {
8 | line-height: 1.3em;
9 | }
10 |
11 | section.has-dark-background, section.has-dark-background h1, section.has-dark-background h2, section.has-dark-background h3, section.has-dark-background h4, section.has-dark-background h5, section.has-dark-background h6 {
12 | color: #fff;
13 | }
14 |
15 | /*********************************************
16 | * GLOBAL STYLES
17 | *********************************************/
18 | :root {
19 | --r-background-color: #F0F1EB;
20 | --r-main-font: Palatino Linotype, Book Antiqua, Palatino, FreeSerif, serif;
21 | --r-main-font-size: 40px;
22 | --r-main-color: #000;
23 | --r-block-margin: 20px;
24 | --r-heading-margin: 0 0 20px 0;
25 | --r-heading-font: Palatino Linotype, Book Antiqua, Palatino, FreeSerif, serif;
26 | --r-heading-color: #383D3D;
27 | --r-heading-line-height: 1.2;
28 | --r-heading-letter-spacing: normal;
29 | --r-heading-text-transform: none;
30 | --r-heading-text-shadow: none;
31 | --r-heading-font-weight: normal;
32 | --r-heading1-text-shadow: none;
33 | --r-heading1-size: 3.77em;
34 | --r-heading2-size: 2.11em;
35 | --r-heading3-size: 1.55em;
36 | --r-heading4-size: 1em;
37 | --r-code-font: monospace;
38 | --r-link-color: #51483D;
39 | --r-link-color-dark: #25211c;
40 | --r-link-color-hover: #8b7c69;
41 | --r-selection-background-color: #26351C;
42 | --r-selection-color: #fff;
43 | }
44 |
45 | .reveal-viewport {
46 | background: #F0F1EB;
47 | background-color: var(--r-background-color);
48 | }
49 |
50 | .reveal {
51 | font-family: var(--r-main-font);
52 | font-size: var(--r-main-font-size);
53 | font-weight: normal;
54 | color: var(--r-main-color);
55 | }
56 |
57 | .reveal ::selection {
58 | color: var(--r-selection-color);
59 | background: var(--r-selection-background-color);
60 | text-shadow: none;
61 | }
62 |
63 | .reveal ::-moz-selection {
64 | color: var(--r-selection-color);
65 | background: var(--r-selection-background-color);
66 | text-shadow: none;
67 | }
68 |
69 | .reveal .slides section,
70 | .reveal .slides section > section {
71 | line-height: 1.3;
72 | font-weight: inherit;
73 | }
74 |
75 | /*********************************************
76 | * HEADERS
77 | *********************************************/
78 | .reveal h1,
79 | .reveal h2,
80 | .reveal h3,
81 | .reveal h4,
82 | .reveal h5,
83 | .reveal h6 {
84 | margin: var(--r-heading-margin);
85 | color: var(--r-heading-color);
86 | font-family: var(--r-heading-font);
87 | font-weight: var(--r-heading-font-weight);
88 | line-height: var(--r-heading-line-height);
89 | letter-spacing: var(--r-heading-letter-spacing);
90 | text-transform: var(--r-heading-text-transform);
91 | text-shadow: var(--r-heading-text-shadow);
92 | word-wrap: break-word;
93 | }
94 |
95 | .reveal h1 {
96 | font-size: var(--r-heading1-size);
97 | }
98 |
99 | .reveal h2 {
100 | font-size: var(--r-heading2-size);
101 | }
102 |
103 | .reveal h3 {
104 | font-size: var(--r-heading3-size);
105 | }
106 |
107 | .reveal h4 {
108 | font-size: var(--r-heading4-size);
109 | }
110 |
111 | .reveal h1 {
112 | text-shadow: var(--r-heading1-text-shadow);
113 | }
114 |
115 | /*********************************************
116 | * OTHER
117 | *********************************************/
118 | .reveal p {
119 | margin: var(--r-block-margin) 0;
120 | line-height: 1.3;
121 | }
122 |
123 | /* Remove trailing margins after titles */
124 | .reveal h1:last-child,
125 | .reveal h2:last-child,
126 | .reveal h3:last-child,
127 | .reveal h4:last-child,
128 | .reveal h5:last-child,
129 | .reveal h6:last-child {
130 | margin-bottom: 0;
131 | }
132 |
133 | /* Ensure certain elements are never larger than the slide itself */
134 | .reveal img,
135 | .reveal video,
136 | .reveal iframe {
137 | max-width: 95%;
138 | max-height: 95%;
139 | }
140 |
141 | .reveal strong,
142 | .reveal b {
143 | font-weight: bold;
144 | }
145 |
146 | .reveal em {
147 | font-style: italic;
148 | }
149 |
150 | .reveal ol,
151 | .reveal dl,
152 | .reveal ul {
153 | display: inline-block;
154 | text-align: left;
155 | margin: 0 0 0 1em;
156 | }
157 |
158 | .reveal ol {
159 | list-style-type: decimal;
160 | }
161 |
162 | .reveal ul {
163 | list-style-type: disc;
164 | }
165 |
166 | .reveal ul ul {
167 | list-style-type: square;
168 | }
169 |
170 | .reveal ul ul ul {
171 | list-style-type: circle;
172 | }
173 |
174 | .reveal ul ul,
175 | .reveal ul ol,
176 | .reveal ol ol,
177 | .reveal ol ul {
178 | display: block;
179 | margin-left: 40px;
180 | }
181 |
182 | .reveal dt {
183 | font-weight: bold;
184 | }
185 |
186 | .reveal dd {
187 | margin-left: 40px;
188 | }
189 |
190 | .reveal blockquote {
191 | display: block;
192 | position: relative;
193 | width: 70%;
194 | margin: var(--r-block-margin) auto;
195 | padding: 5px;
196 | font-style: italic;
197 | background: rgba(255, 255, 255, 0.05);
198 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
199 | }
200 |
201 | .reveal blockquote p:first-child,
202 | .reveal blockquote p:last-child {
203 | display: inline-block;
204 | }
205 |
206 | .reveal q {
207 | font-style: italic;
208 | }
209 |
210 | .reveal pre {
211 | display: block;
212 | position: relative;
213 | width: 90%;
214 | margin: var(--r-block-margin) auto;
215 | text-align: left;
216 | font-size: 0.55em;
217 | font-family: var(--r-code-font);
218 | line-height: 1.2em;
219 | word-wrap: break-word;
220 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
221 | }
222 |
223 | .reveal code {
224 | font-family: var(--r-code-font);
225 | text-transform: none;
226 | tab-size: 2;
227 | }
228 |
229 | .reveal pre code {
230 | display: block;
231 | padding: 5px;
232 | overflow: auto;
233 | max-height: 400px;
234 | word-wrap: normal;
235 | }
236 |
237 | .reveal .code-wrapper {
238 | white-space: normal;
239 | }
240 |
241 | .reveal .code-wrapper code {
242 | white-space: pre;
243 | }
244 |
245 | .reveal table {
246 | margin: auto;
247 | border-collapse: collapse;
248 | border-spacing: 0;
249 | }
250 |
251 | .reveal table th {
252 | font-weight: bold;
253 | }
254 |
255 | .reveal table th,
256 | .reveal table td {
257 | text-align: left;
258 | padding: 0.2em 0.5em 0.2em 0.5em;
259 | border-bottom: 1px solid;
260 | }
261 |
262 | .reveal table th[align=center],
263 | .reveal table td[align=center] {
264 | text-align: center;
265 | }
266 |
267 | .reveal table th[align=right],
268 | .reveal table td[align=right] {
269 | text-align: right;
270 | }
271 |
272 | .reveal table tbody tr:last-child th,
273 | .reveal table tbody tr:last-child td {
274 | border-bottom: none;
275 | }
276 |
277 | .reveal sup {
278 | vertical-align: super;
279 | font-size: smaller;
280 | }
281 |
282 | .reveal sub {
283 | vertical-align: sub;
284 | font-size: smaller;
285 | }
286 |
287 | .reveal small {
288 | display: inline-block;
289 | font-size: 0.6em;
290 | line-height: 1.2em;
291 | vertical-align: top;
292 | }
293 |
294 | .reveal small * {
295 | vertical-align: top;
296 | }
297 |
298 | .reveal img {
299 | margin: var(--r-block-margin) 0;
300 | }
301 |
302 | /*********************************************
303 | * LINKS
304 | *********************************************/
305 | .reveal a {
306 | color: var(--r-link-color);
307 | text-decoration: none;
308 | transition: color 0.15s ease;
309 | }
310 |
311 | .reveal a:hover {
312 | color: var(--r-link-color-hover);
313 | text-shadow: none;
314 | border: none;
315 | }
316 |
317 | .reveal .roll span:after {
318 | color: #fff;
319 | background: var(--r-link-color-dark);
320 | }
321 |
322 | /*********************************************
323 | * Frame helper
324 | *********************************************/
325 | .reveal .r-frame {
326 | border: 4px solid var(--r-main-color);
327 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
328 | }
329 |
330 | .reveal a .r-frame {
331 | transition: all 0.15s linear;
332 | }
333 |
334 | .reveal a:hover .r-frame {
335 | border-color: var(--r-link-color);
336 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
337 | }
338 |
339 | /*********************************************
340 | * NAVIGATION CONTROLS
341 | *********************************************/
342 | .reveal .controls {
343 | color: var(--r-link-color);
344 | }
345 |
346 | /*********************************************
347 | * PROGRESS BAR
348 | *********************************************/
349 | .reveal .progress {
350 | background: rgba(0, 0, 0, 0.2);
351 | color: var(--r-link-color);
352 | }
353 |
354 | /*********************************************
355 | * PRINT BACKGROUND
356 | *********************************************/
357 | @media print {
358 | .backgrounds {
359 | background-color: var(--r-background-color);
360 | }
361 | }
--------------------------------------------------------------------------------
/reveal/theme/solarized.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Solarized Light theme for reveal.js.
3 | * Author: Achim Staebler
4 | */
5 | @import url(./fonts/league-gothic/league-gothic.css);
6 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
7 | /**
8 | * Solarized colors by Ethan Schoonover
9 | */
10 | html * {
11 | color-profile: sRGB;
12 | rendering-intent: auto;
13 | }
14 |
15 | /*********************************************
16 | * GLOBAL STYLES
17 | *********************************************/
18 | :root {
19 | --r-background-color: #fdf6e3;
20 | --r-main-font: Lato, sans-serif;
21 | --r-main-font-size: 40px;
22 | --r-main-color: #657b83;
23 | --r-block-margin: 20px;
24 | --r-heading-margin: 0 0 20px 0;
25 | --r-heading-font: League Gothic, Impact, sans-serif;
26 | --r-heading-color: #586e75;
27 | --r-heading-line-height: 1.2;
28 | --r-heading-letter-spacing: normal;
29 | --r-heading-text-transform: uppercase;
30 | --r-heading-text-shadow: none;
31 | --r-heading-font-weight: normal;
32 | --r-heading1-text-shadow: none;
33 | --r-heading1-size: 3.77em;
34 | --r-heading2-size: 2.11em;
35 | --r-heading3-size: 1.55em;
36 | --r-heading4-size: 1em;
37 | --r-code-font: monospace;
38 | --r-link-color: #268bd2;
39 | --r-link-color-dark: #1a6091;
40 | --r-link-color-hover: #78b9e6;
41 | --r-selection-background-color: #d33682;
42 | --r-selection-color: #fff;
43 | }
44 |
45 | .reveal-viewport {
46 | background: #fdf6e3;
47 | background-color: var(--r-background-color);
48 | }
49 |
50 | .reveal {
51 | font-family: var(--r-main-font);
52 | font-size: var(--r-main-font-size);
53 | font-weight: normal;
54 | color: var(--r-main-color);
55 | }
56 |
57 | .reveal ::selection {
58 | color: var(--r-selection-color);
59 | background: var(--r-selection-background-color);
60 | text-shadow: none;
61 | }
62 |
63 | .reveal ::-moz-selection {
64 | color: var(--r-selection-color);
65 | background: var(--r-selection-background-color);
66 | text-shadow: none;
67 | }
68 |
69 | .reveal .slides section,
70 | .reveal .slides section > section {
71 | line-height: 1.3;
72 | font-weight: inherit;
73 | }
74 |
75 | /*********************************************
76 | * HEADERS
77 | *********************************************/
78 | .reveal h1,
79 | .reveal h2,
80 | .reveal h3,
81 | .reveal h4,
82 | .reveal h5,
83 | .reveal h6 {
84 | margin: var(--r-heading-margin);
85 | color: var(--r-heading-color);
86 | font-family: var(--r-heading-font);
87 | font-weight: var(--r-heading-font-weight);
88 | line-height: var(--r-heading-line-height);
89 | letter-spacing: var(--r-heading-letter-spacing);
90 | text-transform: var(--r-heading-text-transform);
91 | text-shadow: var(--r-heading-text-shadow);
92 | word-wrap: break-word;
93 | }
94 |
95 | .reveal h1 {
96 | font-size: var(--r-heading1-size);
97 | }
98 |
99 | .reveal h2 {
100 | font-size: var(--r-heading2-size);
101 | }
102 |
103 | .reveal h3 {
104 | font-size: var(--r-heading3-size);
105 | }
106 |
107 | .reveal h4 {
108 | font-size: var(--r-heading4-size);
109 | }
110 |
111 | .reveal h1 {
112 | text-shadow: var(--r-heading1-text-shadow);
113 | }
114 |
115 | /*********************************************
116 | * OTHER
117 | *********************************************/
118 | .reveal p {
119 | margin: var(--r-block-margin) 0;
120 | line-height: 1.3;
121 | }
122 |
123 | /* Remove trailing margins after titles */
124 | .reveal h1:last-child,
125 | .reveal h2:last-child,
126 | .reveal h3:last-child,
127 | .reveal h4:last-child,
128 | .reveal h5:last-child,
129 | .reveal h6:last-child {
130 | margin-bottom: 0;
131 | }
132 |
133 | /* Ensure certain elements are never larger than the slide itself */
134 | .reveal img,
135 | .reveal video,
136 | .reveal iframe {
137 | max-width: 95%;
138 | max-height: 95%;
139 | }
140 |
141 | .reveal strong,
142 | .reveal b {
143 | font-weight: bold;
144 | }
145 |
146 | .reveal em {
147 | font-style: italic;
148 | }
149 |
150 | .reveal ol,
151 | .reveal dl,
152 | .reveal ul {
153 | display: inline-block;
154 | text-align: left;
155 | margin: 0 0 0 1em;
156 | }
157 |
158 | .reveal ol {
159 | list-style-type: decimal;
160 | }
161 |
162 | .reveal ul {
163 | list-style-type: disc;
164 | }
165 |
166 | .reveal ul ul {
167 | list-style-type: square;
168 | }
169 |
170 | .reveal ul ul ul {
171 | list-style-type: circle;
172 | }
173 |
174 | .reveal ul ul,
175 | .reveal ul ol,
176 | .reveal ol ol,
177 | .reveal ol ul {
178 | display: block;
179 | margin-left: 40px;
180 | }
181 |
182 | .reveal dt {
183 | font-weight: bold;
184 | }
185 |
186 | .reveal dd {
187 | margin-left: 40px;
188 | }
189 |
190 | .reveal blockquote {
191 | display: block;
192 | position: relative;
193 | width: 70%;
194 | margin: var(--r-block-margin) auto;
195 | padding: 5px;
196 | font-style: italic;
197 | background: rgba(255, 255, 255, 0.05);
198 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
199 | }
200 |
201 | .reveal blockquote p:first-child,
202 | .reveal blockquote p:last-child {
203 | display: inline-block;
204 | }
205 |
206 | .reveal q {
207 | font-style: italic;
208 | }
209 |
210 | .reveal pre {
211 | display: block;
212 | position: relative;
213 | width: 90%;
214 | margin: var(--r-block-margin) auto;
215 | text-align: left;
216 | font-size: 0.55em;
217 | font-family: var(--r-code-font);
218 | line-height: 1.2em;
219 | word-wrap: break-word;
220 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
221 | }
222 |
223 | .reveal code {
224 | font-family: var(--r-code-font);
225 | text-transform: none;
226 | tab-size: 2;
227 | }
228 |
229 | .reveal pre code {
230 | display: block;
231 | padding: 5px;
232 | overflow: auto;
233 | max-height: 400px;
234 | word-wrap: normal;
235 | }
236 |
237 | .reveal .code-wrapper {
238 | white-space: normal;
239 | }
240 |
241 | .reveal .code-wrapper code {
242 | white-space: pre;
243 | }
244 |
245 | .reveal table {
246 | margin: auto;
247 | border-collapse: collapse;
248 | border-spacing: 0;
249 | }
250 |
251 | .reveal table th {
252 | font-weight: bold;
253 | }
254 |
255 | .reveal table th,
256 | .reveal table td {
257 | text-align: left;
258 | padding: 0.2em 0.5em 0.2em 0.5em;
259 | border-bottom: 1px solid;
260 | }
261 |
262 | .reveal table th[align=center],
263 | .reveal table td[align=center] {
264 | text-align: center;
265 | }
266 |
267 | .reveal table th[align=right],
268 | .reveal table td[align=right] {
269 | text-align: right;
270 | }
271 |
272 | .reveal table tbody tr:last-child th,
273 | .reveal table tbody tr:last-child td {
274 | border-bottom: none;
275 | }
276 |
277 | .reveal sup {
278 | vertical-align: super;
279 | font-size: smaller;
280 | }
281 |
282 | .reveal sub {
283 | vertical-align: sub;
284 | font-size: smaller;
285 | }
286 |
287 | .reveal small {
288 | display: inline-block;
289 | font-size: 0.6em;
290 | line-height: 1.2em;
291 | vertical-align: top;
292 | }
293 |
294 | .reveal small * {
295 | vertical-align: top;
296 | }
297 |
298 | .reveal img {
299 | margin: var(--r-block-margin) 0;
300 | }
301 |
302 | /*********************************************
303 | * LINKS
304 | *********************************************/
305 | .reveal a {
306 | color: var(--r-link-color);
307 | text-decoration: none;
308 | transition: color 0.15s ease;
309 | }
310 |
311 | .reveal a:hover {
312 | color: var(--r-link-color-hover);
313 | text-shadow: none;
314 | border: none;
315 | }
316 |
317 | .reveal .roll span:after {
318 | color: #fff;
319 | background: var(--r-link-color-dark);
320 | }
321 |
322 | /*********************************************
323 | * Frame helper
324 | *********************************************/
325 | .reveal .r-frame {
326 | border: 4px solid var(--r-main-color);
327 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
328 | }
329 |
330 | .reveal a .r-frame {
331 | transition: all 0.15s linear;
332 | }
333 |
334 | .reveal a:hover .r-frame {
335 | border-color: var(--r-link-color);
336 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
337 | }
338 |
339 | /*********************************************
340 | * NAVIGATION CONTROLS
341 | *********************************************/
342 | .reveal .controls {
343 | color: var(--r-link-color);
344 | }
345 |
346 | /*********************************************
347 | * PROGRESS BAR
348 | *********************************************/
349 | .reveal .progress {
350 | background: rgba(0, 0, 0, 0.2);
351 | color: var(--r-link-color);
352 | }
353 |
354 | /*********************************************
355 | * PRINT BACKGROUND
356 | *********************************************/
357 | @media print {
358 | .backgrounds {
359 | background-color: var(--r-background-color);
360 | }
361 | }
--------------------------------------------------------------------------------
/reveal/theme/white.css:
--------------------------------------------------------------------------------
1 | /**
2 | * White theme for reveal.js. This is the opposite of the 'black' theme.
3 | *
4 | * By Hakim El Hattab, http://hakim.se
5 | */
6 | @import url(./fonts/source-sans-pro/source-sans-pro.css);
7 | section.has-dark-background, section.has-dark-background h1, section.has-dark-background h2, section.has-dark-background h3, section.has-dark-background h4, section.has-dark-background h5, section.has-dark-background h6 {
8 | color: #fff;
9 | }
10 |
11 | /*********************************************
12 | * GLOBAL STYLES
13 | *********************************************/
14 | :root {
15 | --r-background-color: #fff;
16 | --r-main-font: Source Sans Pro, Helvetica, sans-serif;
17 | --r-main-font-size: 42px;
18 | --r-main-color: #222;
19 | --r-block-margin: 20px;
20 | --r-heading-margin: 0 0 20px 0;
21 | --r-heading-font: Source Sans Pro, Helvetica, sans-serif;
22 | --r-heading-color: #222;
23 | --r-heading-line-height: 1.2;
24 | --r-heading-letter-spacing: normal;
25 | --r-heading-text-transform: uppercase;
26 | --r-heading-text-shadow: none;
27 | --r-heading-font-weight: 600;
28 | --r-heading1-text-shadow: none;
29 | --r-heading1-size: 2.5em;
30 | --r-heading2-size: 1.6em;
31 | --r-heading3-size: 1.3em;
32 | --r-heading4-size: 1em;
33 | --r-code-font: monospace;
34 | --r-link-color: #2a76dd;
35 | --r-link-color-dark: #1a53a1;
36 | --r-link-color-hover: #6ca0e8;
37 | --r-selection-background-color: #98bdef;
38 | --r-selection-color: #fff;
39 | }
40 |
41 | .reveal-viewport {
42 | background: #fff;
43 | background-color: var(--r-background-color);
44 | }
45 |
46 | .reveal {
47 | font-family: var(--r-main-font);
48 | font-size: var(--r-main-font-size);
49 | font-weight: normal;
50 | color: var(--r-main-color);
51 | }
52 |
53 | .reveal ::selection {
54 | color: var(--r-selection-color);
55 | background: var(--r-selection-background-color);
56 | text-shadow: none;
57 | }
58 |
59 | .reveal ::-moz-selection {
60 | color: var(--r-selection-color);
61 | background: var(--r-selection-background-color);
62 | text-shadow: none;
63 | }
64 |
65 | .reveal .slides section,
66 | .reveal .slides section > section {
67 | line-height: 1.3;
68 | font-weight: inherit;
69 | }
70 |
71 | /*********************************************
72 | * HEADERS
73 | *********************************************/
74 | .reveal h1,
75 | .reveal h2,
76 | .reveal h3,
77 | .reveal h4,
78 | .reveal h5,
79 | .reveal h6 {
80 | margin: var(--r-heading-margin);
81 | color: var(--r-heading-color);
82 | font-family: var(--r-heading-font);
83 | font-weight: var(--r-heading-font-weight);
84 | line-height: var(--r-heading-line-height);
85 | letter-spacing: var(--r-heading-letter-spacing);
86 | text-transform: var(--r-heading-text-transform);
87 | text-shadow: var(--r-heading-text-shadow);
88 | word-wrap: break-word;
89 | }
90 |
91 | .reveal h1 {
92 | font-size: var(--r-heading1-size);
93 | }
94 |
95 | .reveal h2 {
96 | font-size: var(--r-heading2-size);
97 | }
98 |
99 | .reveal h3 {
100 | font-size: var(--r-heading3-size);
101 | }
102 |
103 | .reveal h4 {
104 | font-size: var(--r-heading4-size);
105 | }
106 |
107 | .reveal h1 {
108 | text-shadow: var(--r-heading1-text-shadow);
109 | }
110 |
111 | /*********************************************
112 | * OTHER
113 | *********************************************/
114 | .reveal p {
115 | margin: var(--r-block-margin) 0;
116 | line-height: 1.3;
117 | }
118 |
119 | /* Remove trailing margins after titles */
120 | .reveal h1:last-child,
121 | .reveal h2:last-child,
122 | .reveal h3:last-child,
123 | .reveal h4:last-child,
124 | .reveal h5:last-child,
125 | .reveal h6:last-child {
126 | margin-bottom: 0;
127 | }
128 |
129 | /* Ensure certain elements are never larger than the slide itself */
130 | .reveal img,
131 | .reveal video,
132 | .reveal iframe {
133 | max-width: 95%;
134 | max-height: 95%;
135 | }
136 |
137 | .reveal strong,
138 | .reveal b {
139 | font-weight: bold;
140 | }
141 |
142 | .reveal em {
143 | font-style: italic;
144 | }
145 |
146 | .reveal ol,
147 | .reveal dl,
148 | .reveal ul {
149 | display: inline-block;
150 | text-align: left;
151 | margin: 0 0 0 1em;
152 | }
153 |
154 | .reveal ol {
155 | list-style-type: decimal;
156 | }
157 |
158 | .reveal ul {
159 | list-style-type: disc;
160 | }
161 |
162 | .reveal ul ul {
163 | list-style-type: square;
164 | }
165 |
166 | .reveal ul ul ul {
167 | list-style-type: circle;
168 | }
169 |
170 | .reveal ul ul,
171 | .reveal ul ol,
172 | .reveal ol ol,
173 | .reveal ol ul {
174 | display: block;
175 | margin-left: 40px;
176 | }
177 |
178 | .reveal dt {
179 | font-weight: bold;
180 | }
181 |
182 | .reveal dd {
183 | margin-left: 40px;
184 | }
185 |
186 | .reveal blockquote {
187 | display: block;
188 | position: relative;
189 | width: 70%;
190 | margin: var(--r-block-margin) auto;
191 | padding: 5px;
192 | font-style: italic;
193 | background: rgba(255, 255, 255, 0.05);
194 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
195 | }
196 |
197 | .reveal blockquote p:first-child,
198 | .reveal blockquote p:last-child {
199 | display: inline-block;
200 | }
201 |
202 | .reveal q {
203 | font-style: italic;
204 | }
205 |
206 | .reveal pre {
207 | display: block;
208 | position: relative;
209 | width: 90%;
210 | margin: var(--r-block-margin) auto;
211 | text-align: left;
212 | font-size: 0.55em;
213 | font-family: var(--r-code-font);
214 | line-height: 1.2em;
215 | word-wrap: break-word;
216 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
217 | }
218 |
219 | .reveal code {
220 | font-family: var(--r-code-font);
221 | text-transform: none;
222 | tab-size: 2;
223 | }
224 |
225 | .reveal pre code {
226 | display: block;
227 | padding: 5px;
228 | overflow: auto;
229 | max-height: 400px;
230 | word-wrap: normal;
231 | }
232 |
233 | .reveal .code-wrapper {
234 | white-space: normal;
235 | }
236 |
237 | .reveal .code-wrapper code {
238 | white-space: pre;
239 | }
240 |
241 | .reveal table {
242 | margin: auto;
243 | border-collapse: collapse;
244 | border-spacing: 0;
245 | }
246 |
247 | .reveal table th {
248 | font-weight: bold;
249 | }
250 |
251 | .reveal table th,
252 | .reveal table td {
253 | text-align: left;
254 | padding: 0.2em 0.5em 0.2em 0.5em;
255 | border-bottom: 1px solid;
256 | }
257 |
258 | .reveal table th[align=center],
259 | .reveal table td[align=center] {
260 | text-align: center;
261 | }
262 |
263 | .reveal table th[align=right],
264 | .reveal table td[align=right] {
265 | text-align: right;
266 | }
267 |
268 | .reveal table tbody tr:last-child th,
269 | .reveal table tbody tr:last-child td {
270 | border-bottom: none;
271 | }
272 |
273 | .reveal sup {
274 | vertical-align: super;
275 | font-size: smaller;
276 | }
277 |
278 | .reveal sub {
279 | vertical-align: sub;
280 | font-size: smaller;
281 | }
282 |
283 | .reveal small {
284 | display: inline-block;
285 | font-size: 0.6em;
286 | line-height: 1.2em;
287 | vertical-align: top;
288 | }
289 |
290 | .reveal small * {
291 | vertical-align: top;
292 | }
293 |
294 | .reveal img {
295 | margin: var(--r-block-margin) 0;
296 | }
297 |
298 | /*********************************************
299 | * LINKS
300 | *********************************************/
301 | .reveal a {
302 | color: var(--r-link-color);
303 | text-decoration: none;
304 | transition: color 0.15s ease;
305 | }
306 |
307 | .reveal a:hover {
308 | color: var(--r-link-color-hover);
309 | text-shadow: none;
310 | border: none;
311 | }
312 |
313 | .reveal .roll span:after {
314 | color: #fff;
315 | background: var(--r-link-color-dark);
316 | }
317 |
318 | /*********************************************
319 | * Frame helper
320 | *********************************************/
321 | .reveal .r-frame {
322 | border: 4px solid var(--r-main-color);
323 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
324 | }
325 |
326 | .reveal a .r-frame {
327 | transition: all 0.15s linear;
328 | }
329 |
330 | .reveal a:hover .r-frame {
331 | border-color: var(--r-link-color);
332 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
333 | }
334 |
335 | /*********************************************
336 | * NAVIGATION CONTROLS
337 | *********************************************/
338 | .reveal .controls {
339 | color: var(--r-link-color);
340 | }
341 |
342 | /*********************************************
343 | * PROGRESS BAR
344 | *********************************************/
345 | .reveal .progress {
346 | background: rgba(0, 0, 0, 0.2);
347 | color: var(--r-link-color);
348 | }
349 |
350 | /*********************************************
351 | * PRINT BACKGROUND
352 | *********************************************/
353 | @media print {
354 | .backgrounds {
355 | background-color: var(--r-background-color);
356 | }
357 | }
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebPackPlugin = require('html-webpack-plugin')
2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
3 | const path = require('path')
4 |
5 | module.exports = (env, argv = {}) => ({
6 | entry: {
7 | bundle: './src/index.js',
8 | },
9 | output: {
10 | path: path.resolve(__dirname, 'build'),
11 | filename: '[name]-[fullhash].js',
12 | publicPath: '/',
13 | },
14 | plugins: [
15 | new HtmlWebPackPlugin({
16 | template: './TPs/index.html',
17 | filename: './index.html',
18 | favicon: './public/favicon.ico',
19 | }),
20 | new CleanWebpackPlugin({ verbose: true }),
21 | ],
22 | resolve: {
23 | extensions: ['.js'],
24 | alias: {
25 | public: path.resolve(__dirname, 'public'),
26 | },
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.js$/,
32 | exclude: /node_modules/,
33 | loader: 'babel-loader',
34 | },
35 | ],
36 | },
37 | devtool: argv.mode === 'development' ? 'source-map' : false,
38 | devServer: {
39 | static: path.resolve(__dirname, 'public'),
40 | open: true,
41 | historyApiFallback: true,
42 | port: 3000,
43 | },
44 | })
45 |
--------------------------------------------------------------------------------