├── .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 | ![TPs](https://github.com/jbcazaux/formation-reactjs-es6/actions/workflows/main.yml/badge.svg) 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 | [![Licence Creative Commons](http://i.creativecommons.org/l/by-sa/3.0/88x31.png)](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 |
12 | 13 | Filtrer: 14 | 15 | 23 |
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 |
    33 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 34 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 35 | 36 | 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 |
    33 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 34 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 35 | 36 | 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 |
    11 | 19 | 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 | {name} 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 |
    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 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 |
    18 | 22 | 30 | 31 |
    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 | 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 |
    54 |

    reactFuture of React

    56 |

    @jbcazaux

    57 |

    https://formation-reactjs.fr 58 |

    59 |
    60 |
    61 |

    Versions

    62 |
    
     63 |                     "react": "16.7.0-alpha.2"
     64 |                 
    65 |
    66 |
    67 |

    Mixins ou HOC => poubelle !

    68 |
    69 |
    my name is 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 |
    100 |

    useEffect

    101 |
      102 |
    • componentDidMount
    • 103 |
    • componentDidUpdate
    • 104 |
    • (componentWillUnmount)
    • 105 |
    106 |

    https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1 107 |

    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 |

    help ? jbcazaux@gmail.com

    161 |

    https://formation-reactjs.fr

    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 | 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 | 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 |
    34 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 35 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 36 | 37 |
    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 | 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 |
    39 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 40 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 41 | 42 |
    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 |
    11 | 19 |
    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 |
    40 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 41 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 42 | 43 |
    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 |
    48 | setNewItemLabel(e.target.value)} value={newItemLabel} /> 49 | setNewItemPrice(parseFloat(e.target.value))} value={newItemPrice} /> 50 | 51 |
    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 |
    11 | 19 |
    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 |
    6 |
    10 | 17 |
    18 |
    19 | 22 | 25 | 28 | 34 | 40 | 46 | 47 | 48 | 51 | 55 | 60 | 65 | 70 | 71 | 72 |
    32 | # 33 | 38 | Firstname 39 | 44 | Lastname 45 |
    58 | 1 59 | 63 | first 64 | 68 | last 69 |
    73 | 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 | {name} 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 |
    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 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 | --------------------------------------------------------------------------------