<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yann Bertrand</title>
    <description>The latest articles on DEV Community by Yann Bertrand (@yannbertrand).</description>
    <link>https://dev.to/yannbertrand</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F240327%2Fb3e20b27-f584-4d53-9f34-597b4f543893.jpg</url>
      <title>DEV Community: Yann Bertrand</title>
      <link>https://dev.to/yannbertrand</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yannbertrand"/>
    <language>en</language>
    <item>
      <title>ctrl + tab everywhere</title>
      <dc:creator>Yann Bertrand</dc:creator>
      <pubDate>Mon, 21 Mar 2022 15:56:22 +0000</pubDate>
      <link>https://dev.to/yannbertrand/cmdtab-everywhere-1593</link>
      <guid>https://dev.to/yannbertrand/cmdtab-everywhere-1593</guid>
      <description>&lt;p&gt;When using the &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;tab&lt;/code&gt; shortcut, the default browser setting is to switch tab to the next one in order.&lt;/p&gt;

&lt;p&gt;I like it a lot but it's not default in other tools I use a lot as a developer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;tab&lt;/code&gt; in Visual Studio Code
&lt;/h1&gt;

&lt;p&gt;In VSCode we'll have to tweak the keyboard shortcuts preferences. I prefer to edit them in JSON by typing &lt;code&gt;⌘ cmd&lt;/code&gt; + &lt;code&gt;⇧ shift&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;Preferences: Open Keyboard Shortcuts (JSON)&lt;/code&gt; and then adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
        "key": "ctrl+tab",
        "command": "workbench.action.nextEditorInGroup"
    },
    {
        "key": "ctrl+shift+tab",
        "command": "workbench.action.previousEditorInGroup"
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will allow us to navigate through grouped tabs with our browser shortcuts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;tab&lt;/code&gt; in iTerm2
&lt;/h1&gt;

&lt;p&gt;(First never forget to use the &lt;code&gt;Natural Text Editing&lt;/code&gt; key mapping preset. I don't understand why it's not default)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fsbteRXM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1j20yhb1eoklitpjg10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fsbteRXM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1j20yhb1eoklitpjg10.png" alt="" width="880" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To handle &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;tab&lt;/code&gt; correctly, you'll have to tweak the "Keys" tab and replace the key bindings we're looking for to switch to &lt;code&gt;Previous Tab&lt;/code&gt;/&lt;code&gt;Next Tab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SrPZRzt3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb5bz6stdpqna7ai7nbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SrPZRzt3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb5bz6stdpqna7ai7nbd.png" alt="" width="880" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tada we're done with it! 🎉&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Public APIs situation</title>
      <dc:creator>Yann Bertrand</dc:creator>
      <pubDate>Thu, 17 Mar 2022 06:52:05 +0000</pubDate>
      <link>https://dev.to/yannbertrand/public-apis-situation-4101</link>
      <guid>https://dev.to/yannbertrand/public-apis-situation-4101</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/public-apis/public-apis" rel="noopener noreferrer"&gt;Public APIs&lt;/a&gt; is an Open Source list of free and publicly available APIs with more than 1,400 entries from the personal side project to FAANG companies.&lt;/p&gt;

&lt;p&gt;Created in 2015 by Todd Motto, the project quickly grew up as a reference for developers. In 2016 Todd asked the community if some people were interested in maintaining the repository. I was one of the few lucky people who joined the project. Todd migrated the project to a new organization and let us do our maintainer job.&lt;/p&gt;

&lt;p&gt;Public APIs is now one of the most starred project on GitHub beside the React and Vue frameworks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle5dp565diezfw4r900j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle5dp565diezfw4r900j.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One issue we've been having with the project is that we maintainers don't have access to the settings. That means we can't add maintainers, update the description or labels. We don't have access to the GitHub Organization at all.&lt;/p&gt;

&lt;p&gt;I personally learnt &lt;a href="https://github.com/public-apis/public-apis/issues/1401" rel="noopener noreferrer"&gt;Todd was not the owner anymore&lt;/a&gt; in 2020 as I was struggling with &lt;a href="https://github.com/public-apis/public-apis/issues/1268" rel="noopener noreferrer"&gt;the Hacktoberfest flow of incoming PRs&lt;/a&gt;. After finding a way to contact the owners they finally added some more motivated maintainers during March 2021 (that have been doing a great job since then - thanks a lot guys). They also upgraded our rights but none of us are still able to add maintainers.&lt;/p&gt;

&lt;p&gt;Turned out the owners built a company about APIs, it's called APILayer.&lt;/p&gt;

&lt;p&gt;In May 2021, &lt;strong&gt;they replaced the GitHub description of the project to add links to their company.&lt;/strong&gt; A few times they tried to &lt;strong&gt;push their own commercial APIs to the top of the list&lt;/strong&gt;, breaking the build (as we require alphabetically ordered links) and the guidelines as the point of the project is to list free APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2mh1hj2dna6x9nqlf6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2mh1hj2dna6x9nqlf6b.png"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;I reverted their changes and contacted them to explain the issue. I didn't have any answer from APILayer since then.&lt;/p&gt;

&lt;p&gt;This Friday March 11th 2022 they once again broke the rules, by &lt;strong&gt;removing a sponsor from the project and add their own logo plus link to their company&lt;/strong&gt;. I reverted it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqodpgdtkgehxaiyok91o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqodpgdtkgehxaiyok91o.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Saturday March 12th 2022 they crossed the line by &lt;strong&gt;downgrading our maintainers rights&lt;/strong&gt; without notice.&lt;/p&gt;

&lt;p&gt;On Wednesday March 16th they removed the maintainers list from the readme and added one of their business API again.&lt;/p&gt;

&lt;p&gt;It's now clear APILayer don't care about the project nor Open Source, their only goal is to take advantage of the large number of pageviews the project gets to redirect to their own company.&lt;/p&gt;

&lt;p&gt;The team is still discussing what we should do to keep the project going. Please be careful with the project content now because we cannot ensure it's safe.&lt;/p&gt;




&lt;p&gt;Other references :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matheus wrote about the situation &lt;a href="https://github.com/public-apis/public-apis/issues/3104" rel="noopener noreferrer"&gt;in the repo issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>🖥️🎥 Automated screen recording with JavaScript</title>
      <dc:creator>Yann Bertrand</dc:creator>
      <pubDate>Wed, 07 Oct 2020 18:29:19 +0000</pubDate>
      <link>https://dev.to/yannbertrand/automated-screen-recording-with-javascript-18he</link>
      <guid>https://dev.to/yannbertrand/automated-screen-recording-with-javascript-18he</guid>
      <description>&lt;p&gt;When I built the &lt;a href="https://github.com/yannbertrand/macos-defaults/tree/master/record#readme"&gt;macOS &lt;code&gt;defaults&lt;/code&gt; recording feature&lt;/a&gt;, I wanted to automate the maintainer work as much as possible, meaning that I want to script the screenshots and recordings, and rerun them when a new macOS version comes out. I found two packages that helped me a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Record the entire Mac screen using &lt;a href="https://github.com/wulkano/aperture-node"&gt;aperture-node&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/octalmage/robotjs"&gt;robot.js&lt;/a&gt; to move the mouse and use the keyboard programmatically (should work on any OS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how I used them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aperture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aperture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;robot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;robotjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;compressVideo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Action!&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;highlightClicks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cropArea&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouseSmooth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouseSmooth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouseSmooth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tmpRecordingPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;// End recording&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;compressVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpRecordingPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressVideoError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressVideoError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ggtmCRP1Idw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let's explain what happens here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The robot.js &lt;code&gt;moveMouse&lt;/code&gt; method... move the mouse. It does it directly with no delay.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;x&lt;/code&gt; value is set from the left border of the screen. The &lt;code&gt;y&lt;/code&gt; value is from the top border.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveMouseSmooth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;moveMouseSmooth&lt;/code&gt; do it "human-like". It's not perfect but it's good enough. The 3rd parameter adjusts the speed of the mouse movement.&lt;/p&gt;

&lt;p&gt;To make sure last action is ended before doing another one, I add some delay between actions using &lt;a href="https://github.com/sindresorhus/delay"&gt;delay&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Other robot.js methods I've been using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getScreenSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;command&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shift&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Simple as that!&lt;/p&gt;

&lt;p&gt;Let's move forward to Aperture.&lt;/p&gt;

&lt;p&gt;Aperture is a low level Swift script that uses the &lt;a href="https://developer.apple.com/av-foundation/"&gt;AVFoundation framework&lt;/a&gt; with great performances. It was built to fulfil an Open Source screen recorder needs called &lt;a href="https://github.com/wulkano/kap"&gt;Kap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Node API is pretty straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cropArea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;recordWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recordWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recordHeight&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;highlightClicks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The cropArea &lt;code&gt;x&lt;/code&gt; value is set from the left border of the screen. The &lt;code&gt;y&lt;/code&gt; value from &lt;strong&gt;the bottom border&lt;/strong&gt;. I had to be careful about that, as it's not the same referential as robot.js!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tmpRecordingPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;//=&amp;gt; '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stopRecording&lt;/code&gt; method gives us a path where the video is saved.&lt;/p&gt;

&lt;p&gt;Then we can post process our screen recording. In my case, I built a method to resize, compress and move it to another folder.&lt;/p&gt;

&lt;p&gt;Unfortunately, I didn't find a robust solution for resolution enforcement. So I can't guarantee the results are 100% the same on different setups.&lt;/p&gt;

&lt;p&gt;That's it! Isn't it simple? Let me know what you think in the comment section 🙂&lt;/p&gt;

&lt;p&gt;If you wanna go deeper in automated screen recording, take a look at &lt;a href="https://github.com/yannbertrand/macos-defaults/tree/master/record#readme"&gt;macOS defaults recorder&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>mac</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Visualize your 2019 physical activity in a Grafana dashboard</title>
      <dc:creator>Yann Bertrand</dc:creator>
      <pubDate>Tue, 31 Dec 2019 14:52:38 +0000</pubDate>
      <link>https://dev.to/yannbertrand/visualize-your-2019-physical-activity-in-a-grafana-dashboard-31fb</link>
      <guid>https://dev.to/yannbertrand/visualize-your-2019-physical-activity-in-a-grafana-dashboard-31fb</guid>
      <description>&lt;p&gt;I've been owning an &lt;strong&gt;Apple Watch&lt;/strong&gt; for more than a year now. It's a really impressive device, I've found myself recording all the Workouts I did pretty naturally.&lt;/p&gt;

&lt;p&gt;Unfortunately, Apple's Activity app is far from ideal when you want to play with your data and display &lt;strong&gt;custom charts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I decided to try to improve that by downloading my data and put it in a dedicated product for &lt;strong&gt;data visualization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z9bnFo_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xv2gof96xkjdy5q1ifex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z9bnFo_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xv2gof96xkjdy5q1ifex.png" alt="Visualizing my activities by type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Talking about it with my colleagues, they showed me &lt;strong&gt;Grafana&lt;/strong&gt; and &lt;strong&gt;InfluxDB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As I'm working daily with JavaScript and I wanted to build something quick, I went with &lt;strong&gt;Node.js&lt;/strong&gt; to parse the raw data from my Apple Watch and insert it inside the InfluxDB database which is directly read by Grafana.&lt;/p&gt;

&lt;p&gt;It was the first time I had to work with Node streams since the file to parse is huge. It was quite a challenge but exciting as well!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZaqAESjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xr50h3axqgqw17lgo4g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZaqAESjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xr50h3axqgqw17lgo4g2.png" alt="Vizualising my bicycle activity through 2019"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had a lot of fun building it and it feels good to do something I find useful 😁&lt;/p&gt;

&lt;p&gt;Hopefully, you'll like it to.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please &lt;a href="https://github.com/yannbertrand/apple-watch-workouts-year-review"&gt;create your own dashboard&lt;/a&gt; and show it to me!&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yannbertrand"&gt;
        yannbertrand
      &lt;/a&gt; / &lt;a href="https://github.com/yannbertrand/apple-watch-workouts-year-review"&gt;
        apple-watch-workouts-year-review
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Visualize your 2019 activity in a Grafana dashboard
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Welcome to Apple Watch Workouts year review 👋
&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://travis-ci.org/yannbertrand/apple-watch-workouts-year-review" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/75d30834f0d90a8fd420cfd67b0c46c5678b2e7b/68747470733a2f2f7472617669732d63692e6f72672f79616e6e6265727472616e642f6170706c652d77617463682d776f726b6f7574732d796561722d7265766965772e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/82d759d665d52db153f8f2e0a1f76bc732b6f9a3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e312e302d626c75652e7376673f63616368655365636f6e64733d32353932303030"&gt;&lt;img src="https://camo.githubusercontent.com/82d759d665d52db153f8f2e0a1f76bc732b6f9a3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e312e302d626c75652e7376673f63616368655365636f6e64733d32353932303030" alt="Version"&gt;&lt;/a&gt;
&lt;a href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./license"&gt;&lt;img src="https://camo.githubusercontent.com/3ccf4c50a1576b0dd30b286717451fa56b783512/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://twitter.com/_YannBertrand" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/084150ab08a9aafdfb7568ee0a914583e0e6951b/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f5f59616e6e4265727472616e642e7376673f7374796c653d736f6369616c" alt="Twitter: _YannBertrand"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stability: 1 - Experimental&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Visualize your Apple Watch workouts of the year in a Grafana dashboard&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
✨ Demo&lt;/h2&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/summary.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ydnmm-q2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/summary.png" alt="Summary example screenshot"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/types.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J_56rNGT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/types.png" alt="Types example screenshot"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/bicycle.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ij1FLOzw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/bicycle.png" alt="Bicycle example screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
🏗 Install&lt;/h2&gt;
&lt;p&gt;Clone the repo using Git. You'll need Docker, Node.js (tested with v12) and Yarn (tested with v1.21.1).&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;yarn install&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
🚀 Usage&lt;/h2&gt;
&lt;p&gt;The project runs an InfluxDB database and a Grafana instance, launch them with docker-compose:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;docker-compose up -d&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the meantime, export your Apple Watch data from your iPhone. In the Health App:&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-1.jpeg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MB_DKf-d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-1.jpeg" alt="Click on your picture"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-2.jpeg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wyJqWLIh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-2.jpeg" alt="Export All Health Data"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-3.jpeg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1ycTKaT7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/yannbertrand/apple-watch-workouts-year-review/master/readme.md/./images/export-step-3.jpeg" alt="Export"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Send the zip file to your computer.&lt;/p&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;yarn start &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;path_to_export.zip&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Grab a coffee while your data is read and inserted in InfluxDB (my data takes more than a minute to be loaded)!&lt;/p&gt;
&lt;p&gt;📈 Once the script ends, your Grafana dashboard should be available at &lt;a href="http://localhost:3000/d/apple-watch-workouts/year-dashboard?orgId=1" rel="nofollow"&gt;http://localhost:3000/d/apple-watch-workouts/year-dashboard?orgId=1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This dashboard is just a proof of concept&lt;/strong&gt;, take full advantage of Grafana, try to edit the panels…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yannbertrand/apple-watch-workouts-year-review"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>node</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
