├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── config.js ├── content ├── dynamics.md ├── forwardkinematics.md ├── impedancecontrol.md ├── index.mdx ├── inversekinematics.md ├── jacobian.md ├── robotposes.md ├── singularity.md └── transformation.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── images ├── favicon.png ├── impedance │ ├── bag.png │ ├── bag_mass.png │ ├── stair.png │ ├── swing.png │ └── water.png ├── inverse │ └── geometric.png ├── kinematics │ └── touch.png ├── robot.svg └── robotposes │ ├── car.png │ ├── desert.png │ ├── park.png │ └── pose.png ├── netlify.toml ├── package-lock.json ├── package.json └── src ├── CommunityAuthor.js ├── GithubLink.js ├── YoutubeEmbed.js ├── components ├── DarkModeSwitch.js ├── Header.js ├── NextPrevious.js ├── images │ ├── closed.js │ ├── day.png │ ├── github.svg │ ├── help.svg │ ├── logo.svg │ ├── night.png │ ├── opened.js │ └── test.svg ├── index.js ├── layout.js ├── link.js ├── mdxComponents │ ├── LiveProvider.js │ ├── anchor.js │ ├── codeBlock.js │ ├── index.js │ └── loading.js ├── rightSidebar.js ├── sidebar │ ├── index.js │ ├── tree.js │ └── treeNode.js ├── styles │ ├── Docs.js │ ├── GlobalStyles.js │ ├── PageNavigationButtons.js │ └── Sidebar.js ├── theme.js ├── theme │ ├── index.js │ └── themeProvider.js └── themeProvider.js ├── custom-sw-code.js ├── html.js └── templates └── docs.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .cache 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended", 7 | "prettier", 8 | "prettier/react" 9 | ], 10 | "plugins": ["react", "import", "jsx-a11y"], 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | } 15 | }, 16 | "rules": { 17 | "react/prop-types": 0, 18 | "react/react-in-jsx-scope": "off", 19 | "lines-between-class-members": ["error", "always"], 20 | "padding-line-between-statements": [ 21 | "error", 22 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, 23 | { 24 | "blankLine": "always", 25 | "prev": ["const", "let", "var"], 26 | "next": ["const", "let", "var"] 27 | }, 28 | { "blankLine": "always", "prev": "directive", "next": "*" }, 29 | { "blankLine": "any", "prev": "directive", "next": "directive" } 30 | ] 31 | }, 32 | "parser": "babel-eslint", 33 | "parserOptions": { 34 | "ecmaVersion": 10, 35 | "sourceType": "module", 36 | "ecmaFeatures": { 37 | "jsx": true 38 | } 39 | }, 40 | "env": { 41 | "es6": true, 42 | "browser": true, 43 | "node": true 44 | }, 45 | "globals": { 46 | "graphql": false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | .cache 3 | node_modules 4 | *DS_Store 5 | *.env 6 | 7 | .idea/ 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "jsxBracketSameLine": false, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:buster 2 | 3 | # Create app directory 4 | WORKDIR /app 5 | 6 | # Install app dependencies 7 | # RUN npm -g install serve 8 | RUN npm -g install gatsby-cli 9 | 10 | COPY package*.json ./ 11 | 12 | RUN npm ci 13 | 14 | # Bundle app source 15 | COPY . . 16 | 17 | # Build static files 18 | RUN npm run build 19 | 20 | # serve on port 8080 21 | # CMD ["serve", "-l", "tcp://0.0.0.0:8080", "public"] 22 | CMD ["gatsby", "serve", "--verbose", "--prefix-paths", "-p", "8080", "--host", "0.0.0.0"] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hasura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # robotics-explained 2 | 3 | Robotics explained is a collection of articles about robotics. It uses gatsby. -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | gatsby: { 3 | pathPrefix: '/', 4 | siteUrl: 'https://robotics-explained.com/', 5 | gaTrackingId: null, 6 | trailingSlash: false, 7 | }, 8 | sidebar: { 9 | forcedNavOrder: [ 10 | '/robotposes', // add trailing slash if enabled above 11 | '/transformation', 12 | '/forwardkinematics', 13 | '/inversekinematics', 14 | '/jacobian', 15 | '/singularity', 16 | '/dynamics', 17 | '/impedancecontrol', 18 | ], 19 | // collapsedNav: [ 20 | // '/control', // add trailing slash if enabled above 21 | // ], 22 | links: [{ text: 'Github', link: 'https://github.com/woll-an/robotics-explained' }], 23 | frontline: true, 24 | ignoreIndex: true, 25 | title: '', 26 | }, 27 | siteMetadata: { 28 | title: 'Robotics Explained | Robot Course', 29 | description: 'A course about robotics', 30 | ogImage: null, 31 | docsLocation: '', 32 | favicon: 'src/favicon.png', 33 | }, 34 | }; 35 | 36 | module.exports = config; 37 | -------------------------------------------------------------------------------- /content/dynamics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dynamics" 3 | metaTitle: "Dynamics | Robotics Explained" 4 | metaDescription: "How robots move." 5 | --- 6 | 7 | # Torques and Forces 8 | 9 | Up to now we assumed that we can just command arbitrary velocities to our robot. Unfortunately, this is not possible. Each abrupt change of the velocity leads to high accelerations, accelaration is according to Newtons's first law $F = m \cdot a$ proportional to force, and (too much) force will break the robot. A quick note about force and torque. A force causes linear acceleration of an object, whereas a torque causes angular acceleration. For our rotational joints we are interested in torques and for the end-effector we are (mostly) interested in forces. How do we compute the relationship between end-effector force $f$ and joint torque $\tau$? We use our almighty Jacobian! 10 | 11 | $$\tau = J(q)^T \cdot f$$ 12 | 13 | # Forces acting on the robot 14 | 15 | When the robot moves, there are a lot of forces acting on the robot. One component is the force from the acceleration of the robot. It can be computed by multiplying the robot's mass with its joint acceleration. We specify the robot's mass with the so called mass or inertia matrix $M(q)$. The mass matrix depends on the joint angles $q$ as it makes a difference if the arm is stretched or bent. This might seem unintuitiv, as the actual mass does not change. However, the resistance of a physical object to change its velocity - the inertia - does depend on the mass and also mass distribution of the object. This is the reason why an ice dancer can control the velocity of the rotation in a pirouette by extending their arms. 16 | 17 | Other forces acting on the robot are gravitational forces. They are present all the time, even when the robot is not moving. Hoewever, if you want to operate your robot in space, you can ignore this paragraph. The gravitational forces are dependent on the joint configuration $q$. If you extend your arm you need much more power to hold it up compared to just let it hang at your side. This is the same for the robot. 18 | 19 | The last component are centrifugal and Coriolis forces. These depend on the joint configuration and the velocity. You are probably familiar with the contrifugal force. It is the force which holds the water in the bucket when you swirl it around. It acts radial to the axis of rotation and is proportional to the mass, the distance from the axis and the square of the (angular) velocity. 20 | 21 | The Coriolis force is a bit harder to grasp. Imagine you are travelling in a straight line on top of the earth surface to the north. A visitor in a space ship observes you. The path of your travel does not appear as a straight line to the north for the visitor, even though it does for you. Because of the rotation of the earth, the visitor oberserves an additional eastward motion. The force causing this motion is called Coriolis force. 22 | 23 | Besides the forces listed above, there could also be external forces acting on the robot, for example if it is in contact with a surface or carrying payload. 24 | 25 | 26 | # Inverse Dynamics 27 | 28 | To counteract these forces, torques are applied in the joints. This is similar to engaging our muscles when we bring our body in an upright position. The torque $\tau$ required can be computed by summing the terms for all the forces we listed above. $\tau$ is a vector with one dimension for each joint. We have a term for the mass $M(q) \ddot{q}$, Coriolos and centrifugal terms $C(q,\dot{q})$ and gravitational terms $g(q)$. 29 | 30 | $$ \tau = M(q) \ddot{q} + C(q,\dot{q}) + g(q) - \tau_{ext}$$ 31 | 32 | This formula is called the inverse dynamics, as we can compute the torque required given the joint position, velocity and acceleration. In practice, $M(q)$, $C(q, \dot{q})$ and $g(q)$ are often determined with the help of modelling software. 33 | 34 | In the following interactive demo, you can change the torque acting on each of the joints by using the sliders. Try to bring the robot in a position where it is stretched pointing to the left. You will see that the applied torques will have different effects on the robot depending on the configuration. 35 | 36 | 37 | 38 | # Forwards Dynamics 39 | 40 | When there is an inverse dynamics, there is of course also a forwards dynamics. It allows us to compute the joint acceleration given the torques currently applied. We can derive it by rearranging the equation for the inverse dynamics. 41 | 42 | $$\ddot{q} = M(q)^{-1} (\tau - C(q,\dot{q}) - g(q) + \tau_{ext}) $$ -------------------------------------------------------------------------------- /content/forwardkinematics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Forward Kinematics" 3 | metaTitle: "Forward kinematics | Robotics Explained" 4 | metaDescription: "Forwards kinematics allows us to compute the pose of the robot's end-effector given the configuration of its joints." 5 | --- 6 | 7 | Kinematics is the relationship between the geometry of the robot and its movement in the Cartesian space. We take into account the configuration of the robot and the length of the joints. A configuration describes the state of the robot's parts we have control over. For a robot arm the configuration is the state of the motors - the rotational joints. We can measure how far a motor turned from an initial position. For a robot with three motors we express this with a three dimensional vector q, where each row is the rotation of one of the joints in radiant, e.g. $q = [0.5,0,1]^T$. 8 | 9 | One quick note about the notation: In the literature you often find a distinction between the rotation of the motor - $\theta$ - and the rotation of the joint - $q$. Depending on the construction of the robot they might or might not be identical. I will use mainly the rotation of the joint, but don't hesitate to think about an actual motor rotating, because it might be more intuitive for you. 10 | 11 | # Chaining Transformations 12 | 13 | Back to the main problem: Our goal is to develop a transformation matrix $T(q)_{O,ee}$ which allows us to compute the pose of the end-effector given the configuration of the robot. $T_{O,ee}$ is the transformation from the origin (O) to the end-effector (ee). We know the geometry of the three links and we have sensors which measure the rotation of the three joints. We can express the rotation from each joint plus the translation due to the geometry of the link as a transformation matrix. Additionally, we have the transformation from the origin to the first joint. 14 | 15 | As we learned in the article about [transformation matrices](/transformation) we can chain multiple transformation matrices together and to get the overall transformation from the origin to the tip of the end-effector. For our robot, i.e. 16 | 17 | $$T(q)_{O,ee} = T(q_0)_{O,0} \cdot T(q_1)_{0,1} \cdot T(q_2)_{1,2} \cdot T_{2,ee}$$ 18 | 19 | Each of these transformations are simple rotations and translations depending on the joint angle and the link length. 20 | 21 | 22 | 23 | # Transformations for each joint 24 | 25 | 1. Joint 0: This transforamtion is actually quite simple, as we chose the origin of the workspace to be identical with the position of the first joint. We therefore have no translation. The rotation is determind by the rotation of the first joint, $q_0$. 26 | 27 | $$ 28 | T(q_0)_{O,0} 29 | = \begin{bmatrix} 30 | cos(q_0) & - sin(q_0) & 0 \\ 31 | sin(q_0) & cos(q_0) & 0 \\ 32 | 0 & 0 & 1 33 | \end{bmatrix} 34 | $$ 35 | 36 | 2. Joint 1: Besides the rotation from the joint we have a translation by 150 mm in x-direction. 37 | 38 | $$ 39 | T(q_1)_{0,1} 40 | = \begin{bmatrix} 41 | cos(q_1) & - sin(q_1) & 0.15 \\ 42 | sin(q_1) & cos(q_1) & 0 \\ 43 | 0 & 0 & 1 44 | \end{bmatrix} 45 | $$ 46 | 47 | 3. Joint 2: This is identical to joint 1 48 | 49 | $$ 50 | T(q_2)_{1,2} 51 | = \begin{bmatrix} 52 | cos(q_2) & - sin(q_2) & 0.15 \\ 53 | sin(q_2) & cos(q_2) & 0 \\ 54 | 0 & 0 & 1 55 | \end{bmatrix} 56 | $$ 57 | 58 | 4. End-effector: As the tip of the end-effector does not rotate, we have only a translation by 30 mm in x-direction. 59 | 60 | $$ 61 | T_{2,ee} 62 | = \begin{bmatrix} 63 | 1 & 0 & 0.03 \\ 64 | 0 & 1 & 0 \\ 65 | 0 & 0 & 1 66 | \end{bmatrix} 67 | $$ 68 | 69 | By multiplying all these transformation matrices, we get $T(q)_{O,ee}$, the transformation from the origin to the end-effector. 70 | 71 | $$ 72 | T_{O,ee} 73 | = \begin{bmatrix} 74 | cos(\phi_{O,ee}) & -sin(\phi_{O,ee}) & x_{O,ee} \\ 75 | sin(\phi_{O,ee}) & cos(\phi_{O,ee}) & y_{O,ee} \\ 76 | 0 & 0 & 1 77 | \end{bmatrix} 78 | $$ 79 | 80 | with 81 | 82 | $$\phi_{O,ee} = q_0+q_1+q_2$$ 83 | 84 | $$x_{O,ee} = 0.15 \cdot cos(q_0) + 0.15 \cdot cos(q_0+q_1) + 0.03 \cdot cos(q_0+q_1+q_2)$$ 85 | 86 | $$y_{O,ee} = 0.15 \cdot sin(q_0) + 0.15 \cdot sin(q_0+q_1) + 0.03 \cdot sin(q_0+q_1+q_2)$$ 87 | 88 | The angle of the end-effector with respect to the origin is the sum of the angles of all three joints. You can play around with the interactive demo to validate this finding. The x and y components of the end-effector's position are a bit more complicated, but we can find a certain pattern. For each joint we multiply its length with the cos (or sin for the y-component) of its angle with respect to the origin. 89 | 90 | Given the configuration of the robot, we can now compute the Cartesian coordinates of the tip of the end-effector! This is called *forwards kinematics*. Please take a moment to think about what we just achieved with such a transformation! We are now able to understand our robot when we ask where it is, because it is able to translate its inner configuration into a system which has a meaning for us. 91 | 92 | >You: "Hey, where are you?" 93 | >Robot: "I'm at (0.5, 0.1) with a rotation of 0.75. The origin is the center of my first joint" 94 | >You: "Cool, I will be there in a second" 95 | 96 | ![touch](../images/kinematics/touch.png "Touch") 97 | -------------------------------------------------------------------------------- /content/impedancecontrol.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Impedance Control" 3 | metaTitle: "Impedance Control | Robotics Explained" 4 | metaDescription: "Impedance control allows us to control how the robot behaves during an interaction with the environment." 5 | --- 6 | 7 | In this chapter I explain what impedance control is about. Whenever robots are mentioned in this article, I have those consisting of links and joints in mind, with end-effectors to manipulate their environment. 8 | 9 | # Impedance 10 | 11 | Let’s start with the definition of impedance. 12 | Mechanical impedance is a measure how much a structure resists motion when subjected to a (harmonic) force. The inverse of the impedance is admittance. It is the ratio of velocity to force. You can think of pushing a child’s swing with a certain frequency. The lower the admittance of the swing, the more force is necessary to reach the same velocity of the swing. A swing with a very high admittance would just need a tiny little push and the child would swing up to the sky. Wheeee! 13 | 14 | ![swing](../images/impedance/swing.png "Swing") 15 | 16 | So let’s introduce some formulas. Z is the impedance, F is the force and v is the velocity. Whenever we have a force as an output we talk of impedance, i.e. $F = Z \cdot v$. Whenever we have a velocity as an output we talk of admittance, i.e. $v = Z^{-1}\cdot F$. 17 | 18 | Why is this important for robotics? Whenever a robot’s end-effector gets in contact with its environment, both robot and environment react to the contact. The robot acts as an impedance, the environment acts as an admittance. The aim of impedance control is to control both the motion of the robot and its contact forces. To be able to model the connection between force and velocity of the robot, we have to look at mass-spring-damper systems. 19 | 20 | # Mass-spring-damper systems 21 | 22 | So imagine you are a boxer. You put on your boxing gloves and are ready to punch a heavy bag in front of you. How does the heavy bag react if you punch it? 23 | 24 | Let’s assume for a moment that the heavy bag is solely defined by its mass. Remember Newton’s second law, $F=m\cdot a$? When you punch the heavy bag (a pure mass) with a certain force, it is accelerated proportional to the mass of the bag. 25 | 26 | ![bag](../images/impedance/bag.png "Bag") 27 | 28 | If the bag would have an infinite mass, there would be no motion at all. Instead, the bag is deformed. Let’s assume the bag behaves like a spring. Hooke’s law states $F=k\cdot x$. When you hit the bag with a certain force, the surface of the bag will be displaced proportional to the stiffness k of the bag’s material until an equilibrium between the force of your fist and the force of the spring is reached. 29 | 30 | ![bag_mass](../images/impedance/bag_mass.png "Bag Mass") 31 | 32 | Now imagine you are not in the boxing gym, but on the bottom of a deep lake. Each punch now is muuuuch slower, because your motion is damped. The damping force is proportional to the velocity, i.e. $F=-c \cdot v$. The faster your hand moves, the more force is needed to move your fist. 33 | 34 | ![water](../images/impedance/water.png "Water") 35 | 36 | Describing the robot as a mass-spring-damper system, we can express the relationship between force and velocity (i.e. the impedance) as $F=M\cdot a + C \cdot v + K \cdot x$. Note: There are still acceleration $a$ and position $x$ in our equation, which are derivative and integral of the velocity respectively. Expressing our formula in the Laplace domain, we get $F(s)=(Ms+C+Ks^{-1})\cdot v$, with the impedance $Z = Ms+C+Ks^{-1}$. 37 | 38 | # Impedance Control 39 | 40 | By controlling the impedance, we control how the robot behaves during an interaction with the environment by defining its stiffness and damping. We could let it behave like a very loose spring with high compliance! When the robot would be pushed it would move back and force until it reaches after some time its initial position. If we add some damping the end-effector might even return to its initial position after the displacements with no oscillations at all. On the contrary, the robot would only move, if there are high forces from the environment, if the stiffness is high. 41 | 42 | Let’s compare impedance control to other control strategies, i.e. position control and force control. In position control a certain position is commanded and the robot tries to reach the position no matter what. It if can not easily reach the position it will apply high forces which might cause damage. If we use impedance control, we can indirectly control the force and therefore avoid such damaging high forces. This is especially great if there are some uncertainties, e.g. a drill hole is slightly misplaced. The robot will act compliant, i.e. will move slightly as a reaction to the contact forces and slip into the drill hole instead of getting stuck trying to reach the exact commanded position. 43 | 44 | You might wonder why we do not use force control instead. Force control behaves poorly, if the robot’s end-effector is not in contact with another object, as forces will lead to fast movements. Think of walking up a stair, erroneously believing that there is one additional step at the end. You move your foot up and try to push it onto the step. Because no contact is established your food moves down very fast and you have to struggle to keep your balance. 45 | 46 | ![stair](../images/impedance/stair.png "Stair") 47 | 48 | The same is true for a robot trying to apply a force when its end-effector is not in contact with a surface. The big advantage of impedance control is the possibility to control the motion and the force of the robot’s end-effector at the same time! 49 | 50 | Let’s look again at some formulas. The robot’s torques $\tau$ can be described in relation to the joint angles q as 51 | 52 | $$\tau = M(q)\ddot q + C(q, \dot q) + g(q)+\tau_{ext}$$ 53 | 54 | where M is the mass matrix, C are Coriolis torques and g are gravity torques. With 55 | 56 | $$\tau = K(q_d -q) + D(\dot q_d - \dot q) + M(q)\ddot q_d + C(q, \dot q) + g(q)$$ 57 | 58 | as a control law with desired values for joint angles, velocities and accelerations, we get for the closed loop 59 | 60 | $$\tau_{ext} = K(q_d -q) + D(\dot q_d - \dot q) + M(q)(\ddot q_d - \ddot q)$$ 61 | 62 | where K is the stiffness matrix and D is the damping matrix. The robot behaves as a mass-spring-damper system to the environment! -------------------------------------------------------------------------------- /content/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Robotics explained" 3 | --- 4 | 5 | Hi, I'm Annika. 6 | 7 | I am a roboticist and software engineer with several years of experience in intuitive robot programming, robotics control and motion planning. 8 | 9 | On this site, I explain some core robotics concepts helpful to get you started with robotics or to deepen your knowledge. If you are new to robotics I suggest to start with the article about robot poses, but feel free to pick any topic from the menu you are interested in and start there. Up to now, only a small portion of the core concepts of robotics are covered. I plan to update this site regularly with new content. 10 | 11 | Even though I try to explain things as simple as possible, mathematics and especially linear algebra is the language of robotics, so be prepared to see many vectors and matrices. 12 | 13 | If you have questions or feedback, don't hesitate to contact me at 14 | 15 | 16 | 17 | 1. [Robot Pose](/robotposes) 18 | 2. [Transformation Matrix](/transformation) 19 | 3. [Forward Kinematics](/forwardkinematics) 20 | 4. [Inverse Kinematics](/inversekinematics) 21 | 4. [Robot Jacobian](/jacobian) 22 | 4. [Singularity](/singularity) 23 | 5. [Dynamics](/dynamics) 24 | 5. [Impedance Control](/impedancecontrol) -------------------------------------------------------------------------------- /content/inversekinematics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Inverse Kinematics" 3 | metaTitle: "Inverse kinematics | Robotics Explained" 4 | metaDescription: "Inverse kinematics allows us to compute the configuration of the robot's joints given the pose of its end-effector." 5 | --- 6 | 7 | To be honest, we normally don't want our robot to tell us where it is - we want to tell where it should be. How does we achieve this? Imagine you have a GPS device and want to move to 22°13'27.8"N 22°06'55.7"E. The GPS device only tells you your position, just the coordinates, no map. You are for sure able to get to the goal position by moving in a random direction and comparing the coordinates on your device with the goal coordinates, but this would be quite inefficient. If we tell our robot to move to the goal pose (0.5, 0.1) with a rotation of 0.75, it could start moving its motors in a random direction and compare how much the current pose of the end-effector differs from the goal pose. 8 | 9 | Try it out yourself! Can you move the end-effector to (0,0) with a rotation of 0? The current pose is displayed at the bottom of the simulation. 10 | 11 | 12 | 13 | However, it would be much better if we could just compute the configuration - the joint angles - the robot will have at the goal pose and move the joints straight to this configuration. 14 | 15 | In other words, we try to find a formula which takes a pose as an input and outputs the configuration q of the robot. In forwards kinematics we developed a formula which takes the configuration as an input and outputs the pose. We therefore call this problem - you might have guessed it - *inverse kinematics* or IK for short. 16 | 17 | Unfortunately there is no one size fits all solution. Depending on the robot, the complexity of this problem varies significantly and there are different strategies to solve it. Two major approaches are algebraic and geometric inverse kinematics. 18 | 19 | # Algebraic Inverse Kinematics 20 | 21 | For algebraic inverse kinematics, an equation system is set up and solved. For our planar robot we could use the three equations for the forwards kinematics and solve them for $q_0$, $q_1$ and $q_2$. These equation are 22 | 23 | $$\phi_{O,ee} = q_0+q_1+q_2$$ 24 | 25 | $$x_{O,ee} = l_0 \cdot cos(q_0) + l_1 \cdot cos(q_0+q_1) + l_2 \cdot cos(q_0+q_1+q_2)$$ 26 | 27 | $$y_{O,ee} = l_0 \cdot sin(q_0) + l_1 \cdot sin(q_0+q_1) + l_2 \cdot sin(q_0+q_1+q_2)$$ 28 | 29 | where $l_0$, $l_1$ and $l_2$ are the lengths of the links. With a good mathematical software solving these equations would be a matter of plugging in the formulas and waiting for a solution. However, in this article I want to show you a goemetric approach. 30 | 31 | # Geometric Inverse Kinematics 32 | 33 | As the name of this approach suggests, we are going to use a lot of trigonometry. TO be prepared for the next sections, it is helpful to have the definitions of sine, cosine and tangent in mind. We also use the Pythagorean theorem and the law of cosines. 34 | 35 | In the following image you see our robot with an arbitrary pose. Given x, y and $\phi$, we want to compute the three joint angles $q_0$, $q_1$ and $q_2$. I marked four triangles in the image, a green, red, yellow, and blue one. Let's look what we know about these triangles. 36 | 37 | ![geometric](../images/inverse/geometric.png "Geometric Inverse Kinematics") 38 | 39 | ## Green triangle 40 | 41 | We already know a lot about the green triangle. It has a right angle at the bottom right, the bottom left is equal to $\phi$, its hypotenuse is equal to the length of the end-effector $l_2$ and the corner at the top has the position (x,y). With this information we can compute the position of $p_2$, the center of the joint connected to the end-effector. With the definition of sine and cosine, we can compute the length of the bottom side of the rectangle with $l_2 \cdot sin(\phi)$ and the right side of the triangle with $l_2 \cdot cos(\phi)$. The point $p_2$ is therefore defined as 42 | 43 | $$ 44 | p_2 = \begin{bmatrix} 45 | x' \\ 46 | y' 47 | \end{bmatrix} = \begin{bmatrix} 48 | x - l_2 \cdot cos(\phi) \\ 49 | y - l_2 \cdot sin(\phi) 50 | \end{bmatrix} 51 | $$ 52 | 53 | ## Red triangle 54 | 55 | The red triangle has a right angle at the bottom right corner. We can therefore use the Pythagorean theorem to compute the hypotenuse r by using the x- and y- coordinate of $p_2$. 56 | 57 | $$r^2 = x'^2 + y'^2$$ 58 | 59 | Furthermore, we can compute the the angle at the bottom left corner with $atan\Big(\frac{x'}{y'}\Big)$ 60 | 61 | ## Blue triangle 62 | 63 | Now things get more complicated! The blue triangle has no right angle. We use thefore the *Law of cosines* $a^2=b^2+c^2-2bc\cos\alpha$ where a, b and c are the sides of the triangle and $\alpha$ is the angle between the sides b and c. In our triangle, $a = r$, $b = l_0$, $c = l_1$ and $\alpha = \pi - q_1$. By rearranging the formula and plugging in the equation for $r^2$ from the red triangle, we are able to compute the angle of the second joint $q_1$. 64 | 65 | $$q_1 = acos\Big(\frac{x'^2 + y'^2 - l_0^2 - l_1^2}{2l_0l_1}\Big)$$ 66 | 67 | ## Yellow triangle 68 | 69 | The yellow triangle has a right angle at the top. We therefore know, that the side right to the top angle has the length $l_1 \cdot cos(q_1)$ and the side left to the top angle has the length $l_1 \cdot sin(q_1)$ 70 | 71 | ## Blue-yellow triangle 72 | 73 | The angle of the first joint $q_0$ is the sum of the bottom left angles of the blue and the red triangle. We already computed this angle for the red triangle. The angle for the blue triangle can be computed by looking at the triangle consisting of the blue and the yellow triangle. It has a right angle at the top. The angle at the bottom left is therefore $-atan\Big(\frac{l_1 \cdot sin(q_1)}{l_0 + l_1 \cdot cos(q_1)}\Big)$. We can compute $q_0$ with 74 | 75 | $$q_0 = atan\Big(\frac{x'}{y'}\Big) - atan\Big(\frac{l_1 \cdot sin(q_1)}{l_0 + l_1 \cdot cos(q_1)}\Big)$$ 76 | 77 | The only angle missing is now $q_2$. We can compute it with $q_2 = \phi - q_1 - q_0$. 78 | 79 | ## Seeing it in action 80 | 81 | In the interactive demo below, you can see the inverse kinematics in action. With the sliders on top you can modify x, y and $\phi$. At the bottom you see the values for the three joint angles. Whenever the values for x, y or $\phi$ are changed, the configuration is computed with the formulas we just derived. The configuration is then used to draw the robot. 82 | 83 | -------------------------------------------------------------------------------- /content/jacobian.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Robot Jacobian" 3 | metaTitle: "Robot Jacobian | Robotics Explained" 4 | metaDescription: "The robot Jacobian is the relationship between the angular velocity of the joints and the linear and angular velocity of the end-effector" 5 | --- 6 | 7 | We now know how the position of the end-effector in the Cartesian space relates to the configuration of the robot. But robots are no statues, they move around quite a lot and we are interested how fast they are moving. 8 | 9 | # Linear and angular velocity 10 | 11 | Let's refresh quickly our knowledge about velocity. In school we learned that velocity is the distance over time or te be more precise the rate of change of the position, i.e. its time derivative. The linear velocity $v$ can be computed by 12 | 13 | $$v = \frac{ds}{dt}$$ 14 | 15 | and the angular velocity $\omega$ by 16 | 17 | $$\omega = \frac{d\theta}{dt}$$ 18 | 19 | A quick note about the notation: I will use from now on dots to denote the time derivative of a value. Instead of writing $\frac{dq}{dt}$ for the joint velocity, I will use $\dot{q}$ instead. You will also see multiple dots for the second or third derivatives. 20 | 21 | We can now tell the angular velocity of the joints quite easily by taking into account the angle we moved in a certain time span. 22 | If you move for example the slider for $q_1$ in one second from the right to the left, the joint moves with a rotational velocity of $\dot{q_1} = 2\pi \frac{rad}{s}$. 23 | 24 | 25 | 26 | But what is the linear velocity of the end-effector? The linear velocity of the end-effector $v_{ee}$ can be decomposed into two components: The linear velocity in x-direction and the linear velocity in y-direction. As we derived in the chapter about forwards kinematics, the position of the end-effector if we are only able to move $q_1$ is 27 | 28 | $$ 29 | p_{ee} = 30 | \begin{bmatrix} 31 | l_0 + l_1 \cdot cos(q_1) \\ 32 | l_0 + l_1 \cdot sin(q_1) 33 | \end{bmatrix} 34 | $$ 35 | 36 | where $q_1$ changes over time and $l_0$ and $l_1$ are constant. By computing the time derivative, we get the velocity 37 | 38 | $$ 39 | v_{ee} = 40 | \begin{bmatrix} 41 | -l_1 \cdot sin(q_1) \\ 42 | l_1 \cdot cos(q_1) 43 | \end{bmatrix} 44 | $$ 45 | 46 | # Jacobian matrix 47 | 48 | I wrote now a lot about angular and linear velocity without mentioning once the title of this chapter, the Jacobian. It is a matrix named after the mathematician Jacobi. In robotics we use it to express the relationship between the angular velocity of the joints and the linear and angular velocity of the end-effector, i.e. 49 | 50 | $$\dot{p}_{ee} = J_{ee} \cdot \dot{q}$$ 51 | 52 | where $J_{ee}$ consists of all partial derivatives of the kinematics functions. The first two rows are the partial derivatives for the position (x,y) and the last row are the partial derivatives for the angle $\phi$ of the end-effector. 53 | 54 | $$ 55 | J_{ee} = 56 | \begin{bmatrix} 57 | \frac{\partial x}{\partial q_0} & \frac{\partial x}{\partial q_1} & \frac{\partial x}{\partial q_2} \\ 58 | \frac{\partial y}{\partial q_0} & \frac{\partial y}{\partial q_1} & \frac{\partial y}{\partial q_2} \\ 59 | \frac{\partial \phi}{\partial q_0} & \frac{\partial \phi}{\partial q_1} & \frac{\partial \phi}{\partial q_2} 60 | \end{bmatrix} 61 | $$ 62 | 63 | A quick refresh of partial derivatives. $\frac{\partial x}{\partial q_0}$ means, we take the formula for $x$ and assume that all other variables except $q_0$ are constants. Then we compute the derivative. 64 | 65 | We get the following partial derivatives for $x$. 66 | 67 | $$\frac{\partial x}{\partial q_0} = - l_0 \cdot sin(q_0) - l_1 \cdot sin(q_0+q_1) - l_2 \cdot sin(q_0+q_1+q_2)$$ 68 | 69 | $$\frac{\partial x}{\partial q_1} = - l_1 \cdot sin(q_0+q_1) - l_2 \cdot sin(q_0+q_1+q_2)$$ 70 | 71 | $$\frac{\partial x}{\partial q_2} = - l_2 \cdot sin(q_0+q_1+q_2)$$ 72 | 73 | We can do the same for $y$. 74 | 75 | $$\frac{\partial y}{\partial q_0} = l_0 \cdot cos(q_0) + l_1 \cdot cos(q_0+q_1) + l_2 \cdot cos(q_0+q_1+q_2)$$ 76 | 77 | $$\frac{\partial y}{\partial q_1} = l_1 \cdot cos(q_0+q_1) + l_2 \cdot cos(q_0+q_1+q_2)$$ 78 | 79 | $$\frac{\partial y}{\partial q_2} = l_2 \cdot cos(q_0+q_1+q_2)$$ 80 | 81 | The partial derivatives for $\phi$ are all the same. 82 | 83 | $$\frac{\partial \phi}{\partial q_0} = 1$$ 84 | 85 | $$\frac{\partial \phi}{\partial q_1} = 1$$ 86 | 87 | $$\frac{\partial \phi}{\partial q_2} = 1$$ 88 | 89 | Given the velocity of each joint, we can now immediately compute the linear and angular velocities of the end-effector. But this is not the only thing the robot Jacobian is used for! -------------------------------------------------------------------------------- /content/robotposes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Robot Pose" 3 | metaTitle: "Robot Pose | Robotics Explained" 4 | metaDescription: "Robot poses are used to describe the position and rotation of a robot's end-effector in the workspace." 5 | --- 6 | 7 | # The Cartesian space 8 | 9 | Imagine you call your friend. 10 | >You: "Hey, where are you?" 11 | >Friend: "I'm in the park at the bench near the entrance by the church." 12 | >You: "Cool, I will be there in twenty minutes" 13 | 14 | ![park](../images/robotposes/park.png "Park") 15 | 16 | The next day you call your robot. 17 | >You: "Hey, where are you?" 18 | >Robot: "I'm at $[0.1, 0.5, 0]^T$" 19 | >You: "Cool, I will be there in... Wait, where are you?" 20 | 21 | What happened? Robot and you don't speak the same language. In the first dialogue your friend described a position in the world based on some distinctive points. They rely on your knowledge about these points and your ability to identify them. The robot has neither the knowledge nor the sensors to be able to identify such distinctive points. But what happens if we change the scenario? 22 | 23 | >You: "Hey, where are you?" 24 | >Friend: "I'm in the middle of the desert besides a grey rock near a dune." 25 | >You: "Cool, I will be there in... Wait, where are you?" 26 | 27 | ![desert](../images/robotposes/desert.png "Desert") 28 | 29 | In this scenario you both don't have knowledge about the region and you cannot rely on your sensors (i.e. eyes) to identify rocks and dunes correctly as they all might look similar and the environment will change in the desert due to wind and other environmental conditions. 30 | 31 | What do you do? 32 | >You: "Hey, where are you?" 33 | >Friend: "I'm at 22°13'27.8"N 22°06'55.7"E" 34 | >You: "Cool, I will be there in twenty hours." 35 | 36 | You take a GPS device and twenty hours later, you arrive at the said point. Great! What changed? You used the geographic coordinate system which specifies exactly each point on the surface of the earth and a device which translates its state into the language of this system. 37 | 38 | We can talk in a similar way to robots. We take the space in which the robot can move - the *workspace* - and use a system which is able to describe every point within this space. In robotics, we often use the *Cartesian space*. This is just a fancy name for the well known coordinate system with an x- and y-axes (and z-axes in three dimensional space) we drew a thousand times in school. 39 | 40 | # Robot poses and degrees of freedom 41 | 42 | Within the Cartesian space we are able to name a point where the robot should move to. It is often not only important to which position the robot moves but also from which direction a robot's tool points to a position. Imagine for example a robot equipped with a drilling machine. It matters in which direction the hole is drilled, not just at which position. Or a mobile robot which should park in a parking lot. If the rotation of the robot is wrong the owners from the adjacent cars won't be amused. 43 | 44 | ![cars](../images/robotposes/car.png "Cars") 45 | 46 | We therefore use a so-called *pose* to describe how the robot is positioned. In two dimensional space the pose consists of 47 | 48 | * the x-coordinate 49 | * the y-coordinate 50 | * an angle $\phi$ for the rotation 51 | 52 | ![pose](../images/robotposes/pose.png "Pose") 53 | 54 | The end-effector cannot move without changing one of these values. If we defined our pose to consist only of the x-coordinate and the angle $\phi$, the robot could move vertically, without changing its pose. We therefore say that the two-dimensional space has three *degrees of freedom*. Things get more complicated the more dimensions we take into account. In three dimensional space we need the x-, y-, and z-coordinates as well as three angles (e.g. roll, pitch, yaw) to describe the pose: It has six degrees of freedom. The four dimensional space already has ten degrees of freedom. 55 | 56 | # Robot kinematics 57 | 58 | If someone would tell us "Move to 22°13'27.8"N 22°06'55.7"E" we would not be able to achieve this without a device which measures for us where we are. This is the same for the robot. GPS is too inaccurate for most tasks to determine where the tip of the end-effector is located. Imagine we have a robot which should put dishes into the dishwasher. Even an (in)accuracy of 10 cm could result in broken dishes on the floor. Even though there exist sensors which are able to accurately measure the position of the end-effector, they are often quite expensive. But there are other solutions! For a stationary robot, e.g. a robot arm, we can compute the pose of the end-effector by taking into account the geometry of the robot and the state of the motors. This is comparable to predicting the pose of our left index finger by measuring the contraction of every muscle and the length of every bone in our body. The relationship between the geometry of the robot and its movement in the Cartesian space is called *kinematics*. Before we can dive into kinematics though, we need a tool omnipresent in robotics: *Transformation matrices*. -------------------------------------------------------------------------------- /content/singularity.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Singularity" 3 | metaTitle: "Singularity | Robotics Explained" 4 | metaDescription: "Singularities in robotics" 5 | --- 6 | 7 | There is no single definition of the term *singularity*, each discipline seems to have its own: There are mathematical singularities, gravitational singularities, technological singularities and many more. In this article we are interested in the so-called mechanical singularity. What does this mean? In robotics, a singularity is a certain configuration where the motion of the end-effector is restricted. If a robot is in a singularity it "loses" one or more degrees of freedom. 8 | 9 | If the robot is for example fully stretched like in the example below, it is not able to change the rotation nor the y-position. It has lost two degrees of freedom. This is a so-called boundary singularity, as we are at the boundary of the workspace. 10 | 11 | 12 | 13 | But there are other singularities, which are not as obvious. If you set $y = 0$ in the interactive demo and move the x-slider to the left, you will notice the robot joints flips from one side to the other when the end-effector is around (0,0). A real robot would of course not be able to perform such a motion as it requires nearly infinite joint velocity to maintain the velocity in Cartesian space. 14 | 15 | Working with robots means we have to avoid such configurations, as they can damage the robot and lead to uncontrolled behavior. But how do we know where the singularities are? 16 | 17 | In the last chapter we learned about the robot Jacobian and how it expresses the relationship between joint velocity and the linear and angular velocity of the end-effector. 18 | 19 | $$\dot{p}_{ee} = J_{ee} \cdot \dot{q}$$ 20 | 21 | When we want to reason about the robot's movement in Cartesian space, we have to rearrange this formula. This means we have to invert the Jacobian. 22 | 23 | $$\dot{q} = J^{-1}_{ee} \cdot \dot{p}_{ee}$$ 24 | 25 | However, it is not always possible to invert a matrix. This is the case if its determinant is zero. Let's compute the determinant of the Jacobi introduced in the last chapter. You probably learned in school how to compute the determinant of a matrix - I was lazy and let my computer do the work for me. The result is 26 | 27 | $$det(J_{ee}) = sin(q_1)$$ 28 | 29 | We are now interested in configurations where $sin(q_1)$ is zero, as these configurations are singularities. This is the case if $q_1$ is equal to zero or $\pi$. Let's look if this is true for the examples at the beginning of this chapter. For the boundary singularity, the first and second link are aligned and $q_1$ is therefore zero. For the singularity where the robot flips around (0,0) $q_1$ is equal to $\pi$. Great! 30 | 31 | We are now able to tell which configurations of our robot are singularities! 32 | 33 | -------------------------------------------------------------------------------- /content/transformation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Transformation matrices" 3 | metaTitle: "Transformation matrices | Robotics Explained" 4 | metaDescription: "This article is about transformation matrices and why they are important in robotics." 5 | --- 6 | 7 | # Rotation matrices 8 | 9 | Let's say we have a robot arm in a two-dimensional space with three links. Links are the static structures of the robot, comparable to our bones. The links are connected by rotational joints. A rotational joint is similar to our elbow. If we move only the forearm, our hand makes a circular motion around the elbow. The "hand" of the robot - i.e. the last link - is the so-called end-effector. It is designated to interact with the environment. 10 | 11 | You can see the robot I just described in the following interactive demo. To define the Cartesian space, we have to set an origin, where x and y are zero. We can choose any point in the workspace. To simplify our computations we can just choose the center of the first joint as the origin. To the right of this origin is the positive x-dimension, to the top the positive y-dimension. We also have to choose the length of the links of our robot. Let's say the first two links have a length of 0.15 m and the end-effector has a length of 0.03 m. When all joints are at zero (we will write this in the future as $q = [0,0,0]^T$), the tip of the end-effector is at (0.33, 0) with a rotation of 0. This is because all links form a long chain in x-direction (0.15 + 0.15 + 0.03 = 0.33). 12 | 13 | A quick note about units: For readability I often omit units when I think it is clear if it is a linear measure or an angle from the context. The units I omit are always SI-units, i.e. meter [m] for a linear measure and radiant [rad] for an angle. 14 | 15 | But back to our robot. If you move the slider for the first joint (q0), the end-effector will make a circular movement around the origin. 16 | 17 | 18 | 19 | The pose of the tip of the end-effector depends on the angle of the first joint. How can we compute the new pose of the end-effector given the angle $\phi$? To rotate a point counter clockwise by an angle $\phi$, we can use a so-called *rotation matrix* to compute the transformed point. The rotation matrix might look in the beginning a bit scary, but it is super useful! 20 | 21 | $$ 22 | R(\phi) = \begin{bmatrix} 23 | cos(\phi) & - sin(\phi) \\ 24 | sin(\phi) & cos(\phi) \\ 25 | \end{bmatrix} 26 | $$ 27 | 28 | By multiplying a point with this matrix, we get the position of the rotated point. If we rotate for example the position of the end-effector (0.33, 0) by 0.5 rad, we get the new position at (0.28, 0.16). 29 | 30 | $$ 31 | p_{ee} = \begin{bmatrix} 32 | cos(0.5) & - sin(0.5) \\ 33 | sin(0.5) & cos(0.5) \\ 34 | \end{bmatrix} 35 | \cdot 36 | \begin{bmatrix} 37 | 0.33 \\ 38 | 0 \\ 39 | \end{bmatrix} 40 | = 41 | \begin{bmatrix} 42 | 0.26 \\ 43 | 0.16 \\ 44 | \end{bmatrix} 45 | $$ 46 | 47 | The robot with one moving joint has a degree of freedom of one. This means we can choose only the value of one of the three degrees of freedom of the two-dimensional space, the other ones are fixed. If we want to move e.g. to y = 0.33, x has to be 0 and the orientation has to be 0.5 $\pi$. If we want a rotation of 0.5, x has to be 0.28 and y has to be 0.16. We are not able to move e.g. to y = 0.33 and x = 0.1. 48 | 49 | # Transformation matrices 50 | 51 | We are now able to compute every point the end-effector can reach by moving the first joint. But these points lie all in a circle around the base point, which is not very useful for a robot. We want to be able to reach every point in the workspace with an arbitrary rotation of the end-effector. 52 | 53 | Let's look at what happens if we only move the second joint. 54 | 55 | 56 | 57 | The tip of the end-effector rotates now around this joint with a smaller radius than before. We can therefore use a similar rotation matrix as the one for the first joint. But additional to this rotation, we have a translation of the center of this point by $[0.15,0]^T$. How can we now compute the position of the end-effector? We can use a *transformation matrix* which combines rotation and translation in a single 3x3 matrix. It consists of the rotation matrix, the translation vector $[x,y]^T$ and $[0,0,1]$ in the last row. The last row seems to be unnecessary, but you will see soon, that it comes very handy! 58 | 59 | $$ 60 | T(x,y,\phi) 61 | = \begin{bmatrix} 62 | cos(\phi) & - sin(\phi) & x \\ 63 | sin(\phi) & cos(\phi) & y \\ 64 | 0 & 0 & 1 65 | \end{bmatrix} 66 | $$ 67 | 68 | We can now compute the position of the end-effector by multiplying it with the transformation matrix for our first joint. Let's say we want to rotate the second joint by 0.5. In the transformation matrix we replace $\phi$ with 0.5, x with 0.15 and y with 0. Why 0.15 and 0 for x and y? This is the position of the second joint. The initial point of the end-effector is the same as before, i.e. (0.33, 0), but as the first link of the robot is included in the transformation, our point is now (0.33-0.15,0) = (0.18,0). To be able to multiply it with a 3x3 matrix, we have to augment it with a 1: $[0.18,0,1]^T$. 69 | 70 | $$ 71 | p_{ee} = 72 | \begin{bmatrix} 73 | cos(0.5) & - sin(0.5) & 0.15 \\ 74 | sin(0.5) & cos(0.5) & 0 \\ 75 | 0 & 0 & 1 76 | \end{bmatrix} \cdot 77 | \begin{bmatrix} 78 | 0.18 \\ 0 \\ 1 79 | \end{bmatrix} = 80 | \begin{bmatrix} 81 | 0.3 \\ 0.08 \\ 1 82 | \end{bmatrix} 83 | $$ 84 | 85 | The great thing about transformation matrices is, that we can chain them together. Transforming a point from A to B with $T_{A,B}$ and then transforming it from B to C with $T_{B,C}$ is the same as transforming a point from A to C with $T_{A,C} = T_{A,B} \cdot T_{B,C}$. In the next article we will use this property of transformation matrices to compute the tip of the end-effector of the robot, when all three joints rotate. -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | export const onServiceWorkerUpdateReady = () => { 2 | const answer = window.confirm( 3 | `This tutorial has been updated. ` + 4 | `Reload to display the latest version?` 5 | ) 6 | if (answer === true) { 7 | window.location.reload() 8 | } 9 | } -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const config = require("./config"); 3 | const plugins = [ 4 | 'gatsby-plugin-sitemap', 5 | 'gatsby-plugin-sharp', 6 | 'gatsby-plugin-catch-links', 7 | { 8 | resolve: `gatsby-plugin-layout`, 9 | options: { 10 | component: require.resolve(`./src/templates/docs.js`) 11 | } 12 | }, 13 | 'gatsby-plugin-emotion', 14 | 'gatsby-plugin-react-helmet', 15 | { 16 | resolve: "gatsby-source-filesystem", 17 | options: { 18 | name: "docs", 19 | path: `${__dirname}/content/` 20 | } 21 | }, 22 | { 23 | resolve: "gatsby-source-filesystem", 24 | options: { 25 | name: "docs", 26 | path: `${__dirname}/images/` 27 | } 28 | }, 29 | { 30 | resolve: 'gatsby-plugin-mdx', 31 | options: { 32 | gatsbyRemarkPlugins: [ 33 | { 34 | resolve: "gatsby-remark-images", 35 | options: { 36 | maxWidth: 1035, 37 | } 38 | }, 39 | 'gatsby-remark-copy-linked-files', 40 | 'gatsby-remark-katex', 41 | 'gatsby-remark-relative-images', 42 | ], 43 | extensions: [".mdx", ".md"] 44 | } 45 | }, 46 | { 47 | resolve: `gatsby-plugin-gdpr-cookies`, 48 | options: { 49 | googleAnalytics: { 50 | trackingId: 'UA-187924281-1', 51 | cookieName: 'gatsby-gdpr-google-analytics', // default 52 | // Setting this parameter is optional 53 | anonymize: true 54 | }, 55 | googleTagManager: { 56 | trackingId: '', // leave empty if you want to disable the tracker 57 | cookieName: 'gatsby-gdpr-google-tagmanager', // default 58 | dataLayerName: 'dataLayer', // default 59 | }, 60 | facebookPixel: { 61 | pixelId: '', // leave empty if you want to disable the tracker 62 | cookieName: 'gatsby-gdpr-facebook-pixel', // default 63 | }, 64 | // Defines the environments where the tracking should be available - default is ["production"] 65 | // environments: ['production', 'development'] 66 | }, 67 | }, 68 | { 69 | resolve: `gatsby-plugin-manifest`, 70 | options: { 71 | icon: `images/favicon.png`, 72 | name: `Robotics Explained`, 73 | short_name: `RobExpl`, 74 | start_url: `/`, 75 | background_color: `#f7f0eb`, 76 | theme_color: `#a2466c`, 77 | display: `standalone`, 78 | } 79 | }, 80 | ]; 81 | 82 | module.exports = { 83 | pathPrefix: '/', 84 | siteMetadata: { 85 | title: 'Robotics Explained | Robot Course', 86 | description: 'A course about robotics', 87 | docsLocation: config.siteMetadata.docsLocation, 88 | ogImage: config.siteMetadata.ogImage, 89 | favicon: 'images/favicon.png', 90 | siteUrl: 'https://robotics-explained.com', 91 | }, 92 | plugins: plugins 93 | }; 94 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const componentWithMDXScope = require('gatsby-plugin-mdx/component-with-mdx-scope'); 2 | 3 | const path = require('path'); 4 | 5 | const startCase = require('lodash.startcase'); 6 | 7 | const config = require('./config'); 8 | 9 | exports.createPages = ({ graphql, actions }) => { 10 | const { createPage } = actions; 11 | 12 | return new Promise((resolve, reject) => { 13 | resolve( 14 | graphql( 15 | ` 16 | { 17 | allMdx { 18 | edges { 19 | node { 20 | fields { 21 | id 22 | } 23 | tableOfContents 24 | fields { 25 | slug 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | ).then(result => { 33 | if (result.errors) { 34 | console.log(result.errors); // eslint-disable-line no-console 35 | reject(result.errors); 36 | } 37 | 38 | // Create blog posts pages. 39 | result.data.allMdx.edges.forEach(({ node }) => { 40 | createPage({ 41 | path: node.fields.slug ? node.fields.slug : '/', 42 | component: path.resolve('./src/templates/docs.js'), 43 | context: { 44 | id: node.fields.id, 45 | }, 46 | }); 47 | }); 48 | }) 49 | ); 50 | }); 51 | }; 52 | 53 | exports.onCreateWebpackConfig = ({ actions }) => { 54 | actions.setWebpackConfig({ 55 | resolve: { 56 | modules: [path.resolve(__dirname, 'src'), 'node_modules'], 57 | alias: { 58 | $components: path.resolve(__dirname, 'src/components'), 59 | buble: '@philpl/buble', // to reduce bundle size 60 | }, 61 | }, 62 | }); 63 | }; 64 | 65 | exports.onCreateBabelConfig = ({ actions }) => { 66 | actions.setBabelPlugin({ 67 | name: '@babel/plugin-proposal-export-default-from', 68 | }); 69 | }; 70 | 71 | exports.onCreateNode = ({ node, getNode, actions }) => { 72 | const { createNodeField } = actions; 73 | 74 | if (node.internal.type === `Mdx`) { 75 | const parent = getNode(node.parent); 76 | 77 | let value = parent.relativePath.replace(parent.ext, ''); 78 | 79 | if (value === 'index') { 80 | value = ''; 81 | } 82 | 83 | if (config.gatsby && config.gatsby.trailingSlash) { 84 | createNodeField({ 85 | name: `slug`, 86 | node, 87 | value: value === '' ? `/` : `/${value}/`, 88 | }); 89 | } else { 90 | createNodeField({ 91 | name: `slug`, 92 | node, 93 | value: `/${value}`, 94 | }); 95 | } 96 | 97 | createNodeField({ 98 | name: 'id', 99 | node, 100 | value: node.id, 101 | }); 102 | 103 | createNodeField({ 104 | name: 'title', 105 | node, 106 | value: node.frontmatter.title || startCase(parent.name), 107 | }); 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/favicon.png -------------------------------------------------------------------------------- /images/impedance/bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/impedance/bag.png -------------------------------------------------------------------------------- /images/impedance/bag_mass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/impedance/bag_mass.png -------------------------------------------------------------------------------- /images/impedance/stair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/impedance/stair.png -------------------------------------------------------------------------------- /images/impedance/swing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/impedance/swing.png -------------------------------------------------------------------------------- /images/impedance/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/impedance/water.png -------------------------------------------------------------------------------- /images/inverse/geometric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/inverse/geometric.png -------------------------------------------------------------------------------- /images/kinematics/touch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/kinematics/touch.png -------------------------------------------------------------------------------- /images/robot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /images/robotposes/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/robotposes/car.png -------------------------------------------------------------------------------- /images/robotposes/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/robotposes/desert.png -------------------------------------------------------------------------------- /images/robotposes/park.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/robotposes/park.png -------------------------------------------------------------------------------- /images/robotposes/pose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/images/robotposes/pose.png -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "robotics-explained", 3 | "private": true, 4 | "description": "", 5 | "author": "Annika Wollschläger ", 6 | "version": "0.0.1", 7 | "dependencies": { 8 | "@babel/plugin-proposal-export-default-from": "^7.7.4", 9 | "@emotion/core": "^10.0.22", 10 | "@emotion/styled": "^10.0.23", 11 | "@emotion/styled-base": "^10.0.24", 12 | "@mdx-js/loader": "^1.5.1", 13 | "@mdx-js/mdx": "^1.5.1", 14 | "@mdx-js/react": "^1.5.1", 15 | "@philpl/buble": "^0.19.7", 16 | "@playlyfe/gql": "^2.6.2", 17 | "dotenv": "^8.2.0", 18 | "emotion": "^10.0.23", 19 | "emotion-server": "^10.0.17", 20 | "emotion-theming": "^10.0.19", 21 | "gatsby": "^2.18.10", 22 | "gatsby-link": "^2.2.27", 23 | "gatsby-plugin-catch-links": "^2.9.0", 24 | "gatsby-plugin-emotion": "^4.1.18", 25 | "gatsby-plugin-gdpr-cookies": "^1.0.11", 26 | "gatsby-plugin-gtag": "^1.0.12", 27 | "gatsby-plugin-layout": "^1.1.18", 28 | "gatsby-plugin-manifest": "^2.2.33", 29 | "gatsby-plugin-mdx": "^1.0.61", 30 | "gatsby-plugin-offline": "^3.0.29", 31 | "gatsby-plugin-react-helmet": "^3.1.18", 32 | "gatsby-plugin-remove-serviceworker": "^1.0.0", 33 | "gatsby-plugin-sharp": "^2.3.7", 34 | "gatsby-plugin-sitemap": "^2.2.24", 35 | "gatsby-remark-copy-linked-files": "^2.1.33", 36 | "gatsby-remark-images": "^3.1.37", 37 | "gatsby-remark-katex": "^3.7.0", 38 | "gatsby-remark-relative-images": "^2.0.2", 39 | "gatsby-source-filesystem": "^2.1.42", 40 | "gatsby-transformer-remark": "^2.6.42", 41 | "graphql": "^14.5.8", 42 | "is-absolute-url": "^3.0.3", 43 | "katex": "^0.12.0", 44 | "lodash.flatten": "^4.4.0", 45 | "lodash.startcase": "^4.4.0", 46 | "react": "^16.12.0", 47 | "react-cookie-consent": "^6.2.1", 48 | "react-dom": "^16.12.0", 49 | "react-feather": "^2.0.3", 50 | "react-github-btn": "^1.1.1", 51 | "react-helmet": "^5.2.1", 52 | "react-id-generator": "^3.0.0", 53 | "react-instantsearch-dom": "^6.0.0", 54 | "react-live": "^2.2.2", 55 | "react-loadable": "^5.5.0", 56 | "styled-components": "^4.4.1", 57 | "styled-icons": "^9.0.1" 58 | }, 59 | "license": "MIT", 60 | "main": "n/a", 61 | "scripts": { 62 | "start": "gatsby develop", 63 | "build": "gatsby build --prefix-paths", 64 | "format": "prettier --write \"src/**/*.{js,jsx,css,json}\"", 65 | "lint": "eslint --fix \"src/**/*.{js,jsx}\"" 66 | }, 67 | "devDependencies": { 68 | "babel-eslint": "^10.1.0", 69 | "eslint": "^6.8.0", 70 | "eslint-config-prettier": "^6.10.0", 71 | "eslint-plugin-import": "^2.20.1", 72 | "eslint-plugin-jsx-a11y": "^6.2.3", 73 | "eslint-plugin-prettier": "^3.1.2", 74 | "eslint-plugin-react": "^7.19.0", 75 | "gatsby-plugin-remove-trailing-slashes": "^2.1.17", 76 | "prettier": "^1.19.1", 77 | "prism-react-renderer": "^1.0.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/CommunityAuthor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CommunityAuthor = ({ name, imageUrl, githubUrl, description }) => { 4 | return ( 5 | <> 6 |

About the community author

7 |
8 |
9 | {name} 10 |
11 |
12 |
13 | {name} 14 | {githubUrl ? ( 15 | 16 | Github Icon 21 | 22 | ) : null} 23 |
24 |
{description}
25 |
26 |
27 | 28 | ); 29 | }; 30 | 31 | export default CommunityAuthor; 32 | -------------------------------------------------------------------------------- /src/GithubLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const githubIcon = require('./components/images/github.svg'); 3 | 4 | const GithubLink = ({ link, text }) => { 5 | return ( 6 | 7 | github 8 | {text} 9 | 10 | ); 11 | }; 12 | 13 | export default GithubLink; 14 | -------------------------------------------------------------------------------- /src/YoutubeEmbed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const YoutubeEmbed = ({ link }) => { 4 | return ( 5 |
6 | 15 |
16 | ); 17 | }; 18 | 19 | export default YoutubeEmbed; 20 | -------------------------------------------------------------------------------- /src/components/DarkModeSwitch.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import NightImage from './images/night.png'; 5 | import DayImage from './images/day.png'; 6 | 7 | const StyledSwitch = styled('div')` 8 | display: flex; 9 | justify-content: flex-end; 10 | width: 100%; 11 | padding: 0 20px 0 25px; 12 | 13 | /* The switch - the box around the slider */ 14 | .switch { 15 | position: relative; 16 | display: inline-block; 17 | width: 50px; 18 | height: 20px; 19 | } 20 | 21 | /* Hide default HTML checkbox */ 22 | .switch input { 23 | opacity: 0; 24 | width: 0; 25 | height: 0; 26 | } 27 | 28 | /* The slider */ 29 | .slider { 30 | position: absolute; 31 | cursor: pointer; 32 | top: 0; 33 | left: 0; 34 | right: 0; 35 | bottom: 0; 36 | background-color: #ccc; 37 | -webkit-transition: 0.4s; 38 | transition: 0.4s; 39 | } 40 | 41 | .slider:before { 42 | position: absolute; 43 | content: ''; 44 | height: 30px; 45 | width: 30px; 46 | left: 0px; 47 | bottom: 4px; 48 | top: 0; 49 | bottom: 0; 50 | margin: auto 0; 51 | -webkit-transition: 0.4s; 52 | transition: 0.4s; 53 | background: white url(${NightImage}); 54 | background-repeat: no-repeat; 55 | background-position: center; 56 | } 57 | 58 | input:checked + .slider { 59 | background: linear-gradient(to right, #fefb72, #f0bb31); 60 | } 61 | 62 | input:checked + .slider:before { 63 | -webkit-transform: translateX(24px); 64 | -ms-transform: translateX(24px); 65 | transform: translateX(24px); 66 | background: white url(${DayImage}); 67 | background-repeat: no-repeat; 68 | background-position: center; 69 | } 70 | 71 | /* Rounded sliders */ 72 | .slider.round { 73 | border-radius: 34px; 74 | } 75 | 76 | .slider.round:before { 77 | border-radius: 50%; 78 | } 79 | `; 80 | 81 | export const DarkModeSwitch = ({ isDarkThemeActive, toggleActiveTheme }) => ( 82 | 83 | 92 | 93 | ); 94 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { StaticQuery, graphql } from 'gatsby'; 4 | import GitHubButton from 'react-github-btn'; 5 | import Link from './link'; 6 | import Loadable from 'react-loadable'; 7 | 8 | import config from '../../config.js'; 9 | import LoadingProvider from './mdxComponents/loading'; 10 | import { DarkModeSwitch } from './DarkModeSwitch'; 11 | 12 | import Sidebar from './sidebar'; 13 | 14 | function myFunction() { 15 | var x = document.getElementById('navbar'); 16 | 17 | if (x.className === 'topnav') { 18 | x.className += ' responsive'; 19 | } else { 20 | x.className = 'topnav'; 21 | } 22 | } 23 | 24 | const StyledBgDiv = styled('div')` 25 | height: 60px; 26 | background-color: #f8f8f8; 27 | position: relative; 28 | display: none; 29 | background: ${props => (props.isDarkThemeActive ? '#001932' : undefined)}; 30 | 31 | @media (max-width: 767px) { 32 | display: block; 33 | } 34 | `; 35 | 36 | const Header = ({ location, isDarkThemeActive, toggleActiveTheme }) => { 37 | const logoImg = require('./images/logo.svg'); 38 | const logoLink = 'https://robotics-explained.com'; 39 | 40 | return ( 41 |
42 | 67 | 68 |
69 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 |
84 |
85 | ) 86 | }; 87 | 88 | export default Header; 89 | -------------------------------------------------------------------------------- /src/components/NextPrevious.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | import { StyledNextPrevious } from './styles/PageNavigationButtons'; 5 | 6 | const NextPrevious = ({ mdx, nav }) => { 7 | let currentIndex; 8 | 9 | const currentPaginationInfo = nav.map((el, index) => { 10 | if (el && el.url === mdx.fields.slug) { 11 | currentIndex = index; 12 | } 13 | }); 14 | 15 | const nextInfo = {}; 16 | 17 | const previousInfo = {}; 18 | 19 | if (currentIndex === undefined) { 20 | // index 21 | if (nav[0]) { 22 | nextInfo.url = nav[0].url; 23 | nextInfo.title = nav[0].title; 24 | } 25 | previousInfo.url = null; 26 | previousInfo.title = null; 27 | currentIndex = -1; 28 | } else if (currentIndex === 0) { 29 | // first page 30 | nextInfo.url = nav[currentIndex + 1] ? nav[currentIndex + 1].url : null; 31 | nextInfo.title = nav[currentIndex + 1] ? nav[currentIndex + 1].title : null; 32 | previousInfo.url = null; 33 | previousInfo.title = null; 34 | } else if (currentIndex === nav.length - 1) { 35 | // last page 36 | nextInfo.url = null; 37 | nextInfo.title = null; 38 | previousInfo.url = nav[currentIndex - 1] ? nav[currentIndex - 1].url : null; 39 | previousInfo.title = nav[currentIndex - 1] ? nav[currentIndex - 1].title : null; 40 | } else if (currentIndex) { 41 | // any other page 42 | nextInfo.url = nav[currentIndex + 1].url; 43 | nextInfo.title = nav[currentIndex + 1].title; 44 | if (nav[currentIndex - 1]) { 45 | previousInfo.url = nav[currentIndex - 1].url; 46 | previousInfo.title = nav[currentIndex - 1].title; 47 | } 48 | } 49 | 50 | return ( 51 | 52 | {previousInfo.url && currentIndex >= 0 ? ( 53 | 54 |
55 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 | Previous 77 |
78 |
79 | {nav[currentIndex - 1].title} 80 |
81 |
82 | 83 | ) : null} 84 | {nextInfo.url && currentIndex >= 0 ? ( 85 | 86 |
87 |
88 | Next 89 |
90 |
91 | {nav[currentIndex + 1] && nav[currentIndex + 1].title} 92 |
93 |
94 |
95 | 108 | 109 | 110 | 111 | 112 | 113 |
114 | 115 | ) : null} 116 |
117 | ); 118 | }; 119 | 120 | export default NextPrevious; 121 | -------------------------------------------------------------------------------- /src/components/images/closed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ClosedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default ClosedSvg; 10 | -------------------------------------------------------------------------------- /src/components/images/day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/src/components/images/day.png -------------------------------------------------------------------------------- /src/components/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/images/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/images/night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woll-an/robotics-explained/e9f3cada622962d5b627a10f352a0c021ace0980/src/components/images/night.png -------------------------------------------------------------------------------- /src/components/images/opened.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const OpenedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default OpenedSvg; 10 | -------------------------------------------------------------------------------- /src/components/images/test.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './theme'; 2 | export mdxComponents from './mdxComponents'; 3 | export ThemeProvider from './theme/themeProvider'; 4 | export Layout from './layout'; 5 | export Link from './link'; 6 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { MDXProvider } from '@mdx-js/react'; 4 | 5 | import CookieConsent from 'react-cookie-consent'; 6 | import { initializeAndTrack } from 'gatsby-plugin-gdpr-cookies' 7 | import ThemeProvider from './theme/themeProvider'; 8 | import mdxComponents from './mdxComponents'; 9 | import Sidebar from './sidebar'; 10 | import RightSidebar from './rightSidebar'; 11 | import config from '../../config.js'; 12 | 13 | const Wrapper = styled('div')` 14 | display: flex; 15 | justify-content: space-between; 16 | background: ${({ theme }) => theme.colors.background}; 17 | 18 | .sideBarUL li a { 19 | color: ${({ theme }) => theme.colors.text}; 20 | fill: ${({ theme }) => theme.colors.text}; 21 | } 22 | 23 | .sideBarUL .item > a:hover { 24 | background-color: #1ed3c6; 25 | color: #fff !important; 26 | fill: #fff !important; 27 | /* background: #F8F8F8 */ 28 | } 29 | 30 | @media only screen and (max-width: 767px) { 31 | display: block; 32 | } 33 | `; 34 | 35 | const Content = styled('main')` 36 | display: flex; 37 | flex-grow: 1; 38 | margin: 0px 88px; 39 | padding-top: 3rem; 40 | background: ${({ theme }) => theme.colors.background}; 41 | 42 | table tr { 43 | background: ${({ theme }) => theme.colors.background}; 44 | } 45 | 46 | @media only screen and (max-width: 1023px) { 47 | padding-left: 0; 48 | margin: 0 10px; 49 | padding-top: 3rem; 50 | } 51 | `; 52 | 53 | const MaxWidth = styled('div')` 54 | @media only screen and (max-width: 50rem) { 55 | width: 100%; 56 | position: relative; 57 | } 58 | `; 59 | 60 | const LeftSideBarWidth = styled('div')` 61 | width: 298px; 62 | `; 63 | 64 | const RightSideBarWidth = styled('div')` 65 | width: 224px; 66 | `; 67 | 68 | const Layout = ({ children, location }) => ( 69 | 70 | 71 | 72 | initializeAndTrack(location)} 76 | declineButtonText="Decline" 77 | enableDeclineButton 78 | flipButtons 79 | style={{ background: "#000B17" }} 80 | buttonStyle={{ background: "#1ED3C6", color: "#000", fontFamily:"Montserrat" }} 81 | declineButtonStyle={{ background: "#bdfffa", color:"#aaa", fontFamily:"Montserrat" }} 82 | cookieName="gatsby-gdpr-google-analytics"> 83 | This site uses cookies and other tracking technologies to improve your browsing experience on the website, to analyze website traffic, and to understand where visitors are coming from. 84 | 85 | 86 | 87 | 88 | {config.sidebar.title ? ( 89 |
93 | ) : null} 94 | 95 | {children} 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ); 104 | 105 | export default Layout; 106 | -------------------------------------------------------------------------------- /src/components/link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as GatsbyLink } from 'gatsby'; 3 | import isAbsoluteUrl from 'is-absolute-url'; 4 | 5 | const Link = ({ to, ...props }) => 6 | isAbsoluteUrl(to) ? ( 7 | 8 | {props.children} 9 | 10 | ) : ( 11 | 12 | ); 13 | 14 | export default Link; 15 | -------------------------------------------------------------------------------- /src/components/mdxComponents/LiveProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 3 | 4 | const ReactLiveProvider = ({ code }) => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default ReactLiveProvider; 15 | -------------------------------------------------------------------------------- /src/components/mdxComponents/anchor.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const AnchorTag = ({ children: link, ...props }) => { 4 | if (link) { 5 | return ( 6 | 7 | {link} 8 | 9 | ); 10 | } else { 11 | return null; 12 | } 13 | }; 14 | 15 | export default AnchorTag; 16 | -------------------------------------------------------------------------------- /src/components/mdxComponents/codeBlock.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Highlight, { defaultProps } from 'prism-react-renderer'; 3 | import prismTheme from 'prism-react-renderer/themes/vsDark'; 4 | import Loadable from 'react-loadable'; 5 | import LoadingProvider from './loading'; 6 | 7 | /** Removes the last token from a code example if it's empty. */ 8 | function cleanTokens(tokens) { 9 | const tokensLength = tokens.length; 10 | 11 | if (tokensLength === 0) { 12 | return tokens; 13 | } 14 | const lastToken = tokens[tokensLength - 1]; 15 | 16 | if (lastToken.length === 1 && lastToken[0].empty) { 17 | return tokens.slice(0, tokensLength - 1); 18 | } 19 | return tokens; 20 | } 21 | 22 | const LoadableComponent = Loadable({ 23 | loader: () => import('./LiveProvider'), 24 | loading: LoadingProvider, 25 | }); 26 | 27 | /* eslint-disable react/jsx-key */ 28 | const CodeBlock = ({ children: exampleCode, ...props }) => { 29 | if (props['react-live']) { 30 | return ; 31 | } else { 32 | return ( 33 | 34 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 35 |
 36 |             {cleanTokens(tokens).map((line, i) => {
 37 |               let lineClass = {};
 38 | 
 39 |               let isDiff = false;
 40 | 
 41 |               if (line[0] && line[0].content.length && line[0].content[0] === '+') {
 42 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
 43 |                 isDiff = true;
 44 |               } else if (line[0] && line[0].content.length && line[0].content[0] === '-') {
 45 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
 46 |                 isDiff = true;
 47 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '+') {
 48 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
 49 |                 isDiff = true;
 50 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '-') {
 51 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
 52 |                 isDiff = true;
 53 |               }
 54 |               const lineProps = getLineProps({ line, key: i });
 55 | 
 56 |               lineProps.style = lineClass;
 57 |               const diffStyle = {
 58 |                 userSelect: 'none',
 59 |                 MozUserSelect: '-moz-none',
 60 |                 WebkitUserSelect: 'none',
 61 |               };
 62 | 
 63 |               let splitToken;
 64 | 
 65 |               return (
 66 |                 
67 | {line.map((token, key) => { 68 | if (isDiff) { 69 | if ( 70 | (key === 0 || key === 1) & 71 | (token.content.charAt(0) === '+' || token.content.charAt(0) === '-') 72 | ) { 73 | if (token.content.length > 1) { 74 | splitToken = { 75 | types: ['template-string', 'string'], 76 | content: token.content.slice(1), 77 | }; 78 | const firstChar = { 79 | types: ['operator'], 80 | content: token.content.charAt(0), 81 | }; 82 | 83 | return ( 84 | 85 | 89 | 90 | 91 | ); 92 | } else { 93 | return ; 94 | } 95 | } 96 | } 97 | return ; 98 | })} 99 |
100 | ); 101 | })} 102 |
103 | )} 104 |
105 | ); 106 | } 107 | }; 108 | 109 | export default CodeBlock; 110 | -------------------------------------------------------------------------------- /src/components/mdxComponents/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import CodeBlock from './codeBlock'; 5 | import AnchorTag from './anchor'; 6 | 7 | const StyledPre = styled('pre')` 8 | padding: 16px; 9 | background: ${props => props.theme.colors.preFormattedText}; 10 | `; 11 | 12 | export default { 13 | h1: props => ( 14 |

15 | ), 16 | h2: props => ( 17 |

18 | ), 19 | h3: props => ( 20 |

21 | ), 22 | h4: props => ( 23 |

24 | ), 25 | h5: props => ( 26 |

27 | ), 28 | h6: props => ( 29 |
30 | ), 31 | p: props =>

, 32 | pre: props => ( 33 | 34 |

35 |     
36 |   ),
37 |   code: CodeBlock,
38 |   // a: AnchorTag,
39 |   // TODO add `img`
40 |   // TODO add `blockquote`
41 |   // TODO add `ul`
42 |   // TODO add `li`
43 |   // TODO add `table`
44 | };
45 | 


--------------------------------------------------------------------------------
/src/components/mdxComponents/loading.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | 
3 | const LoadingProvider = ({ ...props }) => {
4 |   return 
; 5 | }; 6 | 7 | export default LoadingProvider; 8 | -------------------------------------------------------------------------------- /src/components/rightSidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StaticQuery, graphql } from 'gatsby'; 3 | 4 | // import Link from './link'; 5 | import config from '../../config'; 6 | import { Sidebar, ListItem } from './styles/Sidebar'; 7 | 8 | const SidebarLayout = ({ location }) => ( 9 | { 25 | let navItems = []; 26 | 27 | let finalNavItems; 28 | 29 | if (allMdx.edges !== undefined && allMdx.edges.length > 0) { 30 | const navItems = allMdx.edges.map((item, index) => { 31 | let innerItems; 32 | 33 | if (item !== undefined) { 34 | if ( 35 | item.node.fields.slug === location.pathname || 36 | config.gatsby.pathPrefix + item.node.fields.slug === location.pathname 37 | ) { 38 | if (item.node.tableOfContents.items) { 39 | innerItems = item.node.tableOfContents.items.map((innerItem, index) => { 40 | const itemId = innerItem.title 41 | ? innerItem.title.replace(/\s+/g, '').toLowerCase() 42 | : '#'; 43 | 44 | return ( 45 | 46 | {innerItem.title} 47 | 48 | ); 49 | }); 50 | } 51 | } 52 | } 53 | if (innerItems) { 54 | finalNavItems = innerItems; 55 | } 56 | }); 57 | } 58 | 59 | if (finalNavItems && finalNavItems.length) { 60 | return ( 61 | 62 |
    63 |
  • CONTENTS
  • 64 | {finalNavItems} 65 |
66 |
67 | ); 68 | } else { 69 | return ( 70 | 71 |
    72 |
    73 | ); 74 | } 75 | }} 76 | /> 77 | ); 78 | 79 | export default SidebarLayout; 80 | -------------------------------------------------------------------------------- /src/components/sidebar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tree from './tree'; 3 | import { StaticQuery, graphql } from 'gatsby'; 4 | import styled from '@emotion/styled'; 5 | import { ExternalLink } from 'react-feather'; 6 | import config from '../../../config'; 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | const ListItem = styled(({ className, active, level, ...props }) => { 10 | return ( 11 |
  • 12 | 13 | {props.children} 14 | 15 |
  • 16 | ); 17 | })` 18 | list-style: none; 19 | 20 | a { 21 | color: #5c6975; 22 | text-decoration: none; 23 | font-weight: ${({ level }) => (level === 0 ? 700 : 400)}; 24 | padding: 0.45rem 0 0.45rem ${props => 2 + (props.level || 0) * 1}rem; 25 | display: block; 26 | position: relative; 27 | 28 | &:hover { 29 | color: #1ed3c6 !important; 30 | } 31 | 32 | ${props => 33 | props.active && 34 | ` 35 | // color: #663399; 36 | border-color: rgb(230,236,241) !important; 37 | border-style: solid none solid solid; 38 | border-width: 1px 0px 1px 1px; 39 | background-color: #fff; 40 | `} // external link icon 41 | svg { 42 | float: right; 43 | margin-right: 1rem; 44 | } 45 | } 46 | `; 47 | 48 | const Sidebar = styled('aside')` 49 | width: 100%; 50 | height: 100vh; 51 | overflow: auto; 52 | position: fixed; 53 | padding-left: 0px; 54 | position: -webkit-sticky; 55 | position: -moz-sticky; 56 | position: sticky; 57 | top: 0; 58 | padding-right: 0; 59 | -webkit-box-shadow: -1px 0px 4px 1px rgba(175, 158, 232, 0.4); 60 | 61 | @media only screen and (max-width: 1023px) { 62 | width: 100%; 63 | /* position: relative; */ 64 | height: 100vh; 65 | } 66 | 67 | @media (min-width: 767px) and (max-width: 1023px) { 68 | padding-left: 0; 69 | } 70 | 71 | @media only screen and (max-width: 767px) { 72 | padding-left: 0px; 73 | height: auto; 74 | -webkit-box-shadow: none 75 | } 76 | `; 77 | 78 | const Divider = styled(props => ( 79 |
  • 80 |
    81 |
  • 82 | ))` 83 | list-style: none; 84 | padding: 0.5rem 0; 85 | 86 | hr { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | border-bottom: 1px solid #ede7f3; 91 | } 92 | `; 93 | 94 | const SidebarLayout = ({ location }) => ( 95 | { 111 | return ( 112 | 113 | {config.sidebar.title ? ( 114 |
    118 | ) : null} 119 |
      120 | 121 | {config.sidebar.links && config.sidebar.links.length > 0 && } 122 | {config.sidebar.links.map((link, key) => { 123 | if (link.link !== '' && link.text !== '') { 124 | return ( 125 | 126 | {link.text} 127 | 128 | 129 | ); 130 | } 131 | })} 132 |
    133 | 134 | ); 135 | }} 136 | /> 137 | ); 138 | 139 | export default SidebarLayout; 140 | -------------------------------------------------------------------------------- /src/components/sidebar/tree.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import config from '../../../config'; 3 | import TreeNode from './treeNode'; 4 | 5 | const calculateTreeData = edges => { 6 | const originalData = config.sidebar.ignoreIndex 7 | ? edges.filter( 8 | ({ 9 | node: { 10 | fields: { slug }, 11 | }, 12 | }) => slug !== '/' 13 | ) 14 | : edges; 15 | 16 | const tree = originalData.reduce( 17 | ( 18 | accu, 19 | { 20 | node: { 21 | fields: { slug, title }, 22 | }, 23 | } 24 | ) => { 25 | const parts = slug.split('/'); 26 | 27 | let { items: prevItems } = accu; 28 | 29 | const slicedParts = 30 | config.gatsby && config.gatsby.trailingSlash ? parts.slice(1, -2) : parts.slice(1, -1); 31 | 32 | for (const part of slicedParts) { 33 | let tmp = prevItems && prevItems.find(({ label }) => label == part); 34 | 35 | if (tmp) { 36 | if (!tmp.items) { 37 | tmp.items = []; 38 | } 39 | } else { 40 | tmp = { label: part, items: [] }; 41 | prevItems.push(tmp); 42 | } 43 | prevItems = tmp.items; 44 | } 45 | const slicedLength = 46 | config.gatsby && config.gatsby.trailingSlash ? parts.length - 2 : parts.length - 1; 47 | 48 | const existingItem = prevItems.find(({ label }) => label === parts[slicedLength]); 49 | 50 | if (existingItem) { 51 | existingItem.url = slug; 52 | existingItem.title = title; 53 | } else { 54 | prevItems.push({ 55 | label: parts[slicedLength], 56 | url: slug, 57 | items: [], 58 | title, 59 | }); 60 | } 61 | return accu; 62 | }, 63 | { items: [] } 64 | ); 65 | 66 | const { 67 | sidebar: { forcedNavOrder = [] }, 68 | } = config; 69 | 70 | const tmp = [...forcedNavOrder]; 71 | 72 | if (config.gatsby && config.gatsby.trailingSlash) { 73 | } 74 | tmp.reverse(); 75 | return tmp.reduce((accu, slug) => { 76 | const parts = slug.split('/'); 77 | 78 | let { items: prevItems } = accu; 79 | 80 | const slicedParts = 81 | config.gatsby && config.gatsby.trailingSlash ? parts.slice(1, -2) : parts.slice(1, -1); 82 | 83 | for (const part of slicedParts) { 84 | let tmp = prevItems.find(item => item && item.label == part); 85 | 86 | if (tmp) { 87 | if (!tmp.items) { 88 | tmp.items = []; 89 | } 90 | } else { 91 | tmp = { label: part, items: [] }; 92 | prevItems.push(tmp); 93 | } 94 | if (tmp && tmp.items) { 95 | prevItems = tmp.items; 96 | } 97 | } 98 | // sort items alphabetically. 99 | prevItems.map(item => { 100 | item.items = item.items.sort(function(a, b) { 101 | if (a.label < b.label) return -1; 102 | if (a.label > b.label) return 1; 103 | return 0; 104 | }); 105 | }); 106 | const slicedLength = 107 | config.gatsby && config.gatsby.trailingSlash ? parts.length - 2 : parts.length - 1; 108 | 109 | const index = prevItems.findIndex(({ label }) => label === parts[slicedLength]); 110 | 111 | if (prevItems.length) { 112 | accu.items.unshift(prevItems.splice(index, 1)[0]); 113 | } 114 | return accu; 115 | }, tree); 116 | }; 117 | 118 | const Tree = ({ edges }) => { 119 | let [treeData] = useState(() => { 120 | return calculateTreeData(edges); 121 | }); 122 | 123 | const defaultCollapsed = {}; 124 | 125 | treeData.items.forEach(item => { 126 | if (config.sidebar.collapsedNav && config.sidebar.collapsedNav.includes(item.url)) { 127 | defaultCollapsed[item.url] = true; 128 | } else { 129 | defaultCollapsed[item.url] = false; 130 | } 131 | }); 132 | const [collapsed, setCollapsed] = useState(defaultCollapsed); 133 | 134 | const toggle = url => { 135 | setCollapsed({ 136 | ...collapsed, 137 | [url]: !collapsed[url], 138 | }); 139 | }; 140 | 141 | return ( 142 | 148 | ); 149 | }; 150 | 151 | export default Tree; 152 | -------------------------------------------------------------------------------- /src/components/sidebar/treeNode.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import OpenedSvg from '../images/opened'; 3 | import ClosedSvg from '../images/closed'; 4 | import config from '../../../config'; 5 | import Link from '../link'; 6 | 7 | const TreeNode = ({ className = '', setCollapsed, collapsed, url, title, items, ...rest }) => { 8 | const isCollapsed = collapsed[url]; 9 | 10 | const collapse = () => { 11 | setCollapsed(url); 12 | }; 13 | 14 | const hasChildren = items.length !== 0; 15 | 16 | let location; 17 | 18 | if (typeof document != 'undefined') { 19 | location = document.location; 20 | } 21 | const active = 22 | location && (location.pathname === url || location.pathname === config.gatsby.pathPrefix + url); 23 | 24 | const calculatedClassName = `${className} item ${active ? 'active' : ''}`; 25 | 26 | return ( 27 |
  • 28 | {title && ( 29 | 30 | {title} 31 | {!config.sidebar.frontLine && title && hasChildren ? ( 32 | 35 | ) : null} 36 | 37 | )} 38 | 39 | {!isCollapsed && hasChildren ? ( 40 |
      41 | {items.map((item, index) => ( 42 | 48 | ))} 49 |
    50 | ) : null} 51 |
  • 52 | ); 53 | }; 54 | 55 | export default TreeNode; 56 | -------------------------------------------------------------------------------- /src/components/styles/Docs.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const StyledHeading = styled('h1')` 4 | font-size: 32px; 5 | line-height: 1.5; 6 | font-weight: 500; 7 | border-left: 2px solid #1ed3c6; 8 | padding: 0 16px; 9 | flex: 1; 10 | margin-top: 0; 11 | padding-top: 0; 12 | color: ${props => props.theme.colors.heading}; 13 | `; 14 | 15 | export const Edit = styled('div')` 16 | padding: 1rem 1.5rem; 17 | text-align: right; 18 | 19 | a { 20 | font-size: 14px; 21 | font-weight: 500; 22 | line-height: 1em; 23 | text-decoration: none; 24 | color: #555; 25 | border: 1px solid rgb(211, 220, 228); 26 | cursor: pointer; 27 | border-radius: 3px; 28 | transition: all 0.2s ease-out 0s; 29 | text-decoration: none; 30 | color: rgb(36, 42, 49); 31 | background-color: rgb(255, 255, 255); 32 | height: 30px; 33 | padding: 5px 16px; 34 | &:hover { 35 | background-color: rgb(245, 247, 249); 36 | } 37 | } 38 | `; 39 | 40 | export const StyledMainWrapper = styled.div` 41 | max-width: 750px; 42 | color: ${props => props.theme.colors.text}; 43 | 44 | ul, 45 | ol { 46 | -webkit-padding-start: 40px; 47 | -moz-padding-start: 40px; 48 | -o-padding-start: 40px; 49 | margin: 24px 0px; 50 | padding: 0px 0px 0px 2em; 51 | 52 | li { 53 | font-size: 16px; 54 | line-height: 1.8; 55 | font-weight: 400; 56 | } 57 | } 58 | 59 | a { 60 | transition: color 0.15s; 61 | color: ${props => props.theme.colors.link}; 62 | } 63 | 64 | code { 65 | border: 1px solid #ede7f3; 66 | border-radius: 4px; 67 | padding: 2px 6px; 68 | font-size: 0.9375em; 69 | 70 | background: ${props => props.theme.colors.background}; 71 | } 72 | 73 | @media (max-width: 767px) { 74 | padding: 0 15px; 75 | } 76 | `; 77 | -------------------------------------------------------------------------------- /src/components/styles/GlobalStyles.js: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/core'; 2 | 3 | export const baseStyles = css` 4 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'); 5 | @import url('https://fonts.googleapis.com/css?family=Poppins:300,400,500,600&display=swap'); 6 | @import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap'); 7 | * { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | font-display: swap; 12 | } 13 | ::-webkit-input-placeholder { 14 | /* Edge */ 15 | color: #c2c2c2; 16 | } 17 | 18 | :-ms-input-placeholder { 19 | /* Internet Explorer */ 20 | color: #c2c2c2; 21 | } 22 | 23 | ::placeholder { 24 | color: #c2c2c2; 25 | } 26 | html, 27 | body { 28 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Roboto Light', 'Oxygen', 29 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif, 30 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 31 | 32 | font-size: 16px; 33 | scroll-behavior: smooth; 34 | } 35 | 36 | a { 37 | transition: color 0.15s; 38 | /* color: #663399; */ 39 | } 40 | 41 | body { 42 | font-family: 'Montserrat', 'Roboto'; 43 | } 44 | .visibleMobile { 45 | display: none; 46 | } 47 | .visibleMobileView { 48 | display: none !important; 49 | } 50 | .video-responsive { 51 | position: relative; 52 | padding-bottom: 56.2%; 53 | } 54 | a { 55 | text-decoration: none; 56 | } 57 | a:hover { 58 | text-decoration: none; 59 | } 60 | .displayInline { 61 | display: inline-block; 62 | } 63 | .navBarToggle { 64 | border: 0px solid #fff; 65 | border-radius: 4px; 66 | width: 36px; 67 | height: 33px; 68 | position: absolute; 69 | right: 20px; 70 | padding: 8px 5px; 71 | display: none; 72 | } 73 | .navBarToggle .iconBar { 74 | display: block; 75 | width: 22px; 76 | height: 2px; 77 | border-radius: 1px; 78 | margin: 0 auto; 79 | margin-top: 4px; 80 | background-color: #001934; 81 | } 82 | .navBarToggle .iconBar:first-of-type { 83 | margin-top: 0px; 84 | } 85 | .video-responsive iframe { 86 | position: absolute; 87 | width: 100%; 88 | height: 100%; 89 | } 90 | 91 | .diffNewLine { 92 | color: #22863a; 93 | background-color: #f0fff4; 94 | } 95 | 96 | .diffRemoveLine { 97 | color: red; 98 | background-color: #ffcccc; 99 | } 100 | .navBarParent { 101 | width: 100%; 102 | float: left; 103 | display: flex; 104 | align-items: center; 105 | } 106 | .divider { 107 | height: 30px; 108 | margin: 0 15px; 109 | border-right: 1px solid rgba(255, 255, 255, 0.3); 110 | } 111 | .navBarULRight { 112 | /* position: absolute; 113 | right: 30px; */ 114 | } 115 | .githubIcon { 116 | width: 15px; 117 | margin-right: 5px; 118 | } 119 | 120 | .githubSection { 121 | display: flex; 122 | align-items: center; 123 | color: #000; 124 | opacity: 0.7; 125 | } 126 | 127 | .githubSection:hover { 128 | text-decoration: none; 129 | opacity: 1; 130 | } 131 | 132 | .navbar-default .navbar-toggle .icon-bar { 133 | background-color: #fff !important; 134 | } 135 | 136 | .navbar-default .navbar-toggle:focus, 137 | .navbar-default .navbar-toggle:hover { 138 | background-color: #001933; 139 | } 140 | 141 | .headerWrapper { 142 | border-bottom: 1px solid rgb(212, 218, 223); 143 | display: flex; 144 | align-items: center; 145 | } 146 | .formElement { 147 | background-color: transparent; 148 | padding: 4px; 149 | border-radius: 5px; 150 | position: relative; 151 | } 152 | .formElement:focus { 153 | outline: none; 154 | border: none; 155 | } 156 | .formElement svg path { 157 | fill: #2fd2c5; 158 | } 159 | .hitWrapper { 160 | background-color: #fff; 161 | padding: 0.7em 1em 0.4em; 162 | border-radius: 4px; 163 | position: absolute; 164 | width: 80vw; 165 | max-width: 30em; 166 | top: 40px; 167 | border: 1px solid #ccc; 168 | height: auto; 169 | max-height: 80vh; 170 | overflow: scroll; 171 | left: 0; 172 | } 173 | .hitWrapper ul li { 174 | margin-top: 0.7em; 175 | padding-top: 0.7em; 176 | border-top: 1px solid; 177 | list-style-type: none; 178 | } 179 | .hitWrapper ul li:first-of-type { 180 | border-top: 0px; 181 | margin-top: 0px; 182 | color: black !important; 183 | padding: 0px; 184 | } 185 | .showResults { 186 | display: block; 187 | } 188 | .hideResults { 189 | display: none; 190 | } 191 | .hitWrapper span { 192 | color: black; 193 | font-size: 14px; 194 | } 195 | .headerTitle { 196 | height: auto; 197 | font-size: 16px; 198 | line-height: 1.5; 199 | font-weight: 300; 200 | color: #fff !important; 201 | text-transform: uppercase; 202 | } 203 | .headerTitle a { 204 | color: #fff; 205 | } 206 | 207 | .headerTitle a:hover { 208 | text-decoration: none; 209 | opacity: 0.8; 210 | } 211 | .logoWrapper { 212 | padding: 21px 0; 213 | padding-left: 20px; 214 | } 215 | 216 | .logoContent { 217 | font-family: 'Montserrat', 'Roboto'; 218 | margin-left: 16px; 219 | font-size: 28px; 220 | line-height: 1.5; 221 | font-weight: 500; 222 | padding-right: 10px; 223 | } 224 | 225 | /* Header section starts here */ 226 | .removePadd { 227 | padding: 0 !important; 228 | } 229 | .navBarDefault { 230 | background-color: #000b17; 231 | border-radius: 0; 232 | border-top: 0; 233 | margin-bottom: 0; 234 | border: 0; 235 | display: flex; 236 | align-items: center; 237 | z-index: 1; 238 | padding: 15px; 239 | position: relative; 240 | height: 80px; 241 | } 242 | .navBarHeader { 243 | margin-right: auto; 244 | min-width: 335px; 245 | width: 100%; 246 | padding-right: 20px; 247 | display: flex; 248 | align-items: center; 249 | } 250 | .navBarBrand { 251 | padding: 0px 0px; 252 | display: flex; 253 | align-items: center; 254 | } 255 | 256 | .navBarBrand img { 257 | max-width: 120px; 258 | max-height: 60px; 259 | margin-right: 6px; 260 | display: inline-block; 261 | } 262 | .navBarUL li { 263 | list-style-type: none; 264 | } 265 | .navBarUL { 266 | -webkit-overflow-scrolling: touch; 267 | } 268 | .navBarUL li a { 269 | font-family: 'Montserrat', 'Roboto'; 270 | color: #fff !important; 271 | font-size: 16px; 272 | font-weight: 500; 273 | line-height: 1em; 274 | opacity: 1; 275 | padding: 10px 15px; 276 | } 277 | .navBarNav { 278 | display: flex; 279 | align-items: center; 280 | } 281 | .navBarUL li a img, 282 | .navBarUL li a .shareIcon { 283 | width: 20px; 284 | } 285 | .navBarUL li a:hover { 286 | opacity: 0.7; 287 | } 288 | pre { 289 | border: 0 !important; 290 | background-color: rgb(245, 247, 249); /* !important; */ 291 | } 292 | 293 | blockquote { 294 | color: rgb(116, 129, 141); 295 | margin: 0px 0px 24px; 296 | padding: 0px 0px 0px 12px; 297 | border-left: 4px solid rgb(230, 236, 241); 298 | border-color: rgb(230, 236, 241); 299 | } 300 | .socialWrapper { 301 | display: flex; 302 | align-items: center; 303 | } 304 | .socialWrapper li { 305 | display: inline-block; 306 | } 307 | .socialWrapper li a { 308 | display: contents; 309 | } 310 | /* Header section ends here */ 311 | .sidebarTitle { 312 | background-color: #f8f8f8; 313 | padding: 18px 16px; 314 | font-family: 'Poppins'; 315 | font-size: 18px; 316 | font-weight: 600; 317 | color: #001934; 318 | display: flex; 319 | align-items: center; 320 | } 321 | 322 | .sideBarShow { 323 | display: none; 324 | } 325 | 326 | .sidebarTitle a { 327 | color: #001934; 328 | } 329 | 330 | .greenCircle { 331 | width: 8px; 332 | height: 8px; 333 | background-color: #1cd3c6; 334 | border-radius: 50%; 335 | margin: 0 12px; 336 | } 337 | 338 | .headerNav { 339 | font-family: 'Montserrat', 'Roboto'; 340 | padding: 0px 24px; 341 | color: #001933; 342 | font-size: 16px; 343 | font-weight: 500; 344 | line-height: 1em; 345 | } 346 | 347 | .headerNav a { 348 | color: #001933; 349 | text-decoration: none; 350 | } 351 | 352 | .headerNav a:hover { 353 | text-decoration: none; 354 | } 355 | 356 | .logoWrapper img { 357 | width: 40px; 358 | } 359 | 360 | .sideBarUL { 361 | margin-top: 32px; 362 | } 363 | 364 | .sideBarUL li { 365 | list-style-type: none; 366 | width: auto; 367 | } 368 | 369 | .sideBarUL li a { 370 | /* color: #fff; */ 371 | font-size: 14px; 372 | font-weight: 500; 373 | line-height: 1.5; 374 | padding: 7px 24px 7px 16px; 375 | padding-left: 10px; 376 | padding-right: 25px; 377 | border-style: solid none solid solid; 378 | border-width: 1px 0px 1px 1px; 379 | border-color: transparent currentcolor transparent transparent; 380 | } 381 | 382 | .hideFrontLine .collapser { 383 | background: transparent; 384 | border: none; 385 | outline: none; 386 | position: absolute; 387 | right: 20px; 388 | z-index: 1; 389 | cursor: pointer; 390 | height: 12px; 391 | } 392 | 393 | .hideFrontLine .active > a { 394 | background-color: #1ed3c6; 395 | color: #fff !important; 396 | fill: #fff !important; 397 | } 398 | 399 | .firstLevel ul .item ul .item { 400 | border-left: 1px solid #e6ecf1; 401 | } 402 | 403 | .sideBarUL .item { 404 | list-style: none; 405 | padding: 0; 406 | } 407 | 408 | .sideBarUL .item > a { 409 | color: #1ED3C6; 410 | fill: #1ED3C6; 411 | text-decoration: none; 412 | display: flex; 413 | align-items: center; 414 | position: relative; 415 | width: 100%; 416 | padding-right: 35px; 417 | padding-left: 15px; 418 | } 419 | 420 | .showFrontLine .item > a:hover { 421 | background-color: #001933; 422 | } 423 | 424 | .showFrontLine .active > a { 425 | /* color: #fff; */ 426 | background-color: #001933; 427 | } 428 | 429 | .sideBarUL .item .item { 430 | margin-left: 16px; 431 | } 432 | 433 | .firstLevel > ul > .item { 434 | margin-left: 0 !important; 435 | } 436 | 437 | .showFrontLine .item .item { 438 | border-left: 1px solid #e6ecf1; 439 | border-left-color: rgb(230, 236, 241); 440 | padding: 0; 441 | width: calc(100% - 16px) !important; 442 | } 443 | 444 | .showFrontLine .item .active > a { 445 | border-color: rgb(230, 236, 241) !important; 446 | border-style: solid none solid solid; 447 | border-width: 1px 0px 1px 1px; 448 | background-color: #1ed3c6 !important; 449 | color: #fff; 450 | } 451 | 452 | .titleWrapper { 453 | display: flex; 454 | align-items: center; 455 | padding-bottom: 40px; 456 | border-bottom: 1px solid rgb(230, 236, 241); 457 | margin-bottom: 32px; 458 | } 459 | 460 | .gitBtn { 461 | height: 30px; 462 | min-height: 30px; 463 | display: flex; 464 | align-items: center; 465 | } 466 | 467 | .gitBtn img { 468 | width: 15px; 469 | display: inline-block; 470 | margin-right: 5px; 471 | } 472 | 473 | .addPaddTopBottom { 474 | padding: 50px 0; 475 | } 476 | 477 | .preRightWrapper { 478 | display: block; 479 | margin: 0px; 480 | flex: 1 1 0%; 481 | padding: 16px; 482 | text-align: right; 483 | } 484 | 485 | .smallContent { 486 | display: block; 487 | margin: 0px; 488 | padding: 0px; 489 | color: #6e6e6e; 490 | } 491 | 492 | .smallContent span { 493 | font-size: 12px; 494 | line-height: 1.625; 495 | font-weight: 400; 496 | } 497 | 498 | /* **************************** */ 499 | 500 | .nextRightWrapper { 501 | display: block; 502 | margin: 0px; 503 | padding: 16px; 504 | flex: 1 1 0%; 505 | } 506 | 507 | /* tables.css */ 508 | table { 509 | padding: 0; 510 | } 511 | 512 | table tr { 513 | border-top: 1px solid #cccccc; 514 | margin: 0; 515 | padding: 0; 516 | } 517 | 518 | table tr:nth-of-type(2n) { 519 | background-color: #f8f8f8; 520 | } 521 | 522 | table tr th { 523 | font-weight: bold; 524 | border: 1px solid #cccccc; 525 | text-align: left; 526 | margin: 0; 527 | padding: 6px 13px; 528 | } 529 | 530 | table tr td { 531 | border: 1px solid #cccccc; 532 | text-align: left; 533 | margin: 0; 534 | padding: 6px 13px; 535 | } 536 | 537 | table tr th :first-of-type, 538 | table tr td :first-of-type { 539 | margin-top: 0; 540 | } 541 | 542 | table tr th :last-child, 543 | table tr td :last-child { 544 | margin-bottom: 0; 545 | } 546 | /* end - tables.css */ 547 | 548 | /* Image styling */ 549 | img { 550 | max-width: 100%; 551 | } 552 | /* end image */ 553 | .githubBtn { 554 | display: flex; 555 | align-items: center; 556 | font-size: 16px; 557 | padding: 10px 0px; 558 | padding-left: 15px; 559 | max-height: 40px; 560 | } 561 | .githubBtn span span { 562 | display: flex; 563 | align-items: center; 564 | } 565 | 566 | .communitySection { 567 | font-size: 24px; 568 | font-weight: 700; 569 | } 570 | .authorSection { 571 | padding: 20px 0; 572 | } 573 | .authorSection, 574 | .authorName { 575 | display: flex; 576 | align-items: center; 577 | } 578 | .authorImg img { 579 | width: 75px; 580 | height: 75px; 581 | border-radius: 50%; 582 | min-width: 75px; 583 | max-width: 75px; 584 | min-height: 75px; 585 | max-height: 75px; 586 | } 587 | .authorDetails { 588 | padding-left: 10px; 589 | } 590 | .authorDesc { 591 | padding-top: 5px; 592 | font-size: 14px; 593 | } 594 | .authorName img { 595 | margin-left: 10px; 596 | display: inline-block; 597 | width: 20px; 598 | } 599 | .authorName img:hover { 600 | opacity: 0.7; 601 | } 602 | 603 | .heading1 { 604 | font-size: 26px; 605 | font-weight: 800; 606 | line-height: 1.5; 607 | margin-bottom: 16px; 608 | margin-top: 32px; 609 | } 610 | 611 | .heading2 { 612 | font-size: 24px; 613 | font-weight: 700; 614 | line-height: 1.5; 615 | margin-bottom: 16px; 616 | margin-top: 32px; 617 | } 618 | 619 | .heading3 { 620 | font-size: 20px; 621 | font-weight: 600; 622 | line-height: 1.5; 623 | margin-bottom: 16px; 624 | margin-top: 32px; 625 | } 626 | 627 | .heading4 { 628 | font-size: 18px; 629 | font-weight: 500; 630 | line-height: 1.5; 631 | margin-bottom: 16px; 632 | margin-top: 32px; 633 | } 634 | 635 | .heading5 { 636 | font-size: 16px; 637 | font-weight: 400; 638 | line-height: 1.5; 639 | margin-bottom: 16px; 640 | margin-top: 32px; 641 | } 642 | 643 | .heading6 { 644 | font-size: 14px; 645 | font-weight: 300; 646 | line-height: 1.5; 647 | margin-bottom: 16px; 648 | margin-top: 32px; 649 | } 650 | 651 | .paragraph { 652 | margin: 16px 0px 32px; 653 | line-height: 1.625; 654 | } 655 | 656 | .pre { 657 | font-size: 14px; 658 | margin: 0px; 659 | padding: 16px; 660 | overflow: auto; 661 | } 662 | 663 | .poweredBy { 664 | font-size: 0.6em; 665 | text-align: end; 666 | padding: 0; 667 | } 668 | .topnav { 669 | -webkit-transition: top 0.5s, bottom 0.5s; 670 | } 671 | 672 | @media (max-width: 767px) { 673 | .formElement svg path { 674 | fill: #001934; 675 | } 676 | .visibleMobileView { 677 | display: block !important; 678 | } 679 | .socialWrapper { 680 | position: absolute; 681 | right: 10px; 682 | top: 29px; 683 | } 684 | .responsive { 685 | position: relative; 686 | } 687 | .headerTitle { 688 | padding-right: 50px; 689 | font-size: 16px; 690 | } 691 | .navBarBrand { 692 | min-height: 40px; 693 | } 694 | .navBarBrand img { 695 | margin-right: 8px; 696 | } 697 | .topnav.responsive .visibleMobile { 698 | display: block; 699 | } 700 | .topnav .navBarUL { 701 | display: none; 702 | } 703 | .topnav.responsive .navBarUL { 704 | display: block; 705 | text-align: left; 706 | } 707 | .hiddenMobile { 708 | display: none !important; 709 | } 710 | hr { 711 | margin-top: 0; 712 | margin-bottom: 0; 713 | } 714 | .navBarParent { 715 | display: block; 716 | } 717 | .separator { 718 | margin-top: 20px; 719 | margin-bottom: 20px; 720 | } 721 | .navBarULRight { 722 | position: static; 723 | } 724 | .navBarUL { 725 | display: flex; 726 | align-items: center; 727 | margin: 7.5px 0px; 728 | } 729 | .navBarUL li { 730 | height: 37px; 731 | } 732 | .navBarUL li a { 733 | font-size: 14px; 734 | padding: 10px 15px; 735 | } 736 | 737 | .navBarDefault { 738 | display: block; 739 | height: auto; 740 | } 741 | 742 | .navBarToggle { 743 | margin-right: 0; 744 | display: block; 745 | position: absolute; 746 | left: 11px; 747 | top: 15px; 748 | background: #fff; 749 | } 750 | 751 | .navBarHeader { 752 | display: flex; 753 | min-width: auto; 754 | padding-right: 0; 755 | align-items: center; 756 | } 757 | 758 | .navBarBrand { 759 | font-size: 20px; 760 | padding: 0 0; 761 | padding-left: 0; 762 | flex: initial; 763 | padding-right: 15px; 764 | } 765 | 766 | .titleWrapper { 767 | padding: 0 15px; 768 | display: block; 769 | } 770 | 771 | .gitBtn { 772 | display: inline-block; 773 | } 774 | 775 | .mobileView { 776 | text-align: left !important; 777 | padding-left: 0 !important; 778 | } 779 | 780 | .hitWrapper { 781 | width: 100%; 782 | right: 0; 783 | top: 35px; 784 | max-height: fit-content; 785 | position: static; 786 | } 787 | } 788 | 789 | @media (min-width: 768px) and (max-width: 991px) { 790 | .navBarDefault { 791 | padding: 10px; 792 | } 793 | .navBarBrand { 794 | font-size: 22px; 795 | } 796 | .navBarHeader { 797 | min-width: 240px; 798 | flex: initial; 799 | } 800 | .githubBtn { 801 | padding: 10px 10px; 802 | } 803 | .divider { 804 | margin: 0 5px; 805 | height: 20px; 806 | } 807 | .hitWrapper { 808 | max-width: 500px; 809 | } 810 | .navBarUL li a { 811 | padding: 10px 5px; 812 | } 813 | } 814 | 815 | .katex-display > .katex { 816 | display: inline-block; 817 | white-space: nowrap; 818 | max-width: 100%; 819 | overflow-x: auto; 820 | overflow-y: hidden; 821 | text-align: initial; 822 | } 823 | .katex { 824 | font: normal 1.21em KaTeX_Main, Times New Roman, serif; 825 | line-height: 1.2; 826 | white-space: normal; 827 | text-indent: 0; 828 | } 829 | /* Hide scrollbar for Chrome, Safari and Opera */ 830 | .katex::-webkit-scrollbar { 831 | display: none; 832 | } 833 | 834 | /* Hide scrollbar for IE, Edge and Firefox */ 835 | .katex { 836 | -ms-overflow-style: none; /* IE and Edge */ 837 | scrollbar-width: none; /* Firefox */ 838 | } 839 | `; 840 | -------------------------------------------------------------------------------- /src/components/styles/PageNavigationButtons.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const StyledNextPrevious = styled('div')` 4 | margin: 0px; 5 | padding: 0px; 6 | width: auto; 7 | display: grid; 8 | grid-template-rows: auto; 9 | column-gap: 24px; 10 | grid-template-columns: calc(50% - 8px) calc(50% - 8px); 11 | 12 | .previousBtn { 13 | cursor: pointer; 14 | -moz-box-align: center; 15 | -moz-box-direction: normal; 16 | -moz-box-orient: horizontal; 17 | margin: 0px; 18 | padding: 0px; 19 | position: relative; 20 | display: flex; 21 | flex-direction: row; 22 | align-items: center; 23 | place-self: stretch; 24 | border-radius: 3px; 25 | border: 1px solid rgb(230, 236, 241); 26 | transition: border 200ms ease 0s; 27 | text-decoration: none; 28 | 29 | background-color: ${props => props.theme.colors.background}; 30 | color: ${props => props.theme.colors.text}; 31 | } 32 | 33 | .nextBtn { 34 | cursor: pointer; 35 | -moz-box-align: center; 36 | -moz-box-direction: normal; 37 | -moz-box-orient: horizontal; 38 | margin: 0px; 39 | padding: 0px; 40 | position: relative; 41 | display: flex; 42 | flex-direction: row; 43 | align-items: center; 44 | place-self: stretch; 45 | border-radius: 3px; 46 | border: 1px solid rgb(230, 236, 241); 47 | transition: border 200ms ease 0s; 48 | text-decoration: none; 49 | 50 | background-color: ${props => props.theme.colors.background}; 51 | color: ${props => props.theme.colors.text}; 52 | } 53 | 54 | .nextBtn:hover, 55 | .previousBtn:hover { 56 | text-decoration: none; 57 | border: 1px solid #1ed3c6; 58 | } 59 | 60 | .nextBtn:hover .rightArrow, 61 | .previousBtn:hover .leftArrow { 62 | color: #1ed3c6; 63 | } 64 | 65 | .leftArrow { 66 | display: flex; 67 | margin: 0px; 68 | color: rgb(157, 170, 182); 69 | flex: 0 0 auto; 70 | font-size: 24px; 71 | transition: color 200ms ease 0s; 72 | padding: 16px; 73 | padding-right: 16px; 74 | } 75 | 76 | .rightArrow { 77 | display: flex; 78 | flex: 0 0 auto; 79 | font-size: 24px; 80 | transition: color 200ms ease 0s; 81 | padding: 16px; 82 | padding-left: 16px; 83 | margin: 0px; 84 | color: rgb(157, 170, 182); 85 | } 86 | 87 | .nextPreviousTitle { 88 | display: block; 89 | margin: 0px; 90 | padding: 0px; 91 | transition: color 200ms ease 0s; 92 | } 93 | 94 | .nextPreviousTitle span { 95 | font-size: 16px; 96 | line-height: 1.5; 97 | font-weight: 500; 98 | } 99 | 100 | @media (max-width: 767px) { 101 | display: block; 102 | padding: 0 15px; 103 | 104 | .previousBtn { 105 | margin-bottom: 20px; 106 | } 107 | } 108 | `; 109 | -------------------------------------------------------------------------------- /src/components/styles/Sidebar.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Sidebar = styled('aside')` 4 | width: 100%; 5 | border-right: 1px solid #ede7f3; 6 | height: 100vh; 7 | overflow: auto; 8 | position: fixed; 9 | padding-left: 24px; 10 | position: -webkit-sticky; 11 | position: -moz-sticky; 12 | position: sticky; 13 | top: 0; 14 | 15 | background: ${props => props.theme.colors.background}; 16 | 17 | .rightSideTitle { 18 | font-size: 10px; 19 | line-height: 1; 20 | font-weight: 700; 21 | text-transform: uppercase; 22 | letter-spacing: 1.2px; 23 | padding: 7px 24px 7px 16px; 24 | border-left: 1px solid #e6ecf1; 25 | border-left-color: rgb(230, 236, 241); 26 | 27 | color: ${props => props.theme.colors.text}; 28 | } 29 | 30 | .rightSideBarUL { 31 | margin-top: 32px; 32 | } 33 | 34 | .rightSideBarUL li { 35 | list-style-type: none; 36 | border-left: 1px solid #e6ecf1; 37 | border-left-color: rgb(230, 236, 241); 38 | } 39 | 40 | .rightSideBarUL li a { 41 | font-size: 12px; 42 | font-weight: 500; 43 | line-height: 1.5; 44 | padding: 7px 24px 7px 16px; 45 | 46 | color: ${props => props.theme.colors.text}; 47 | } 48 | 49 | @media only screen and (max-width: 50rem) { 50 | width: 100%; 51 | position: relative; 52 | } 53 | `; 54 | 55 | export const ListItem = styled(({ className, active, level, ...props }) => { 56 | return ( 57 |
  • 58 | 59 | {props.children} 60 | 61 |
  • 62 | ); 63 | })` 64 | list-style: none; 65 | 66 | a { 67 | color: #5c6975; 68 | text-decoration: none; 69 | font-weight: ${({ level }) => (level === 0 ? 700 : 400)}; 70 | padding: 0.45rem 0 0.45rem ${props => 2 + (props.level || 0) * 1}rem; 71 | display: block; 72 | position: relative; 73 | 74 | &:hover { 75 | color: #1ed3c6 !important; 76 | } 77 | 78 | ${props => 79 | props.active && 80 | ` 81 | color: #1ED3C6; 82 | border-color: rgb(230,236,241) !important; 83 | border-style: solid none solid solid; 84 | border-width: 1px 0px 1px 1px; 85 | background-color: #fff; 86 | `} // external link icon 87 | svg { 88 | float: right; 89 | margin-right: 1rem; 90 | } 91 | } 92 | `; 93 | -------------------------------------------------------------------------------- /src/components/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fonts: { 3 | mono: '"SF Mono", "Roboto Mono", Menlo, monospace', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/theme/index.js: -------------------------------------------------------------------------------- 1 | import "katex/dist/katex.min.css" 2 | 3 | const baseTheme = { 4 | fonts: { 5 | mono: '"SF Mono", "Roboto Mono", Menlo, monospace', 6 | }, 7 | }; 8 | 9 | const lightTheme = { 10 | ...baseTheme, 11 | colors: { 12 | background: '#fff', 13 | heading: '#000', 14 | text: '#3B454E', 15 | preFormattedText: 'rgb(245, 247, 249)', 16 | link: '#1000EE', 17 | }, 18 | }; 19 | 20 | const darkTheme = { 21 | ...baseTheme, 22 | colors: { 23 | background: '#001933', 24 | heading: '#fff', 25 | text: '#fff', 26 | preFormattedText: '#000', 27 | link: '#1ED3C6', 28 | }, 29 | }; 30 | 31 | export { lightTheme, darkTheme }; 32 | -------------------------------------------------------------------------------- /src/components/theme/themeProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ThemeProvider as EmotionThemeProvider } from 'emotion-theming'; 3 | import { Global } from '@emotion/core'; 4 | 5 | import { lightTheme, darkTheme } from './index'; 6 | import Header from '../Header'; 7 | import { baseStyles } from '../styles/GlobalStyles'; 8 | 9 | class ThemeProvider extends React.Component { 10 | state = { 11 | isDarkThemeActive: false, 12 | }; 13 | 14 | componentDidMount() { 15 | this.retrieveActiveTheme(); 16 | } 17 | 18 | retrieveActiveTheme = () => { 19 | const isDarkThemeActive = JSON.parse(window.localStorage.getItem('isDarkThemeActive')); 20 | 21 | this.setState({ isDarkThemeActive }); 22 | }; 23 | 24 | toggleActiveTheme = () => { 25 | this.setState(prevState => ({ isDarkThemeActive: !prevState.isDarkThemeActive })); 26 | 27 | window.localStorage.setItem('isDarkThemeActive', JSON.stringify(!this.state.isDarkThemeActive)); 28 | }; 29 | 30 | render() { 31 | const { children, location } = this.props; 32 | 33 | const { isDarkThemeActive } = this.state; 34 | 35 | const currentActiveTheme = isDarkThemeActive ? darkTheme : lightTheme; 36 | 37 | return ( 38 |
    39 | 40 |
    45 | {children} 46 |
    47 | ); 48 | } 49 | } 50 | 51 | export default ThemeProvider; 52 | -------------------------------------------------------------------------------- /src/components/themeProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ThemeProvider as EmotionThemeProvider } from 'emotion-theming'; 3 | import { default as defaultTheme } from './theme'; 4 | import Header from './Header'; 5 | 6 | export default function ThemeProvider({ children, theme = {}, location }) { 7 | return ( 8 |
    9 |
    10 | {children} 11 |
    12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/custom-sw-code.js: -------------------------------------------------------------------------------- 1 | workbox.routing.registerRoute( 2 | new RegExp('https:.*min.(css|js)'), 3 | workbox.strategies.staleWhileRevalidate({ 4 | cacheName: 'cdn-cache', 5 | }) 6 | ); 7 | -------------------------------------------------------------------------------- /src/html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import config from '../config'; 4 | 5 | export default class HTML extends React.Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | {config.siteMetadata.title ? ( 14 | 15 | ) : null } 16 | {config.siteMetadata.title ? ( 17 | {config.siteMetadata.title} 18 | ) : null } 19 | {config.siteMetadata.ogImage ? ( 20 | 21 | ) : null} 22 | 23 | {this.props.headComponents} 24 | 25 | 26 | {this.props.preBodyComponents} 27 |
    28 | {this.props.postBodyComponents} 29 |