<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Angelos Orfanakos]]></title><description><![CDATA[Technical blog of Angelos Orfanakos]]></description><link>https://angelos.dev/</link><generator>GatsbyJS</generator><lastBuildDate>Tue, 02 Sep 2025 06:25:31 GMT</lastBuildDate><atom:link href="https://angelos.dev/feed.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[Restore last working directory in Bash]]></title><description><![CDATA[<p>After switching from <a href="https://ohmyz.sh/">Zsh</a> to <a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash</a>, I missed the ability to restore the last working directory when opening a new terminal. So I came up with my own, simple implementation.</p>
<p>The contents of <code class="language-text">restore_last_dir.sh</code>:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function-name function">save_last_dir</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token builtin class-name">echo</span> <span class="token string">"BASH_LAST_DIR=<span class="token entity" title="\&quot;">\"</span><span class="token environment constant">$PWD</span><span class="token entity" title="\&quot;">\"</span>"</span> <span class="token operator">></span>~/.bash_lastdir
<span class="token punctuation">}</span>

<span class="token builtin class-name">trap</span> save_last_dir EXIT

<span class="token keyword">function</span> <span class="token function-name function">cd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token builtin class-name">builtin</span> <span class="token builtin class-name">cd</span> <span class="token string">"<span class="token variable">$@</span>"</span> <span class="token operator">></span>/dev/null
  save_last_dir
<span class="token punctuation">}</span>

<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-f</span> ~/.bash_lastdir <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>
  <span class="token builtin class-name">source</span> ~/.bash_lastdir <span class="token operator">&amp;&amp;</span>
  <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-d</span> <span class="token string">"<span class="token variable">$BASH_LAST_DIR</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>
  <span class="token builtin class-name">cd</span> <span class="token string">"<span class="token variable">$BASH_LAST_DIR</span>"</span></code></pre></div>
<p>To use it, you simply have to insert the following line in your <code class="language-text">~/.bashrc</code>:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">source</span> ~/path/to/restore_last_dir.sh</code></pre></div>]]></description><link>https://angelos.dev//2025/06/restore-last-working-directory-in-bash/</link><guid isPermaLink="false">https://angelos.dev//2025/06/restore-last-working-directory-in-bash/</guid><pubDate>Sun, 15 Jun 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[EsriTileLayer component for React Leaflet]]></title><description><![CDATA[<p><a href="https://react-leaflet.js.org/">React Leaflet</a> is a <a href="https://reactjs.org/">React</a> library that exposes <a href="https://leafletjs.com/">Leaflet</a> classes as
React components, making it very easy to add interactive maps to React web apps.</p>
<p>This is the fifth in a <a href="/2021/10/go-to-coordinates-component-for-react-leaflet/">series of posts</a> on how I use the library. My
intention is to share back some of the things I’ve learned and implemented in
the hope of them being useful to others.</p>
<p>In this post, I present the <code class="language-text">EsriTileLayer</code> component which exposes <a href="https://www.esri.com/">Esri</a>
tile layers by wrapping <a href="https://github.com/Esri/esri-leaflet">esri-leaflet</a> as a React component.</p>
<p>First, the component:</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber 0" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createLayerComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@react-leaflet/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> <span class="token constant">L</span> <span class="token keyword">from</span> <span class="token string">'leaflet'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> esri <span class="token keyword">from</span> <span class="token string">'esri-leaflet'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> PropTypes <span class="token keyword">from</span> <span class="token string">'prop-types'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">createEsriTileLayer</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> imagery <span class="token operator">=</span> esri<span class="token punctuation">.</span><span class="token function">tiledMapLayer</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span>    <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'</span><span class="token punctuation">,</span>
    <span class="token operator">...</span>props
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> labels <span class="token operator">=</span> esri<span class="token punctuation">.</span><span class="token function">tiledMapLayer</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span>    <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer'</span><span class="token punctuation">,</span>
    <span class="token operator">...</span>props
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> group <span class="token operator">=</span> <span class="token constant">L</span><span class="token punctuation">.</span><span class="token function">layerGroup</span><span class="token punctuation">(</span><span class="token punctuation">[</span>imagery<span class="token punctuation">,</span> labels<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">instance</span><span class="token operator">:</span> group<span class="token punctuation">,</span> context <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> EsriTileLayer <span class="token operator">=</span> <span class="token function">createLayerComponent</span><span class="token punctuation">(</span>createEsriTileLayer<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> EsriTileLayer<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 7: Satellite imagery tile layer</li>
<li>Line 11: Place labels overlay tile layer</li>
<li>Line 15: Both layers are combined into a single one</li>
<li>Line 6, 9, 13: Any <code class="language-text">props</code> passed to the component (e.g. <code class="language-text">token</code>) are forwarded to each tile layer</li>
</ul>
<p>And here’s how you’d use it in a map:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> EsriTileLayer <span class="token keyword">from</span> <span class="token string">'./EsriTileLayer'</span><span class="token punctuation">;</span>
<span class="token comment">// import EsriTileLayer from './EsriTileLayerWrapper';</span>

<span class="token keyword">const</span> <span class="token constant">GREECE_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">MyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MapContainer</span></span>
      <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">GREECE_BOUNDS</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">'100vh'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
    <span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">EsriTileLayer</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">MapContainer</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MyMap<span class="token punctuation">;</span></code></pre></div>
<p>Since <a href="https://github.com/Esri/esri-leaflet">esri-leaflet</a> is supposed to be used in the browser and breaks with frameworks that perform <a href="https://developer.mozilla.org/en-US/docs/Glossary/SSG">SSG</a> like <a href="https://www.gatsbyjs.com/">Gatsby</a>, I had to wrap it in the following component to make it work with <code class="language-text">gatsby build</code>:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">EsriTileLayerWrapper</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>Component<span class="token punctuation">,</span> setComponent<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token string">'./EsriTileLayer'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">mod</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token function">setComponent</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> mod<span class="token punctuation">.</span>default<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> Component <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Component</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> EsriTileLayerWrapper<span class="token punctuation">;</span></code></pre></div>
<p>To use this component, you simply replace <code class="language-text">EsriTileLayer</code> with <code class="language-text">EsriTileLayerWrapper</code>.</p>]]></description><link>https://angelos.dev//2025/05/esri-tile-layer-component-for-react-leaflet/</link><guid isPermaLink="false">https://angelos.dev//2025/05/esri-tile-layer-component-for-react-leaflet/</guid><pubDate>Fri, 30 May 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Σεισμολογικά δεδομένα Πανεπιστημίου Αθηνών στο Home Assistant]]></title><description><![CDATA[<p>Το παρόν άρθρο περιέχει οδηγίες για το πώς μπορούμε να έχουμε διαθέσιμα δεδομένα για τον πιο πρόσφατο σεισμό μιας περιοχής από το <a href="http://www.geophysics.geol.uoa.gr/stations/maps/recent_gr.html">Εργαστήριο Σεισμολογίας του Πανεπιστημίου Αθηνών</a> στο <a href="https://www.home-assistant.io/">Home Assistant</a>. Ως παράδειγμα τοποθεσίας χρησιμοποιείται η Σαντορίνη με ακτίνα 35 km.</p>
<p>Χρησιμοποιώντας το <a href="https://github.com/home-assistant/addons/blob/master/configurator/DOCS.md">File editor Add-on</a>, δημιουργούμε νέο φάκελο (directory) με όνομα <code class="language-text">packages</code> (αν δεν υπάρχει ήδη) και μέσα του δημιουργούμε νέο αρχείο με όνομα <code class="language-text">earthquake_santorini.yaml</code> και περιεχόμενο:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">earthquake_santorini</span><span class="token punctuation">:</span>
  <span class="token key atrule">rest</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">resource_template</span><span class="token punctuation">:</span> <span class="token string">'http://www.geophysics.geol.uoa.gr/stations/gmapv3_db/index.php'</span>
      <span class="token key atrule">method</span><span class="token punctuation">:</span> <span class="token string">'POST'</span>
<span class="gatsby-highlight-code-line">      <span class="token key atrule">payload</span><span class="token punctuation">:</span> <span class="token string">'lang=en&amp;outflag=csv&amp;selmode=circle&amp;CATDB=SC3DB&amp;circlat=36.4102&amp;circlon=25.3894&amp;circrad=35&amp;fromdepth=0&amp;todepth=200&amp;frommagn=3&amp;tomagn=8&amp;search=.csv'</span></span>      <span class="token key atrule">headers</span><span class="token punctuation">:</span>
        <span class="token key atrule">Content-Type</span><span class="token punctuation">:</span> <span class="token string">'application/x-www-form-urlencoded'</span>
      <span class="token key atrule">scan_interval</span><span class="token punctuation">:</span> <span class="token number">3600</span>
      <span class="token key atrule">sensor</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake datetime'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_datetime
          <span class="token key atrule">device_class</span><span class="token punctuation">:</span> <span class="token string">'timestamp'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ ((value.split("\n")[-2].split(",")[0].split(".")[0].strip() + "Z") | regex_replace(find="\s+", replace="T") | replace("/", "-") | as_datetime).isoformat() }}'</span>
          
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake latitude'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_latitude
          <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'°'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[1] | float }}'</span>

        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake longitude'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_longitude
          <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'°'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[2] | float }}'</span>
          
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake depth'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_depth
          <span class="token key atrule">device_class</span><span class="token punctuation">:</span> <span class="token string">'distance'</span>
          <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'km'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[3] | float }}'</span>
          
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake magnitude'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_magnitude
          <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'richter'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[4] | float }}'</span>
          
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake location'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_location
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[5] }}'</span>
          
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake distance'</span>
          <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_distance
          <span class="token key atrule">device_class</span><span class="token punctuation">:</span> <span class="token string">'distance'</span>
          <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'km'</span>
          <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split("\n")[-2].split(",")[6] }}'</span>

  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">sensor</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Santorini earthquake location coordinates'</span>
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> earthquake_santorini_location_coordinates
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token string">'{{ states("sensor.earthquake_santorini_magnitude") }}'</span>
      <span class="token key atrule">attributes</span><span class="token punctuation">:</span>
        <span class="token key atrule">latitude</span><span class="token punctuation">:</span> <span class="token string">'{{ states("sensor.earthquake_santorini_latitude") | float }}'</span>
        <span class="token key atrule">longitude</span><span class="token punctuation">:</span> <span class="token string">'{{ states("sensor.earthquake_santorini_longitude") | float }}'</span></code></pre></div>
<p>Η τιμή του <code class="language-text">payload</code> στην πέμπτη γραμμή περιέχει τις παραμέτρους που ορίζουν τα δεδομένα που επιθυμούμε:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">lang=en&amp;outflag=csv&amp;selmode=circle&amp;CATDB=SC3DB&amp;circlat=36.4102&amp;circlon=25.3894&amp;circrad=35&amp;fromdepth=0&amp;todepth=200&amp;frommagn=4&amp;tomagn=8&amp;search=.csv</code></pre></div>
<p>Πιο συγκεκριμένα:</p>
<ul>
<li><code class="language-text">lang</code> — Γλώσσα κειμένου για τον σένσορα <code class="language-text">earthquake_santorini_location</code> (<code class="language-text">en</code> για αγγλικά, <code class="language-text">el</code> για ελληνικά)</li>
<li><code class="language-text">outflag</code> — Μορφή δεδομένων (<code class="language-text">csv</code> για <a href="https://en.wikipedia.org/wiki/Comma-separated_values">CSV</a>)</li>
<li><code class="language-text">selmode</code> — Ορισμός σχήματος περιοχής γύρω από την τοποθεσία ενδιαφέροντος (<code class="language-text">circle</code> για κύκλο)</li>
<li><code class="language-text">CATDB</code> — Η βάση δεδομένων που χρησιμοποιείται (<code class="language-text">SC3DB</code> για <em>NKUA analysis</em>)</li>
<li><code class="language-text">circlat</code>, <code class="language-text">circlon</code> — Γεωγραφικό πλάτος και μήκος του στίγματος της τοποθεσίας ενδιαφέροντος</li>
<li><code class="language-text">circrad</code> — Η ακτίνα του κύκλου ενδιαφέροντος (σε km)</li>
<li><code class="language-text">fromdepth</code> — Ελάχιστο εστιακό βάθος σεισμού (σε km)</li>
<li><code class="language-text">todepth</code> — Μέγιστο εστιακό βάθος σεισμού (σε km)</li>
<li><code class="language-text">frommag</code> — Ελάχιστο μέγεθος σεισμού (σε <a href="https://en.wikipedia.org/wiki/Richter_scale">Richter</a>)</li>
<li><code class="language-text">tomagn</code> — Μέγιστο μέγεθος σεισμού (σε <a href="https://en.wikipedia.org/wiki/Richter_scale">Richter</a>)</li>
</ul>
<p>Για να βρούμε τις κατάλληλες τιμές των παραμέτρων <code class="language-text">circlat</code> και <code class="language-text">circlon</code> μπορούμε να πάμε στο <a href="https://vouna.gr/">vouna.gr</a> και να σημειώσουμε τις συντεταγμένες του στίγματος οι οποίες εμφανίζονται κάτω-αριστερά στον χάρτη καθώς κουνάμε το ποντίκι.</p>
<p>Τέλος, πάλι στο <a href="https://github.com/home-assistant/addons/blob/master/configurator/DOCS.md">File editor Add-on</a>, πηγαίνουμε ένα επίπεδο πίσω και ανοίγουμε το αρχείο <code class="language-text">configuration.yaml</code> προσθέτοντας:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">homeassistant</span><span class="token punctuation">:</span>
  <span class="token key atrule">packages</span><span class="token punctuation">:</span> <span class="token tag">!include_dir_merge_named</span> packages</code></pre></div>
<p>Κάνουμε επανεκκίνηση στο <a href="https://www.home-assistant.io/">Home Assistant</a>.</p>
<p>Αυτό θα έχει ως αποτέλεσμα να δημιουργηθούν οι ακόλουθοι σένσορες:</p>
<ul>
<li><code class="language-text">earthquake_santorini_datetime</code> — Ημερομηνία και ώρα σεισμού (<code class="language-text">timestamp</code>)</li>
<li><code class="language-text">earthquake_santorini_latitude</code> — Γεωγραφικό πλάτος επίκεντρου σεισμού σε μοίρες (<code class="language-text">float</code>)</li>
<li><code class="language-text">earthquake_santorini_longitude</code> — Γεωγραφικό μήκος επίκεντρου σεισμού σε μοίρες (<code class="language-text">float</code>)</li>
<li><code class="language-text">earthquake_santorini_depth</code> — Εστιακό βάθος σεισμού σε km (<code class="language-text">distance</code>)</li>
<li><code class="language-text">earthquake_santorini_magnitude</code> — Μέγεθος σεισμού σε <a href="https://en.wikipedia.org/wiki/Richter_scale">Richter</a> (<code class="language-text">float</code>)</li>
<li><code class="language-text">earthquake_santorini_location</code> — Περιγραφή τοποθεσίας σεισμού (<code class="language-text">string</code>)</li>
<li><code class="language-text">earthquake_santorini_distance</code> — Απόσταση επίκεντρου σεισμού από το στίγμα τοποθεσίας σε km (<code class="language-text">distance</code>)</li>
<li><code class="language-text">earthquake_santorini_location_coordinates</code> — Στίγμα επίκεντρου σεισμού για εμφάνιση σε χάρτη</li>
</ul>
<p>Ακολουθεί παράδειγμα κάρτας σε dashboard:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">type</span><span class="token punctuation">:</span> vertical<span class="token punctuation">-</span>stack
<span class="token key atrule">title</span><span class="token punctuation">:</span> Santorini last earthquake
<span class="token key atrule">cards</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> map
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.earthquake_santorini_location_coordinates
        <span class="token key atrule">label_mode</span><span class="token punctuation">:</span> state
    <span class="token key atrule">theme_mode</span><span class="token punctuation">:</span> auto
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> entities
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.earthquake_santorini_datetime
        <span class="token key atrule">name</span><span class="token punctuation">:</span> When
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.earthquake_santorini_distance
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Distance
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.earthquake_santorini_depth
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Depth
        <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>arrow<span class="token punctuation">-</span>up<span class="token punctuation">-</span>down
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.earthquake_santorini_location
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Location
    <span class="token key atrule">show_header_toggle</span><span class="token punctuation">:</span> <span class="token boolean important">false</span></code></pre></div>
<p>Και ένα ενδεικτικό αποτέλεσμα:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/10583a15970033a59163f29180da61ac/0a47e/ha-dashboard-card.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 165.6%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAhCAIAAABWXBxEAAAACXBIWXMAAAsTAAALEwEAmpwYAAADH0lEQVR42pVV2W7bOBT1///LvBTzBQMEKBBgnABpsym2E9larIW7SIqU1EOxbh2ndtuLa5qieHTOXUQtjDH/z3Zzc7NcLu/u7jC5f7i/vb3F4vX1NVaurq6SJBmGwVrbH9kCv7fZttsdrCjLt7d0X1ZpiusM6y/Jy3q9qarae9+/M7ewpht8710/ejvAne2dYLqEm55P3tteWifCupG9UQeX1qiF1bIoCsmJN8JpjrFTzba9T5svXOSDURgp33ojHTYcOfAL00mllOkC7MeNPVvt2nup9t6eYt6DtZSCGy0iGAxd12TkcUcepKq8hUJ+Ftzss+vP/1XF69DLwyqnItu1D+Cv2KbmG921v5adbzf/fvon3SRDr9yBPCrftl8L8mw1PctsFRWktB07Dg/kFVsjbSV9ibJ/zeyMHHtxkhhsNZoU9DkjDwVNoOKj8gDuNXcQFrTxE3yLyMkTMgdHCjtVY3GwClmcx27Rh+cBxo7GAEYVWsL3TZ2RJJvBcEhgMkMJuSy4rCIzC37EjLIJRaqmKas6o6sfYKQwbb/O413BVgBHWKQ9gC06p25ELlSLbjk44n9GImu2afirkPXMfK4NNIMEpaqWp0TsVFfNmZd+dofenmOG7Ig/eYqMKrxRaHIkyRl5UirqDZ1jZu5MP5xtEqeJ1+2BWfwd+Fzf/yFYXtjhL62HmPEm80O13uWsD63ShQNE836+/O5a4P1HFwXZtmvhvaa9JvMEznHC4G2p6vS1YUXdZA3ZNKHErxWpWrKuKZVq4Zwfx2kcRxyO0zSFv8HPHs06HHx+wBjdD+O8f8J8AcDjW/2Y1tPg9/sKh+J00YQQjDGMeHAAL5+zZZJP04DDVkp5GUwICSd0UQRwVBsEj6M/2GW8cy6GFJgrqmrWGa1Xq9V6vb5MDiS+BGmavmP+W4vMI1OGSgPmp6enuq5jCBcwP5mxD9Uw1uEGPnq/DTgqDyWLMe+JLIlEibuu45x/rBYIKKUoT9M0bDbk5Tv4JWuTrB0Hn+cF9ODr8xEMWFmWqBDiwjzPc5Cjw9w423HNhg8W98S70bz33wC8qGbHXANC8gAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Home Assistant dashboard card"
        title=""
        src="/static/10583a15970033a59163f29180da61ac/0b533/ha-dashboard-card.png"
        srcset="/static/10583a15970033a59163f29180da61ac/fac75/ha-dashboard-card.png 125w,
/static/10583a15970033a59163f29180da61ac/63868/ha-dashboard-card.png 250w,
/static/10583a15970033a59163f29180da61ac/0b533/ha-dashboard-card.png 500w,
/static/10583a15970033a59163f29180da61ac/0a47e/ha-dashboard-card.png 600w"
        sizes="(max-width: 500px) 100vw, 500px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>]]></description><link>https://angelos.dev//2025/02/seismologika-dedomena-panepistimiou-athinon-sto-home-assistant/</link><guid isPermaLink="false">https://angelos.dev//2025/02/seismologika-dedomena-panepistimiou-athinon-sto-home-assistant/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Μετεωρολογικός σταθμός Αστεροσκοπείου στο Home Assistant]]></title><description><![CDATA[<p>Το παρόν άρθρο περιέχει οδηγίες για το πώς μπορούμε να προσθέσουμε τα στοιχεία ενός <a href="https://www.meteo.gr/gmap.cfm">μετεωρολογικού σταθμού</a> του <a href="https://www.meteo.gr/">Αστεροσκοπείου Αθηνών</a> στο <a href="https://www.home-assistant.io/">Home Assistant</a> π.χ. για να τα εμφανίσουμε σε κάρτα στο dashboard. Ως παράδειγμα τοποθεσίας χρησιμοποιείται ο <a href="https://penteli.meteo.gr/stations/vyronas/">μετεωρολογικός σταθμός του Βύρωνα</a>.</p>
<p>Χρησιμοποιώντας το <a href="https://github.com/home-assistant/addons/blob/master/configurator/DOCS.md">File editor Add-on</a>, δημιουργούμε νέο φάκελο (directory) με όνομα <code class="language-text">packages</code> (αν δεν υπάρχει ήδη) και μέσα του δημιουργούμε νέο αρχείο με όνομα <code class="language-text">meteo_vironas.yaml</code> και περιεχόμενο:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">meteo_vironas</span><span class="token punctuation">:</span>
  <span class="token key atrule">scrape</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">resource</span><span class="token punctuation">:</span> <span class="token string">'https://penteli.meteo.gr/stations/vyronas/'</span>
    <span class="token key atrule">headers</span><span class="token punctuation">:</span>
      <span class="token key atrule">User-Agent</span><span class="token punctuation">:</span> <span class="token string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3'</span>
    <span class="token key atrule">scan_interval</span><span class="token punctuation">:</span> <span class="token number">3600</span>
    <span class="token key atrule">sensor</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas last updated
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_last_updated
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .headline.gradient span
        <span class="token key atrule">index</span><span class="token punctuation">:</span> <span class="token number">1</span>
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ (strptime(value, "%d/%m/%Y %H:%M") | as_local).isoformat() }}'</span>
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> timestamp
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas temperature
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_temperature
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .realtime .lright span
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split(" ")[0] | float }}'</span>
        <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'°C'</span>
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> temperature
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas humidity
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_humidity
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .realtime .lright span
        <span class="token key atrule">index</span><span class="token punctuation">:</span> <span class="token number">1</span>
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split(" ")[0] | float }}'</span>
        <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'%'</span>
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> humidity
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas wind speed
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_wind_speed
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .realtime .lright span
        <span class="token key atrule">index</span><span class="token punctuation">:</span> <span class="token number">3</span>
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split(" ")[0] | float }}'</span>
        <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'km/h'</span>
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> wind_speed
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas barometric pressure
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_barometric_pressure
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .realtime .lright span
        <span class="token key atrule">index</span><span class="token punctuation">:</span> <span class="token number">4</span>
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split(" ")[0] | float }}'</span>
        <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> hPa
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> pressure
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Meteo Vironas rain today
        <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> meteo_vironas_rain_today
        <span class="token key atrule">select</span><span class="token punctuation">:</span> .realtime .lright span
        <span class="token key atrule">index</span><span class="token punctuation">:</span> <span class="token number">5</span>
        <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value.split(" ")[0] | float }}'</span>
        <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
        <span class="token key atrule">device_class</span><span class="token punctuation">:</span> precipitation</code></pre></div>
<p>Στη συνέχεια, πάλι στο <a href="https://github.com/home-assistant/addons/blob/master/configurator/DOCS.md">File editor Add-on</a>, πηγαίνουμε ένα επίπεδο πίσω και ανοίγουμε το αρχείο <code class="language-text">configuration.yaml</code> προσθέτοντας:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">homeassistant</span><span class="token punctuation">:</span>
  <span class="token key atrule">packages</span><span class="token punctuation">:</span> <span class="token tag">!include_dir_merge_named</span> packages</code></pre></div>
<p>Κάνουμε επανεκκίνηση στο <a href="https://www.home-assistant.io/">Home Assistant</a>.</p>
<p>Αυτό θα έχει ως αποτέλεσμα να δημιουργηθούν οι ακόλουθοι σένσορες:</p>
<ul>
<li><code class="language-text">meteo_vironas_last_updated</code> — Ημερομηνία και ώρα τελευταίας ενημέρωσης (<code class="language-text">timestamp</code>)</li>
<li><code class="language-text">meteo_vironas_temperature</code> —  Θερμοκρασία σε °C (<code class="language-text">temperature</code>)</li>
<li><code class="language-text">meteo_vironas_humidity</code> — Υγρασία σε % (<code class="language-text">humidity</code>)</li>
<li><code class="language-text">meteo_vironas_wind_speed</code> — Ταχύτητα ανέμου σε km/h (<code class="language-text">wind_speed</code>)</li>
<li><code class="language-text">meteo_vironas_barometric_pressure</code> — Βαρομετρική πίεση σε hPa (<code class="language-text">pressure</code>)</li>
<li><code class="language-text">meteo_vironas_rain_today</code> — Ύψος σημερινής βροχής σε mm (<code class="language-text">precipitation</code>)</li>
</ul>
<p>Ακολουθεί παράδειγμα κάρτας σε dashboard:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">type</span><span class="token punctuation">:</span> vertical<span class="token punctuation">-</span>stack
<span class="token key atrule">cards</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> vertical<span class="token punctuation">-</span>stack
    <span class="token key atrule">cards</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> horizontal<span class="token punctuation">-</span>stack
        <span class="token key atrule">cards</span><span class="token punctuation">:</span>
          <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
            <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
            <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_temperature
            <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
            <span class="token key atrule">unit</span><span class="token punctuation">:</span> ⁰C
            <span class="token key atrule">name</span><span class="token punctuation">:</span> Θερμοκρασία
          <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
            <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
            <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_humidity
            <span class="token key atrule">unit</span><span class="token punctuation">:</span> <span class="token string">"%"</span>
            <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
            <span class="token key atrule">name</span><span class="token punctuation">:</span> Υγρασία
      <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> horizontal<span class="token punctuation">-</span>stack
        <span class="token key atrule">cards</span><span class="token punctuation">:</span>
          <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
            <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
            <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_wind_speed
            <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
            <span class="token key atrule">name</span><span class="token punctuation">:</span> Άνεμος
          <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
            <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
            <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_rain_today
            <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
            <span class="token key atrule">name</span><span class="token punctuation">:</span> Βροχή
          <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
            <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
            <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_barometric_pressure
            <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
            <span class="token key atrule">name</span><span class="token punctuation">:</span> Πίεση
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> entities
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.meteo_vironas_last_updated
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Τελευταία ενημέρωση
    <span class="token key atrule">show_header_toggle</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
<span class="token key atrule">title</span><span class="token punctuation">:</span> Meteo.gr Βύρωνας</code></pre></div>
<p>Και το αποτέλεσμα:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/82fb7e91975e32baa4ae56e3dbf1cbe1/9128f/ha-dashboard-card.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 96.80000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZ0lEQVR42nVT207kMAzt/38W4mFhxQoJaQZmB3Z6md6nbZqmTRon2ZMGEOyC5Qc7ju2TYydaluXq6ur6+vr+/v5wONzc3Dw8PNze3sZxbIxRSq3fS4RwWZZVVTVNc7lcYMMoiqLrOoTVNyIliqpIa+2cs9a6TewmwdabmK8EdxCKYKFVvkmappxPSOOzPGQtDDQBKADJsoyIQlE+qz9Fh0aALRGIk6TveylluLGS6fiM8kg+eHkqiwJtXhGRmWYFN8LDdvv9y8sLkt/Cr7DholwSx49PhzwvUSgchwtoExnFnV3dV0KEB29QSTo9vRd9iyKZJ5b9tuLsZO9W5lbuzNaBBE2F0cqK3A5Hy46On9zSuHV0cw2blkukF0aiJlHSmHqdcq8cmq2i01NtpjPNrVdc42diCYqSqPTcRRrgNIEDYx0Zq8l6F0IGLk7laojM6g9sUG3sqnGT/JzBNnYDSKt+2mbgxnFMkgRcwj6eW0wOBeqmzvNzVRZp0e7ixm5sq+fnZ0x4FmJgo1gWJLTtZbfbtW0LtsUiQTvowc6h4q+7u6puZgVgOsLp6XTq+2EYGNYTQDBexth+v8fyhOEHbqdpyrIzho6i2EQ8FKMSzpLXMAmjtRL288zCMr6uQFheUqR4BA41SzXLDJicwGehRE9ypKWjpTdyMLInNa4LN1NFY+aV52ZMtWijDaffddKrJ9cvvYMD95OCcUPWa7gDhBQBQlwNSTU4a0DYyDle/raJn5AzNoILsAODc45aPvnHLvn5mOG5VV2HHxZI+kf6vusuXVPX4fP7jxG+0cdv/O7+Lx9raa3/Aqu/RYQ/Nct2AAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Home Assistant dashboard card"
        title=""
        src="/static/82fb7e91975e32baa4ae56e3dbf1cbe1/0b533/ha-dashboard-card.png"
        srcset="/static/82fb7e91975e32baa4ae56e3dbf1cbe1/fac75/ha-dashboard-card.png 125w,
/static/82fb7e91975e32baa4ae56e3dbf1cbe1/63868/ha-dashboard-card.png 250w,
/static/82fb7e91975e32baa4ae56e3dbf1cbe1/0b533/ha-dashboard-card.png 500w,
/static/82fb7e91975e32baa4ae56e3dbf1cbe1/9128f/ha-dashboard-card.png 603w"
        sizes="(max-width: 500px) 100vw, 500px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>]]></description><link>https://angelos.dev//2025/02/meteorologikos-stathmos-asteroskopeiou-sto-home-assistant/</link><guid isPermaLink="false">https://angelos.dev//2025/02/meteorologikos-stathmos-asteroskopeiou-sto-home-assistant/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Ajax Systems alarm system in Home Assistant]]></title><description><![CDATA[<p>This post details how to integrate an <a href="https://ajax.systems/">Ajax Systems</a> alarm system into <a href="https://www.home-assistant.io/">Home Assistant</a> so that it’s possible to both read and “write” (set) its state (armed, disarmed, night mode, triggered) This enables cool automations such as:</p>
<ul>
<li>“Disarm in the morning when I’m at home” — Also possible with the <a href="https://ajax.systems/ajax-security-system/">mobile app</a> using schedules</li>
<li>“Arm when I leave the vicinity of the home” — Somewhat possible with the <a href="https://ajax.systems/ajax-security-system/">mobile app</a> using a <a href="https://en.wikipedia.org/wiki/Geofence">geofence</a> (you get a push notification when you leave the zone)</li>
<li>“Turn on external lights when the alarm triggers”</li>
</ul>
<h3>Reading the state</h3>
<p>This is possible with the <a href="https://www.home-assistant.io/integrations/sia/">SIA Alarm Systems</a> Home Assistant integration. The documentation provides an easy-to-follow example on how to set it up for an <a href="https://ajax.systems/">Ajax Systems</a> alarm, so it’s not covered here.</p>
<p><strong>Note:</strong> Make sure you enter <code class="language-text">AAA</code> as an <em>Account ID</em> and <code class="language-text">45321</code> as a <em>Port</em> in order for the YAML samples (included later) to work.</p>
<p>After the setup is complete, we need to create an <a href="https://www.home-assistant.io/integrations/input_select/">input_select</a> to keep the alarm state between <a href="https://www.home-assistant.io/integrations/sia/">SIA</a> and <a href="https://www.home-assistant.io/">Home Assistant</a> in-sync:</p>
<ul>
<li>Open your <a href="https://www.home-assistant.io/">Home assistant</a> installation</li>
<li>Go to <em>Settings</em> → <em>Devices &#x26; services</em> → <em>Helpers</em></li>
<li>Click <em>Create Helper</em></li>
<li>Select <em>Dropdown</em></li>
<li>Enter <code class="language-text">Alarm</code> for <em>Name</em></li>
<li>Enter <code class="language-text">mdi:shield-home</code> for <em>Icon</em></li>
<li>Add the following options, one for each possible alarm state: <code class="language-text">armed_away</code>, <code class="language-text">armed_night</code>, <code class="language-text">disarmed</code>, <code class="language-text">triggered</code></li>
<li>Click <em>Create</em></li>
</ul>
<p>Now it’s time to create the necessary automations for the syncing. First the one from <a href="https://www.home-assistant.io/integrations/sia/">SIA</a> to <a href="https://www.home-assistant.io/">Home Assistant</a>:</p>
<ul>
<li>Go to <em>Settings</em> → <em>Automations &#x26; scenes</em></li>
<li>Click <em>Create Automation</em></li>
<li>Select <em>Create new automation</em></li>
<li>Click on the three dots on the top-right of the screen</li>
<li>Select <em>Edit in YAML</em></li>
<li>Replace the contents with the following YAML</li>
<li>Click <em>Save</em></li>
</ul>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">alias</span><span class="token punctuation">:</span> Alarm sync from SIA
  <span class="token key atrule">triggers</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">trigger</span><span class="token punctuation">:</span> state
    <span class="token key atrule">entity_id</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> alarm_control_panel.45321_aaa_zone_1_alarm
  <span class="token key atrule">conditions</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">condition</span><span class="token punctuation">:</span> template
    <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">"{{ states('alarm_control_panel.45321_aaa_zone_1_alarm') != states('input_select.alarm') }}"</span>
  <span class="token key atrule">actions</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">action</span><span class="token punctuation">:</span> input_select.select_option
    <span class="token key atrule">target</span><span class="token punctuation">:</span>
      <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> input_select.alarm
    <span class="token key atrule">data_template</span><span class="token punctuation">:</span>
      <span class="token key atrule">option</span><span class="token punctuation">:</span> <span class="token string">"{{ states('alarm_control_panel.45321_aaa_zone_1_alarm') }}"</span></code></pre></div>
<p>Then the one from <a href="https://www.home-assistant.io/">Home Assistant</a> to <a href="https://www.home-assistant.io/integrations/sia/">SIA</a>:</p>
<ul>
<li>Click <em>Create Automation</em></li>
<li>Select <em>Create new automation</em></li>
<li>Click on the three dots on the top-right of the screen</li>
<li>Select <em>Edit in YAML</em></li>
<li>Replace the contents with the following YAML</li>
<li>Click <em>Save</em></li>
</ul>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">alias</span><span class="token punctuation">:</span> Alarm sync to SpaceControl
  <span class="token key atrule">triggers</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">trigger</span><span class="token punctuation">:</span> state
    <span class="token key atrule">entity_id</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> input_select.alarm
  <span class="token key atrule">conditions</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">condition</span><span class="token punctuation">:</span> template
    <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">"{{ states('alarm_control_panel.45321_aaa_zone_1_alarm') != states('input_select.alarm') }}"</span>
  <span class="token key atrule">actions</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">data_template</span><span class="token punctuation">:</span>
      <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {% if is_state('input_select.alarm', 'disarmed') %}
          switch.ajax_spacecontrol_disarm
        {% elif is_state('input_select.alarm', 'armed_night') %}
          switch.ajax_spacecontrol_night
        {% elif is_state('input_select.alarm', 'armed_away') %}
          switch.ajax_spacecontrol_arm
        {% elif is_state('input_select.alarm', 'triggered') %}
          switch.ajax_spacecontrol_panic
        {% else %}
        {% endif %}</span>
    <span class="token key atrule">action</span><span class="token punctuation">:</span> switch.turn_on</code></pre></div>
<h3>Setting the state</h3>
<p><a href="https://ajax.systems/">Ajax Systems</a> does not provide a consumer-level API (only an <a href="https://ajax.systems/blog/enterprise-api/">enterprise one</a>), so we have to do some hardware hacking to get it to work.</p>
<h4>Necessary hardware</h4>
<ul>
<li><a href="https://ajax.systems/products/ajaxspacecontrol/">Ajax SpaceControl</a> key fob</li>
<li><a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">Wemos D1 Mini</a> (I bought it from <a href="https://www.aliexpress.com/item/32529101036.html">AliExpress</a>)</li>
<li>Thin (~0.5 mm) soldering wire with flux core (preferably lead-free, otherwise it’s toxic when inhaled)</li>
<li>Spool of 22 (or similar) <a href="https://en.wikipedia.org/wiki/American_wire_gauge">AWG</a> tinned, flexible copper wire</li>
</ul>
<h4>Necessary tools</h4>
<ul>
<li>Soldering iron, ideally with temperature adjustment</li>
<li>Two (2) <a href="https://www.aliexpress.com/w/wholesale-helping-hands-soldering.html">helping hands for soldering</a></li>
<li>Wire cutter and stripper (for 22 <a href="https://en.wikipedia.org/wiki/American_wire_gauge">AWG</a> or similar)</li>
</ul>
<h4>Preparation steps</h4>
<ul>
<li><a href="https://support.ajax.systems/en/manuals/ajaxspacecontrol/">Connect the key fob to your alarm system</a> Make sure pressing <em>Arm</em>, <em>Disarm</em>, <em>Night Mode</em> and <em>Panic</em> works.</li>
<li><a href="https://support.ajax.systems/en/how-to-replace-batteries-in-spacecontrol/">Open the key fob</a> and remove the integrated circuit board (ICB) Store its plastic enclosure somewhere safe in case you ever need it.</li>
<li>Remove the battery from the key fob ICB as the whole thing is going to be powered by the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a>. If you don’t need the battery, recycle it.</li>
<li>Cut six (6) 10 cm long wires and strip their ends (no more than 5 mm) If you have many colors, make four (4) white for the buttons, one (1) red for positive and one (1) black for negative.</li>
<li>Secure the key fob ICB onto the first <a href="https://www.aliexpress.com/w/wholesale-helping-hands-soldering.html">helping hands</a></li>
<li>Secure the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a> onto the second <a href="https://www.aliexpress.com/w/wholesale-helping-hands-soldering.html">helping hands</a></li>
<li>Plug your soldering iron into a wall outlet and get ready for some soldering!</li>
</ul>
<h4>Soldering on the <strong>key fob ICB</strong></h4>
<ul>
<li>Solder four (4) wires, one to each button position <a href="https://bytebucket.org/iesus_sonesson/d1-ajax-mqtt/raw/2b4bf39ffa72f30249bf600ef66406c73319e251/images/soldered-connections.jpg">as pictured here</a> where <strong>top-left</strong> is <strong>night mode</strong>, <strong>top-right</strong> is <strong>arm</strong>, <strong>bottom-left</strong> is <strong>panic mode</strong> and <strong>bottom-right</strong> is <strong>disarm</strong>. Take care to solder only the metal contacts and not the rest of the board!</li>
<li>Solder one (1) wire to the <strong>positive</strong> battery terminal <a href="https://bytebucket.org/iesus_sonesson/d1-ajax-mqtt/raw/2b4bf39ffa72f30249bf600ef66406c73319e251/images/red-wire.jpg">as pictured here (red wire)</a></li>
<li>Solder one (1) wire to <strong>ground</strong> <a href="https://bytebucket.org/iesus_sonesson/d1-ajax-mqtt/raw/2b4bf39ffa72f30249bf600ef66406c73319e251/images/soldered-connections.jpg">as pictured here (black wire)</a></li>
</ul>
<h4>Soldering on the <strong>D1 Mini</strong></h4>
<ul>
<li>Solder the <strong>disarm</strong> wire to the <strong>GPIO4</strong> pin</li>
<li>Solder the <strong>panic mode</strong> wire to the <strong>GPIO5</strong> pin</li>
<li>Solder the <strong>arm</strong> wire to the <strong>GPIO12</strong> pin</li>
<li>Solder the <strong>night mode</strong> wire to the <strong>GPIO13</strong> pin</li>
<li>Solder the <strong>positive</strong> battery terminal wire to the <strong>3V3</strong> (VCC) pin <a href="https://bytebucket.org/iesus_sonesson/d1-ajax-mqtt/raw/2b4bf39ffa72f30249bf600ef66406c73319e251/images/completed-project.jpg">as pictured here</a></li>
<li>Solder the <strong>negative</strong> battery terminal wire to the <strong>GND</strong> pin <a href="https://bytebucket.org/iesus_sonesson/d1-ajax-mqtt/raw/2b4bf39ffa72f30249bf600ef66406c73319e251/images/completed-project.jpg">as pictured here</a></li>
</ul>
<h4>Home Assistant setup</h4>
<ul>
<li>Remove the key fob ICB and the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a> from the <a href="https://www.aliexpress.com/w/wholesale-helping-hands-soldering.html">helping hands</a></li>
<li>Connect the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a> to your <a href="https://www.home-assistant.io/">Home Assistant</a> machine with a USB cable</li>
<li>Go to the add-on store of your <a href="https://www.home-assistant.io/">Home Assistant</a> installation and <a href="https://esphome.io/guides/getting_started_hassio">install the ESPHome Device Builder add-on</a></li>
<li>Once the add-on is installed, click <em>Start</em> and then <em>Open Web UI</em></li>
<li>Click <em>New Device</em>, enter <code class="language-text">Ajax SpaceControl</code> as a <em>Name</em> and follow the prompts to add the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a></li>
<li>Once the device is listed, click <em>Edit</em> to open the configuration editor</li>
<li><strong>Append</strong> the following YAML</li>
<li>Click <em>Save</em> and <em>Install</em> to install the firmware to the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">D1 Mini</a></li>
</ul>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">text_sensor</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> wifi_info
    <span class="token key atrule">ip_address</span><span class="token punctuation">:</span>
      <span class="token key atrule">name</span><span class="token punctuation">:</span> WiFi IP address
    <span class="token key atrule">ssid</span><span class="token punctuation">:</span>
      <span class="token key atrule">name</span><span class="token punctuation">:</span> WiFi SSID

  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> version
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Firmware version

<span class="token key atrule">sensor</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> wifi_signal
    <span class="token key atrule">name</span><span class="token punctuation">:</span> WiFi Signal
    <span class="token key atrule">update_interval</span><span class="token punctuation">:</span> 60s
    <span class="token key atrule">device_class</span><span class="token punctuation">:</span> signal_strength

<span class="token key atrule">switch</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> gpio
    <span class="token key atrule">pin</span><span class="token punctuation">:</span> GPIO4
    <span class="token key atrule">inverted</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">id</span><span class="token punctuation">:</span> relay_disarm
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Disarm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> <span class="token string">"mdi:shield-off"</span>
    <span class="token key atrule">on_turn_on</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">delay</span><span class="token punctuation">:</span> 500ms
      <span class="token punctuation">-</span> <span class="token key atrule">switch.turn_off</span><span class="token punctuation">:</span> relay_disarm

  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> gpio
    <span class="token key atrule">pin</span><span class="token punctuation">:</span> GPIO5
    <span class="token key atrule">inverted</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">id</span><span class="token punctuation">:</span> relay_panic
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Panic
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> <span class="token string">"mdi:shield-alert"</span>
    <span class="token key atrule">on_turn_on</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">delay</span><span class="token punctuation">:</span> 500ms
      <span class="token punctuation">-</span> <span class="token key atrule">switch.turn_off</span><span class="token punctuation">:</span> relay_panic

  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> gpio
    <span class="token key atrule">pin</span><span class="token punctuation">:</span> GPIO12
    <span class="token key atrule">inverted</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">id</span><span class="token punctuation">:</span> relay_arm
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Arm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> <span class="token string">"mdi:shield-lock"</span>
    <span class="token key atrule">on_turn_on</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">delay</span><span class="token punctuation">:</span> 500ms
      <span class="token punctuation">-</span> <span class="token key atrule">switch.turn_off</span><span class="token punctuation">:</span> relay_arm

  <span class="token punctuation">-</span> <span class="token key atrule">platform</span><span class="token punctuation">:</span> gpio
    <span class="token key atrule">pin</span><span class="token punctuation">:</span> GPIO13
    <span class="token key atrule">inverted</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">id</span><span class="token punctuation">:</span> relay_night
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Night
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> <span class="token string">"mdi:shield-moon"</span>
    <span class="token key atrule">on_turn_on</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">delay</span><span class="token punctuation">:</span> 500ms
      <span class="token punctuation">-</span> <span class="token key atrule">switch.turn_off</span><span class="token punctuation">:</span> relay_night</code></pre></div>
<h4>Finishing up</h4>
<p>Here’s the YAML of a dashboard card to test things out:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">type</span><span class="token punctuation">:</span> vertical<span class="token punctuation">-</span>stack
<span class="token key atrule">title</span><span class="token punctuation">:</span> Alarm
<span class="token key atrule">cards</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> glance
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> alarm_control_panel.45321_aaa_zone_1_alarm
        <span class="token key atrule">name</span><span class="token punctuation">:</span> SIA
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Alarm
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> grid
    <span class="token key atrule">columns</span><span class="token punctuation">:</span> <span class="token number">2</span>
    <span class="token key atrule">square</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">cards</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">show_name</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">show_icon</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">type</span><span class="token punctuation">:</span> button
        <span class="token key atrule">tap_action</span><span class="token punctuation">:</span>
          <span class="token key atrule">action</span><span class="token punctuation">:</span> call<span class="token punctuation">-</span>service
          <span class="token key atrule">service</span><span class="token punctuation">:</span> input_select.select_option
          <span class="token key atrule">service_data</span><span class="token punctuation">:</span>
            <span class="token key atrule">option</span><span class="token punctuation">:</span> armed_away
            <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">entity</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Arm
        <span class="token key atrule">show_state</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
        <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>shield<span class="token punctuation">-</span>lock
      <span class="token punctuation">-</span> <span class="token key atrule">show_name</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">show_icon</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">type</span><span class="token punctuation">:</span> button
        <span class="token key atrule">tap_action</span><span class="token punctuation">:</span>
          <span class="token key atrule">action</span><span class="token punctuation">:</span> call<span class="token punctuation">-</span>service
          <span class="token key atrule">service</span><span class="token punctuation">:</span> input_select.select_option
          <span class="token key atrule">service_data</span><span class="token punctuation">:</span>
            <span class="token key atrule">option</span><span class="token punctuation">:</span> disarmed
            <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">entity</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Disarm
        <span class="token key atrule">show_state</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
        <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>shield<span class="token punctuation">-</span>off
      <span class="token punctuation">-</span> <span class="token key atrule">show_name</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">show_icon</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">type</span><span class="token punctuation">:</span> button
        <span class="token key atrule">tap_action</span><span class="token punctuation">:</span>
          <span class="token key atrule">action</span><span class="token punctuation">:</span> call<span class="token punctuation">-</span>service
          <span class="token key atrule">service</span><span class="token punctuation">:</span> input_select.select_option
          <span class="token key atrule">service_data</span><span class="token punctuation">:</span>
            <span class="token key atrule">option</span><span class="token punctuation">:</span> armed_night
            <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">entity</span><span class="token punctuation">:</span> input_select.alarm
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Night Mode
        <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>shield<span class="token punctuation">-</span>moon
      <span class="token punctuation">-</span> <span class="token key atrule">show_name</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">show_icon</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
        <span class="token key atrule">type</span><span class="token punctuation">:</span> button
        <span class="token key atrule">tap_action</span><span class="token punctuation">:</span>
          <span class="token key atrule">action</span><span class="token punctuation">:</span> none
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Panic
        <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>shield<span class="token punctuation">-</span>alert
        <span class="token key atrule">hold_action</span><span class="token punctuation">:</span>
          <span class="token key atrule">action</span><span class="token punctuation">:</span> perform<span class="token punctuation">-</span>action
          <span class="token key atrule">perform_action</span><span class="token punctuation">:</span> input_select.select_option
          <span class="token key atrule">target</span><span class="token punctuation">:</span>
            <span class="token key atrule">entity_id</span><span class="token punctuation">:</span> input_select.alarm</code></pre></div>
<p>Here’s how it looks:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/f7db1b377b5f3850a2ce4b8daf23c6eb/5fd3e/ha-dashboard-card.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 150.4%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAIAAACjcKk8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAClElEQVR42q1U22vTYBTvn+U/oSgKvgqCIAhT2IviBQUfFHwQLwVfBGvnBdmD21qHF6oPm4zVbS1iY2ear2vSS3pdkzZNvi/x5Du95MagsEMIOd/v/E7O5ZfETNO0bZsxRik157SYruvZbDaXywmCAM+WZc1BNgyjxK1erw8GgyNCR9x8ZHiVzQ0rhwcaZYAGICDG8BTMcRzwFUWBu+MxhKAjQiRd16YncHfJszhma7qGWMAgY6fbhaK9SX3kER2lpXXN1F3M8aWAwlc3BX1ocihExpZUXaWMOqF3A7ne0agbbEe8ud/vS5IkH8iEkF6vN+0NTNM0WEetWgWo3W7PyNDMdHq4DFgerCEw2DDkThvXgAZpLL6tscttikKg5QkGom9goT7pEWhw2llB3ikqYTLEWZTlxNq2IAfJjA9mZaNw5lby7J2l9NZfcBkfPkoI3B97pcV4+sTl+IfveTcvP+dk5sKv1nfefcslv+zC5ZKZPRmHi679FE5eTyw8WXufQbI9JuNG2oeDe4nM/WSmpxnhnocjuvA0de15utbu85o9PSNfrqn1Zme6YS/ZZrTRbFWqDcMYRQ9MUeSmqkZOGz4MURSJJMFnG02ee1XUv3efZrj4vFAgeCZPrjjqdUGJftQKuMehMJx2gTT+Ka3wtNHdl1t/SCP4PeOiP2/vX3y4fPrm68yu6FUYohu/yfm7b8/dXgK1+EQyUdivFytbi/HUm697EQrbLJy6kYDULz9lffLEmtWufunRx6vPUiA1rG2sbV4kyO7K49ULD5aV5iHWPOsZ4nStT+QGUVRjOADX1zPnE8VFI/5hsJV8Pl8SxXKZFItFk/8lvdOG50rloEwI5j0OhU1/+sxjeIJisCfmhRD9D1s+uYbdVjUxAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Home Assistant dashboard card"
        title=""
        src="/static/f7db1b377b5f3850a2ce4b8daf23c6eb/0b533/ha-dashboard-card.png"
        srcset="/static/f7db1b377b5f3850a2ce4b8daf23c6eb/fac75/ha-dashboard-card.png 125w,
/static/f7db1b377b5f3850a2ce4b8daf23c6eb/63868/ha-dashboard-card.png 250w,
/static/f7db1b377b5f3850a2ce4b8daf23c6eb/0b533/ha-dashboard-card.png 500w,
/static/f7db1b377b5f3850a2ce4b8daf23c6eb/5fd3e/ha-dashboard-card.png 594w"
        sizes="(max-width: 500px) 100vw, 500px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Try restarting (not reloading) <a href="https://www.home-assistant.io/">Home Assistant</a> in case it doesn’t work.</p>]]></description><link>https://angelos.dev//2025/02/ajax-systems-alarm-system-in-home-assistant/</link><guid isPermaLink="false">https://angelos.dev//2025/02/ajax-systems-alarm-system-in-home-assistant/</guid><pubDate>Fri, 14 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Bresser weather station in Home Assistant]]></title><description><![CDATA[<p>This is a guide on how to display data from a <a href="https://www.bresser.com/p/bresser-wi-fi-colour-weather-station-with-5-in-1-professional-sensor-7002580">Bresser 5-in-1</a> (or similar) weather station in <a href="https://www.home-assistant.io/">Home Assistant</a>.</p>
<h3>Assumptions</h3>
<p>You already have <a href="https://www.home-assistant.io/">Home Assistant</a> up and running in a system (e.g. <a href="https://www.raspberrypi.org/">Raspberry Pi</a>) with enough power to drive external USB devices.</p>
<h3>Necessary hardware</h3>
<ul>
<li>A USB dongle that can capture FM radio, as this is how the weather station transmits data. I can recommend the <a href="https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/">RTL-SDR BLOG</a> V3 which is considered best-in-class.</li>
<li>An antenna with an SMA male connector (threads inside, with pin) to connect to the dongle. I wasn’t able to capture any data without the antenna.</li>
</ul>
<h3>Test FM dongle</h3>
<p>Screw the antenna to the dongle and plug it in your workstation (regular computer)</p>
<p>Install <a href="https://github.com/merbanan/rtl_433">rtl_433</a>. In Debian:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">sudo apt install rtl-433</code></pre></div>
<p>Verify you can receive data by running the following command for a few minutes:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">sudo rtl_433 -f 868.3M -R 172</code></pre></div>
<p>If protocol <code class="language-text">172</code> does not seem to work, issue <code class="language-text">rtl_433 -R help</code> for a list of supported protocols.</p>
<p>The output should look something like this:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">   time      : &lt;time>
   model     : Bresser-6in1 id        : &lt;id>
   channel   : 0            Battery   : 1             Temperature: 14.5 C
   Humidity  : 61           Sensor type: 1            Wind Gust : 0.0 m/s
   Wind Speed: 0.0 m/s      Direction : 90            UV        : 0.0
   Flags     : 0            Integrity : CRC</code></pre></div>
<p>Note that if you later place the dongle someplace else, it may not be able to receive data, even with the antenna on (e.g. due to signal interference and/or walls) I realized this, the hard way, after placing it in a closet and trying to debug, for hours, why it’s not receiving anything. So this test should ideally happen (using a laptop) near the physical space the dongle will later be placed at.</p>
<h3>Install add-ons</h3>
<p>Install the following add-ons in <a href="https://www.home-assistant.io/">Home Assistant</a>:</p>
<ul>
<li><a href="https://github.com/home-assistant/addons/tree/master/configurator">File editor</a> to edit configuration files (official add-on)</li>
<li><a href="https://github.com/home-assistant/addons/tree/master/mosquitto">Mosquitto broker</a> to store weather station data (official add-on)</li>
<li><a href="https://github.com/catduckgnaf/rtl_433_haos_addon">rtl_433</a> to receive weather station data with the dongle and forward it to <a href="https://en.wikipedia.org/wiki/MQTT">MQTT</a> (community add-on)</li>
</ul>
<h3>Create user</h3>
<p><a href="https://www.home-assistant.io/integrations/person/#adding-a-person-to-home-assistant">Create a user in Home Assistant</a>, the login details (username, password) of which will be used by <a href="https://github.com/catduckgnaf/rtl_433_haos_addon">rtl_433</a> in the next step to connect to <a href="https://github.com/home-assistant/addons/tree/master/mosquitto">Mosquitto broker</a> via <a href="https://en.wikipedia.org/wiki/MQTT">MQTT</a>.</p>
<h3>Configure rtl_433</h3>
<p>Edit <code class="language-text">/homeassistant/rtl_433/rtl_433.conf</code> with the <a href="https://github.com/home-assistant/addons/tree/master/configurator">File editor</a> add-on and <strong>replace its contents</strong> with:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="ini"><pre class="language-ini"><code class="language-ini">report_meta stats
report_meta time:iso:usec:tz
report_meta protocol

gain 0

convert si

frequency 868.3M

pulse_detect autolevel

verbose 5

<span class="gatsby-highlight-code-line"><span class="token key attr-name">output mqtt://core-mosquitto:1883,user</span><span class="token punctuation">=</span><span class="token value attr-value">&lt;username>,pass=&lt;password>,retain=1,devices=rtl_433/bresser51</span></span>output kv

protocol 172 # Bresser Weather Center 6-in-1, 7-in-1 indoor, soil, new 5-in-1, 3-in-1 wind gauge, Froggit WH6000, Ventus C8488A</code></pre></div>
<p>Make sure you replace <code class="language-text">&lt;username></code> and <code class="language-text">&lt;password></code> with the details of the user you created in the previous step.</p>
<h3>Verify rtl_433 works</h3>
<p>Go to the add-on page of <a href="https://github.com/home-assistant/addons/tree/master/mosquitto">Mosquitto broker</a> and <a href="https://github.com/catduckgnaf/rtl_433_haos_addon">rtl_433</a> and start them (in this order) Make sure the “Start on boot” and “Watchdog” options are enabled.</p>
<p>Switch to the Log tab of <a href="https://github.com/catduckgnaf/rtl_433_haos_addon">rtl_433</a>. You should see an output similar to:</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber 0" class="language-text line-numbers"><code class="language-text">Starting rtl_433 with rtl_433.conf located in /config/rtl_433
rtl_433 version 24.10-3-g4fa37568 branch master at 202411051225 inputs file rtl_tcp RTL-SDR
New defaults active, use "-Y classic -s 250k" if you need the old defaults
MQTT: Publishing MQTT data to core-mosquitto port 1883
MQTT: Publishing device info to MQTT topic "rtl_433/bresser51".
Protocols: Registered 2 out of 266 device decoding protocols [ 172 172 ]
Input: The internals of input handling changed, read about and report problems on PR #1978
SDR: Found 1 device(s)
SDR: trying device 0: Realtek, RTL2838UHIDIR, SN: 00000001
Detached kernel driver
Found Rafael Micro R820T tuner
SDR: Using device 0: Realtek, RTL2838UHIDIR, SN: 00000001, "Generic RTL2832U OEM"
Exact sample rate is: 250000.000414 Hz
[R82XX] PLL not locked!
SDR: Sample rate set to 250000 S/s.
Input: Bit detection level set to 0.0 (Auto).
SDR: Tuner gain set to Auto.
Input: Reading samples in async mode...
SDR: Tuned to 868.300MHz.
MQTT: MQTT Connected...
MQTT: MQTT Connection established.
Baseband: low pass filter for 250000 Hz at cutoff 50000 Hz, 20.0 us</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Lines 4, 5: Configured to forward any data it receives to MQTT broker</li>
<li>Lines 20, 21: Connection to MQTT broker established</li>
<li>Line 6: Configured protocol <code class="language-text">172</code> enabled</li>
<li>Lines 8, 9, 11, 12: USB dongle detected</li>
<li>Line 19: Listening to configured frequency 868.3 MHz</li>
</ul>
<p>You should also see an output similar to the previous one during the test:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">   time      : &lt;time>
   model     : Bresser-6in1 id        : &lt;id>
   channel   : 0            Battery   : 1             Temperature: 14.5 C
   Humidity  : 61           Sensor type: 1            Wind Gust : 0.0 m/s
   Wind Speed: 0.0 m/s      Direction : 90            UV        : 0.0
   Flags     : 0            Integrity : CRC</code></pre></div>
<p>Which means the dongle is receiving data from the weather station.</p>
<h3>Configure sensors in Home Assistant</h3>
<p>Edit <code class="language-text">/homeassistant/configuration.yml</code> with the <a href="https://github.com/home-assistant/addons/tree/master/configurator">File editor</a> add-on and <strong>append</strong>:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">mqtt</span><span class="token punctuation">:</span>
  <span class="token key atrule">sensor</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Temperature
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/temperature_C
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_temperature_C
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | float }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'°C'</span>
      <span class="token key atrule">device_class</span><span class="token punctuation">:</span> temperature

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Humidity
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/humidity
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_humidity
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | int }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'%'</span>
      <span class="token key atrule">device_class</span><span class="token punctuation">:</span> humidity

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Gust
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/wind_max_m_s
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_max_m_s
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | float }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> m/s
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>windy

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Speed
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/wind_avg_m_s
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_avg_m_s
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | float }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> m/s
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>windy

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Direction
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/wind_dir_deg
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_dir_deg
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | int }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> <span class="token string">'°'</span>
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>compass<span class="token punctuation">-</span>rose

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/rain_mm
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_mm
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | float }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Time
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/time
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_time
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>clock<span class="token punctuation">-</span>outline

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 UV
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/uv
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_uv
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | int }}'</span>
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>white<span class="token punctuation">-</span>balance<span class="token punctuation">-</span>sunny

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Light
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/light_lux
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_light_lux
      <span class="token key atrule">value_template</span><span class="token punctuation">:</span> <span class="token string">'{{ value | int }}'</span>
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> lux
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>lightbulb<span class="token punctuation">-</span>outline

  <span class="token key atrule">binary_sensor</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Battery
      <span class="token key atrule">state_topic</span><span class="token punctuation">:</span> rtl_433/bresser51/battery_ok
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_battery_ok
      <span class="token key atrule">payload_on</span><span class="token punctuation">:</span> <span class="token number">0</span>
      <span class="token key atrule">payload_off</span><span class="token punctuation">:</span> <span class="token number">1</span>
      <span class="token key atrule">device_class</span><span class="token punctuation">:</span> battery

<span class="token key atrule">sql</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 1 Minute Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>1 minute')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 5 Minutes Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>5 minutes')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 1 Hour Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>1 hour')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 24 Hours Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>24 hours')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 48 Hours Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>48 hours')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 7 Days Cumulative
    <span class="token key atrule">query</span><span class="token punctuation">:</span> <span class="token punctuation">></span><span class="token punctuation">-</span>
      SELECT state
      FROM states
      INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
      WHERE states_meta.entity_id = 'sensor.bresser51_rain'
      AND last_updated_ts &lt;= strftime('%s'<span class="token punctuation">,</span> <span class="token string">'now'</span><span class="token punctuation">,</span> '<span class="token punctuation">-</span>7 days')
      ORDER BY last_updated_ts DESC
      LIMIT 1
    <span class="token key atrule">column</span><span class="token punctuation">:</span> state
    <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
    <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy

<span class="token key atrule">template</span><span class="token punctuation">:</span>
  <span class="token key atrule">sensor</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Speed km/h
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_speed_kmh
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> km/h
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>windsock
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ (states('sensor.bresser51_wind_speed') | float) * 3.6 }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Speed Beaufort
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_speed_bft
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> bft
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>windsock
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ (((states('sensor.bresser51_wind_speed') | float) / 0.836) ** (2/3)) | round(0, 'ceil') }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Gust km/h
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_gust_kmh
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> km/h
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>windsock
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ (states('sensor.bresser51_wind_gust') | float) * 3.6 }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Gust Beaufort
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_gust_bft
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> bft
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>windsock
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ (((states('sensor.bresser51_wind_gust') | float) / 0.836) ** (2/3)) | round(0, 'ceil') }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Wind Direction Name
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_wind_direction_name
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>compass<span class="token punctuation">-</span>rose
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {% set direction = "N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW N".split() %}
        {% set sector = ((states('sensor.bresser51_wind_direction') | int) / 22) | int % 16 %}
        {{ direction[sector] }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 1 Minute
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_1_minute
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_1_minute_cumulative') | float)) | round(1) }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 5 Minutes
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_5_minutes
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_5_minutes_cumulative') | float)) | round(1) }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 1 Hour
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_1_hour
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_1_hour_cumulative') | float)) | round(1) }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 24 Hours
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_24_hours
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_24_hours_cumulative') | float)) | round(1) }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 48 Hours
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_48_hours
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_48_hours_cumulative') | float)) | round(1) }}</span>

    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Bresser51 Rain 7 Days
      <span class="token key atrule">unique_id</span><span class="token punctuation">:</span> bresser51_rain_7_days
      <span class="token key atrule">unit_of_measurement</span><span class="token punctuation">:</span> mm
      <span class="token key atrule">icon</span><span class="token punctuation">:</span> mdi<span class="token punctuation">:</span>weather<span class="token punctuation">-</span>rainy
      <span class="token key atrule">state</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        {{ ((states('sensor.bresser51_rain') | float) - (states('sensor.bresser51_rain_7_days_cumulative') | float)) | round(1) }}</span></code></pre></div>
<p><em>Adjusted for the Northern hemisphere and latest <a href="https://www.home-assistant.io/">Home Assistant</a> versions (Core 2024.11.0, Supervisor 2024.11.2, Operating System 13.2, Frontend 20241106.0), based on the <a href="https://www.vromans.org/johan/articles/hass_bresser51/index.html">great article by Johan Vromans</a></em></p>
<p>This will eventually end up creating the following, self-explanatory sensors:</p>
<ul>
<li><code class="language-text">sensor.bresser51_temperature</code> (°C)</li>
<li><code class="language-text">sensor.bresser51_humidity</code> (%)</li>
<li><code class="language-text">sensor.bresser51_wind_speed</code> (m/s)</li>
<li><code class="language-text">sensor.bresser51_wind_speed_kmh</code> (km/h)</li>
<li><code class="language-text">sensor.bresser51_wind_speed_beaufort</code> (bft)</li>
<li><code class="language-text">sensor.bresser51_wind_gust</code> (m/s)</li>
<li><code class="language-text">sensor.bresser51_wind_gust_kmh</code> (km/h)</li>
<li><code class="language-text">sensor.bresser51_wind_gust_beaufort</code> (bft)</li>
<li><code class="language-text">sensor.bresser51_wind_direction_name</code> (N, S, W, E, etc)</li>
<li><code class="language-text">sensor.bresser51_rain</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_1_minute</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_5_minutes</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_1_hour</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_24_hours</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_48_hours</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_rain_7_days</code> (mm)</li>
<li><code class="language-text">sensor.bresser51_time</code></li>
<li><code class="language-text">sensor.bresser51_uv</code></li>
<li><code class="language-text">sensor.bresser51_light</code> (lux)</li>
<li><code class="language-text">binary_sensor.bresser51_battery</code> (<code class="language-text">"on"</code> if the battery is low)</li>
</ul>
<p>Note that the amount of rainfall sent by the weather station (<code class="language-text">sensor.bresser51_rain</code>) is cumulative. All other rainfall sensors are differential (i.e. amount of rain in the last 5 minutes, hour, day, etc)</p>
<h3>Create sensors</h3>
<p>Restart <a href="https://www.home-assistant.io/">Home Assistant</a> to reload the configuration.</p>
<p>Install the <a href="https://www.home-assistant.io/integrations/mqtt/">MQTT integration</a> which will create the sensors.</p>
<h3>Display in dashboard</h3>
<p>Here’s the vertical-stack card YAML I use in my dashboard to display weather data:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">type</span><span class="token punctuation">:</span> vertical<span class="token punctuation">-</span>stack
<span class="token key atrule">title</span><span class="token punctuation">:</span> Weather station
<span class="token key atrule">cards</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">graph</span><span class="token punctuation">:</span> line
    <span class="token key atrule">type</span><span class="token punctuation">:</span> sensor
    <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_temperature
    <span class="token key atrule">detail</span><span class="token punctuation">:</span> <span class="token number">2</span>
    <span class="token key atrule">unit</span><span class="token punctuation">:</span> ⁰C
    <span class="token key atrule">hours_to_show</span><span class="token punctuation">:</span> <span class="token number">48</span>
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Temperature
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> gauge
    <span class="token key atrule">name</span><span class="token punctuation">:</span> Humidity
    <span class="token key atrule">unit</span><span class="token punctuation">:</span> <span class="token string">'%'</span>
    <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_humidity
    <span class="token key atrule">max</span><span class="token punctuation">:</span> <span class="token number">100</span>
    <span class="token key atrule">min</span><span class="token punctuation">:</span> <span class="token number">0</span>
    <span class="token key atrule">needle</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> glance
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_wind_direction_name
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Direction
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_wind_beaufort
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Speed
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_wind_gust_beaufort
        <span class="token key atrule">name</span><span class="token punctuation">:</span> Gust
  <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> glance
    <span class="token key atrule">entities</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_1_minute
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 1 Minute
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_5_minutes
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 5 Minutes
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_1_hour
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 1 Hour
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_24_hours
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 24 Hours
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_48_hours
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 48 Hours
      <span class="token punctuation">-</span> <span class="token key atrule">entity</span><span class="token punctuation">:</span> sensor.bresser51_rain_7_days
        <span class="token key atrule">name</span><span class="token punctuation">:</span> 7 Days</code></pre></div>
<p>Have fun!</p>
<h3>Troubleshooting</h3>
<p>In case something does not work:</p>
<ul>
<li>Check add-on logs for any error messages</li>
<li>Verify <a href="https://github.com/home-assistant/addons/tree/master/mosquitto">Mosquitto broker</a> receives data from <a href="https://github.com/catduckgnaf/rtl_433_haos_addon">rtl_433</a> with <a href="https://community.home-assistant.io/t/addon-mqtt-explorer-new-version/603739">MQTT Explorer</a> (community add-on)</li>
<li>Try restarting (not only reloading) <a href="https://www.home-assistant.io/">Home Assistant</a></li>
</ul>]]></description><link>https://angelos.dev//2024/11/bresser-weather-station-in-home-assistant/</link><guid isPermaLink="false">https://angelos.dev//2024/11/bresser-weather-station-in-home-assistant/</guid><pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Switch terminal theme based on location day/night]]></title><description><![CDATA[<p>I recently switched from <a href="https://code.visualstudio.com/">VS Code</a>, <a href="https://ethanschoonover.com/solarized/">Solarized</a>, <a href="https://github.com/source-foundry/Hack">Hack</a> and <a href="https://wiki.archlinux.org/title/rxvt-unicode">rxvt-unicode</a> to <a href="http://www.lazyvim.org/">LazyVim</a>, <a href="https://github.com/folke/tokyonight.nvim">TokyoNight</a>, <a href="https://github.com/tonsky/FiraCode">FiraCode</a> and <a href="https://sw.kovidgoyal.net/kitty/">kitty</a>.</p>
<p>My previous setup would <a href="https://github.com/agorf/urxvt-solarized">auto-switch</a> between the light and dark <a href="https://ethanschoonover.com/solarized/">theme</a> on the <a href="https://wiki.archlinux.org/title/rxvt-unicode">terminal</a> and the <a href="https://code.visualstudio.com/">editor</a> based on the time of day for <a href="https://en.wikipedia.org/wiki/Athens">my location</a> and whether it was day or night. I’ve found this is easier on my eyes.</p>
<p>I wanted to replicate this functionality with the new setup, so I came up with the following <a href="https://www.ruby-lang.org/">Ruby</a> script that queries (and caches the response of) an <a href="https://sunrisesunset.io/api/">API</a> for the sunrise and sunset time of a specified location and symlinks the right theme file based on whether it is currently day or night.</p>
<p>Note: I intentionally chose not to use a <a href="https://github.com/mikereedell/sunrisesunset-ruby">Gem</a> to calculate the sunrise/sunset time to keep the script dependency-free.</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'fileutils'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'json'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'net/http'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'uri'</span></span>

<span class="token keyword">if</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>empty<span class="token operator">?</span>
  warn <span class="token string-literal"><span class="token string">"Usage: LAT=&lt;latitude_degrees> LON=&lt;longitude_degrees> </span><span class="token interpolation"><span class="token delimiter punctuation">#{</span><span class="token content">$<span class="token number">0</span></span><span class="token delimiter punctuation">}</span></span><span class="token string"> &lt;day_file_path> &lt;night_file_path> &lt;symlink_path>"</span></span>
  exit <span class="token number">1</span>
<span class="token keyword">end</span>

<span class="token constant">API_URL_FMT</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'https://api.sunrisesunset.io/json?lat=%f&amp;lng=%f&amp;time_format=24'</span></span><span class="token punctuation">.</span>freeze
<span class="token constant">CACHE_PATH_FMT</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'/tmp/sunrise-sunset-cache-%s-%s.json'</span></span><span class="token punctuation">.</span>freeze

<span class="token constant">LATITUDE</span> <span class="token operator">=</span> <span class="token constant">ENV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'LAT'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>freeze <span class="token comment"># Latitude of area in degrees North</span>
<span class="token constant">LONGITUDE</span> <span class="token operator">=</span> <span class="token constant">ENV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'LON'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>freeze <span class="token comment"># Longitude of area in degrees East</span>
<span class="token constant">DAY_PATH</span> <span class="token operator">=</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment"># Path to day theme file</span>
<span class="token constant">NIGHT_PATH</span> <span class="token operator">=</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment"># Path to night theme file</span>
<span class="token constant">DEST_PATH</span> <span class="token operator">=</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token comment"># Path to end-result theme file (symbolic link)</span>

cache_path <span class="token operator">=</span> format<span class="token punctuation">(</span><span class="token constant">CACHE_PATH_FMT</span><span class="token punctuation">,</span> <span class="token constant">LATITUDE</span><span class="token punctuation">,</span> <span class="token constant">LONGITUDE</span><span class="token punctuation">)</span>

<span class="token comment"># Remove if outdated</span>
<span class="token keyword">if</span> <span class="token builtin">File</span><span class="token punctuation">.</span>exist<span class="token operator">?</span><span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin">File</span><span class="token punctuation">.</span>mtime<span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span><span class="token punctuation">.</span>to_date <span class="token operator">!=</span> Date<span class="token punctuation">.</span>today
  FileUtils<span class="token punctuation">.</span>rm_f<span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span>
<span class="token keyword">end</span>

data <span class="token operator">=</span>
  <span class="token keyword">begin</span>
    <span class="token constant">JSON</span><span class="token punctuation">.</span>load<span class="token punctuation">(</span><span class="token builtin">File</span><span class="token punctuation">.</span>open<span class="token punctuation">(</span>json_cache_path<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token keyword">rescue</span> Errno<span class="token double-colon punctuation">::</span><span class="token constant">ENOENT</span>
    url <span class="token operator">=</span> format<span class="token punctuation">(</span><span class="token constant">API_URL_FMT</span><span class="token punctuation">,</span> <span class="token constant">LATITUDE</span><span class="token punctuation">,</span> <span class="token constant">LONGITUDE</span><span class="token punctuation">)</span>
    uri <span class="token operator">=</span> <span class="token constant">URI</span><span class="token punctuation">.</span>parse<span class="token punctuation">(</span>url<span class="token punctuation">)</span>
    response <span class="token operator">=</span> Net<span class="token double-colon punctuation">::</span><span class="token constant">HTTP</span><span class="token punctuation">.</span>get_response<span class="token punctuation">(</span>uri<span class="token punctuation">)</span>
    <span class="token builtin">File</span><span class="token punctuation">.</span>open<span class="token punctuation">(</span>json_cache_path<span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">'w'</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">|</span>f<span class="token operator">|</span> f<span class="token punctuation">.</span>write<span class="token punctuation">(</span>response<span class="token punctuation">.</span>body<span class="token punctuation">)</span> <span class="token punctuation">}</span>

    <span class="token constant">JSON</span><span class="token punctuation">.</span>parse<span class="token punctuation">(</span>response<span class="token punctuation">.</span>body<span class="token punctuation">)</span>
  <span class="token keyword">end</span>

sunrise<span class="token punctuation">,</span> sunset <span class="token operator">=</span> data<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'results'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>values_at<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'dawn'</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">'dusk'</span></span><span class="token punctuation">)</span>
now <span class="token operator">=</span> <span class="token builtin">Time</span><span class="token punctuation">.</span>now
sunrise_time <span class="token operator">=</span> <span class="token class-name">Time</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span>year<span class="token punctuation">,</span> now<span class="token punctuation">.</span>month<span class="token punctuation">,</span> now<span class="token punctuation">.</span>day<span class="token punctuation">,</span> <span class="token operator">*</span>sunrise<span class="token punctuation">.</span>split<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">':'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>map<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token symbol">:to_i</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
sunset_time <span class="token operator">=</span> <span class="token class-name">Time</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span>year<span class="token punctuation">,</span> now<span class="token punctuation">.</span>month<span class="token punctuation">,</span> now<span class="token punctuation">.</span>day<span class="token punctuation">,</span> <span class="token operator">*</span>sunset<span class="token punctuation">.</span>split<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">':'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>map<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token symbol">:to_i</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

theme_path <span class="token operator">=</span>
  <span class="token keyword">if</span> now <span class="token operator">></span> sunrise_time <span class="token operator">&amp;&amp;</span> now <span class="token operator">&lt;</span> sunset_time
    <span class="token constant">DAY_PATH</span>
  <span class="token keyword">else</span>
    <span class="token constant">NIGHT_PATH</span>
  <span class="token keyword">end</span>

FileUtils<span class="token punctuation">.</span>ln_s<span class="token punctuation">(</span>theme_path<span class="token punctuation">,</span> <span class="token constant">DEST_PATH</span><span class="token punctuation">,</span> <span class="token symbol">force</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span></code></pre></div>
<p>I then proceeded to rewrite it in a more object-oriented approach, with <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">object composition</a> and <a href="https://en.wikipedia.org/wiki/Single-responsibility_principle">separated concerns</a> among classes and methods (drawing inspiration from the <a href="https://sandimetz.com/products">POODR</a> book):</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'date'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'fileutils'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'json'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'net/http'</span></span>
<span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'uri'</span></span>

<span class="token keyword">module</span> <span class="token class-name">DayNight</span>
  <span class="token keyword">class</span> <span class="token class-name">FileCacher</span>
    attr_reader <span class="token symbol">:cache_path</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span><span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span>
      <span class="token variable">@cache_path</span> <span class="token operator">=</span> cache_path
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">outdated_cache</span></span><span class="token operator">?</span>
      <span class="token builtin">File</span><span class="token punctuation">.</span>exist<span class="token operator">?</span><span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin">File</span><span class="token punctuation">.</span>mtime<span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span><span class="token punctuation">.</span>to_date <span class="token operator">!=</span> Date<span class="token punctuation">.</span>today
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">bust_cache</span></span>
      FileUtils<span class="token punctuation">.</span>rm_f<span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">write_cache</span></span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
      <span class="token builtin">File</span><span class="token punctuation">.</span>open<span class="token punctuation">(</span>cache_path<span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">'w'</span></span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>f<span class="token operator">|</span>
        f<span class="token punctuation">.</span>write<span class="token punctuation">(</span>data<span class="token punctuation">)</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">call</span></span>
      bust_cache <span class="token keyword">if</span> outdated_cache<span class="token operator">?</span>

      <span class="token builtin">File</span><span class="token punctuation">.</span>read<span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span>
    <span class="token keyword">rescue</span> Errno<span class="token double-colon punctuation">::</span><span class="token constant">ENOENT</span>
      <span class="token keyword">yield</span><span class="token punctuation">.</span>tap <span class="token keyword">do</span> <span class="token operator">|</span>data<span class="token operator">|</span>
        write_cache<span class="token punctuation">(</span>data<span class="token punctuation">)</span>
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>

  <span class="token keyword">class</span> <span class="token class-name">DataFetcher</span>
    <span class="token constant">CACHE_PATH_FMT</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'/tmp/sunrise-sunset-cache-%s-%s.json'</span></span><span class="token punctuation">.</span>freeze
    <span class="token constant">API_URL_FMT</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'https://api.sunrisesunset.io/json?lat=%f&amp;lng=%f&amp;time_format=24'</span></span><span class="token punctuation">.</span>freeze

    attr_reader <span class="token symbol">:latitude</span><span class="token punctuation">,</span> <span class="token symbol">:longitude</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span><span class="token punctuation">(</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">)</span>
      <span class="token variable">@latitude</span> <span class="token operator">=</span> latitude
      <span class="token variable">@longitude</span> <span class="token operator">=</span> longitude
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">cache_path</span></span>
      format<span class="token punctuation">(</span><span class="token constant">CACHE_PATH_FMT</span><span class="token punctuation">,</span> latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">url</span></span>
      format<span class="token punctuation">(</span><span class="token constant">API_URL_FMT</span><span class="token punctuation">,</span> latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">response</span></span>
      uri <span class="token operator">=</span> <span class="token constant">URI</span><span class="token punctuation">.</span>parse<span class="token punctuation">(</span>url<span class="token punctuation">)</span>
      Net<span class="token double-colon punctuation">::</span><span class="token constant">HTTP</span><span class="token punctuation">.</span>get_response<span class="token punctuation">(</span>uri<span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">data</span></span>
      <span class="token class-name">FileCacher</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>cache_path<span class="token punctuation">)</span><span class="token punctuation">.</span>call <span class="token keyword">do</span>
        response<span class="token punctuation">.</span>body
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">call</span></span>
      <span class="token constant">JSON</span><span class="token punctuation">.</span>parse<span class="token punctuation">(</span>data<span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>

  <span class="token keyword">class</span> <span class="token class-name">Calculator</span>
    attr_reader <span class="token symbol">:data</span><span class="token punctuation">,</span> <span class="token symbol">:now</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
      <span class="token variable">@data</span> <span class="token operator">=</span> data
      <span class="token variable">@now</span> <span class="token operator">=</span> <span class="token builtin">Time</span><span class="token punctuation">.</span>now
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">to_time</span></span><span class="token punctuation">(</span>time_str<span class="token punctuation">)</span>
      <span class="token class-name">Time</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>now<span class="token punctuation">.</span>year<span class="token punctuation">,</span> now<span class="token punctuation">.</span>month<span class="token punctuation">,</span> now<span class="token punctuation">.</span>day<span class="token punctuation">,</span> <span class="token operator">*</span>time_str<span class="token punctuation">.</span>split<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">':'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>map<span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token symbol">:to_i</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">sunrise_time</span></span>
      to_time<span class="token punctuation">(</span>data<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'results'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'dawn'</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">sunset_time</span></span>
      to_time<span class="token punctuation">(</span>data<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'results'</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'dusk'</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">day</span></span><span class="token operator">?</span>
      sunrise_time <span class="token operator">&lt;</span> now <span class="token operator">&amp;&amp;</span> now <span class="token operator">&lt;</span> sunset_time
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>

  <span class="token keyword">class</span> <span class="token class-name">Symlinker</span>
    attr_reader <span class="token symbol">:day_path</span><span class="token punctuation">,</span> <span class="token symbol">:night_path</span><span class="token punctuation">,</span> <span class="token symbol">:dest_path</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span><span class="token punctuation">(</span><span class="token symbol">day_path</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">night_path</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">dest_path</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">is_day</span><span class="token operator">:</span><span class="token punctuation">)</span>
      <span class="token variable">@day_path</span> <span class="token operator">=</span> day_path
      <span class="token variable">@night_path</span> <span class="token operator">=</span> night_path
      <span class="token variable">@dest_path</span> <span class="token operator">=</span> dest_path
      <span class="token variable">@is_day</span> <span class="token operator">=</span> is_day
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">day</span></span><span class="token operator">?</span>
      <span class="token variable">@is_day</span>
    <span class="token keyword">end</span>

    <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">source_path</span></span>
      <span class="token keyword">if</span> day<span class="token operator">?</span>
        day_path
      <span class="token keyword">else</span>
        night_path
      <span class="token keyword">end</span>
    <span class="token keyword">end</span>

    <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">call</span></span>
      FileUtils<span class="token punctuation">.</span>ln_s<span class="token punctuation">(</span>source_path<span class="token punctuation">,</span> dest_path<span class="token punctuation">,</span> <span class="token symbol">force</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">symlink</span></span><span class="token punctuation">(</span><span class="token symbol">latitude</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">longitude</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">day_path</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">night_path</span><span class="token operator">:</span><span class="token punctuation">,</span> <span class="token symbol">dest_path</span><span class="token operator">:</span><span class="token punctuation">)</span>
    data <span class="token operator">=</span> <span class="token class-name">DataFetcher</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">)</span><span class="token punctuation">.</span>call

    <span class="token class-name">Symlinker</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>
      <span class="token symbol">day_path</span><span class="token operator">:</span> day_path<span class="token punctuation">,</span>
      <span class="token symbol">night_path</span><span class="token operator">:</span> night_path<span class="token punctuation">,</span>
      <span class="token symbol">dest_path</span><span class="token operator">:</span> dest_path<span class="token punctuation">,</span>
      <span class="token symbol">is_day</span><span class="token operator">:</span> <span class="token class-name">Calculator</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span>day<span class="token operator">?</span>
    <span class="token punctuation">)</span><span class="token punctuation">.</span>call
  <span class="token keyword">end</span>
<span class="token keyword">end</span>

<span class="token keyword">if</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>empty<span class="token operator">?</span>
  warn <span class="token string-literal"><span class="token string">"Usage: LAT=&lt;latitude_degrees> LON=&lt;longitude_degrees> </span><span class="token interpolation"><span class="token delimiter punctuation">#{</span><span class="token content">$<span class="token number">0</span></span><span class="token delimiter punctuation">}</span></span><span class="token string"> &lt;day_file_path> &lt;night_file_path> &lt;symlink_path>"</span></span>
  exit <span class="token number">1</span>
<span class="token keyword">end</span>

DayNight<span class="token punctuation">.</span>symlink<span class="token punctuation">(</span>
  <span class="token symbol">latitude</span><span class="token operator">:</span> <span class="token constant">ENV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'LAT'</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment"># Latitude of area in degrees North</span>
  <span class="token symbol">longitude</span><span class="token operator">:</span> <span class="token constant">ENV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'LON'</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment"># Longitude of area in degrees East</span>
  <span class="token symbol">day_path</span><span class="token operator">:</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment"># Path to day file</span>
  <span class="token symbol">night_path</span><span class="token operator">:</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment"># Path to night file</span>
  <span class="token symbol">dest_path</span><span class="token operator">:</span> <span class="token constant">ARGV</span><span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token comment"># Path to destination file (symbolic link)</span>
<span class="token punctuation">)</span></code></pre></div>
<p>I run it with <a href="https://crontab.guru/">cron</a> every 5 minutes (output of <code class="language-text">crontab -l</code>):</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">*/5 * * * * <span class="token assign-left variable">LAT</span><span class="token operator">=</span><span class="token number">37.9838</span> <span class="token assign-left variable">LON</span><span class="token operator">=</span><span class="token number">23.7275</span> ruby ~/work/scripts/symlink-day-night.rb ~/src/tokyonight.nvim/extras/kitty/tokyonight_day.conf ~/src/tokyonight.nvim/extras/kitty/tokyonight_night.conf ~/.config/kitty/theme.conf</code></pre></div>
<p>And in my <a href="https://sw.kovidgoyal.net/kitty/">kitty</a> <a href="https://sw.kovidgoyal.net/kitty/conf/">config</a> I simply reference the symlinked theme:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="ini"><pre class="language-ini"><code class="language-ini">font_family FiraCode
font_size 14
update_check_interval 0
<span class="gatsby-highlight-code-line">include theme.conf</span></code></pre></div>]]></description><link>https://angelos.dev//2024/11/switch-terminal-theme-based-on-a-locations-day-night/</link><guid isPermaLink="false">https://angelos.dev//2024/11/switch-terminal-theme-based-on-a-locations-day-night/</guid><pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Abort Capistrano deploy with unpushed Git commits]]></title><description><![CDATA[<p>To have <a href="https://capistranorb.com/">Capistrano</a> abort the deploy when there are unpushed commits in
<a href="https://git-scm.com/">Git</a>, add the following block in <code class="language-text">config/deploy.rb</code>:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby">before <span class="token symbol">:deploy</span><span class="token punctuation">,</span> <span class="token symbol">:abort_with_unpushed_changes</span> <span class="token keyword">do</span>
  run_locally <span class="token keyword">do</span>
    <span class="token keyword">if</span> <span class="token operator">!</span>test<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'git merge-base --is-ancestor @ @{u}'</span></span><span class="token punctuation">)</span>
      error <span class="token string-literal"><span class="token string">'Push changes first'</span></span>
      <span class="token keyword">raise</span> <span class="token string-literal"><span class="token string">'Unpushed changes'</span></span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code></pre></div>]]></description><link>https://angelos.dev//2022/06/abort-capistrano-deploy-with-unpushed-git-commits/</link><guid isPermaLink="false">https://angelos.dev//2022/06/abort-capistrano-deploy-with-unpushed-git-commits/</guid><pubDate>Sat, 04 Jun 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[GoToCoordinates component for React Leaflet]]></title><description><![CDATA[<p><a href="https://react-leaflet.js.org/">React Leaflet</a> is a <a href="https://reactjs.org/">React</a> library that exposes <a href="https://leafletjs.com/">Leaflet</a> classes as
React components, making it very easy to add interactive maps to React web apps.</p>
<p>This is the fourth in a <a href="/2021/07/mouse-coordinates-component-for-react-leaflet/">series of posts</a> on how I use the library. My
intention is to share back some of the things I’ve learned and implemented in
the hope of them being useful to others.</p>
<p>In this post, I present the <code class="language-text">GoToCoordinates</code> component which accepts a pair of
latitude and longitude coordinates and centers the map, drawing a marker on the
exact point. You can see it in action on <a href="https://vouna.gr/">vouna.gr</a>.</p>
<p>First, the component:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> PropTypes <span class="token keyword">from</span> <span class="token string">'prop-types'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useMap <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-leaflet'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token constant">MAP_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// Greece</span>
<span class="token keyword">const</span> <span class="token constant">MIN_ZOOM</span> <span class="token operator">=</span> <span class="token number">13</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">PLACEHOLDER</span> <span class="token operator">=</span> <span class="token string">'37.984167, 23.728056'</span><span class="token punctuation">;</span> <span class="token comment">// Athens, center</span>
<span class="token keyword">const</span> <span class="token constant">COORDINATES_REGEX</span> <span class="token operator">=</span>
  <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\s*-?(90|[1-8]?[0-9])(\.\d+)?\s*°?(\s*,\s*|\s+)-?(180|1[0-7][0-9]|[1-9]?[0-9])(\.\d+)?\s*°?\s*$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">coordinatesWithinBounds</span><span class="token punctuation">(</span><span class="token parameter">latitude<span class="token punctuation">,</span> longitude</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span><span class="token punctuation">[</span>minLat<span class="token punctuation">,</span> minLon<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>maxLat<span class="token punctuation">,</span> maxLon<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token constant">MAP_BOUNDS</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    latitude <span class="token operator">>=</span> minLat <span class="token operator">&amp;&amp;</span>
    latitude <span class="token operator">&lt;=</span> maxLat <span class="token operator">&amp;&amp;</span>
    longitude <span class="token operator">>=</span> minLon <span class="token operator">&amp;&amp;</span>
    longitude <span class="token operator">&lt;=</span> maxLon
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">GoToCoordinates</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> setPointCoordinates<span class="token punctuation">,</span> clearPointCoordinates <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">;</span>

  <span class="token keyword">const</span> <span class="token punctuation">[</span>coordinates<span class="token punctuation">,</span> setCoordinates<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> map <span class="token operator">=</span> <span class="token function">useMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">function</span> <span class="token function">handleCoordinatesChange</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">target</span><span class="token operator">:</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span> <span class="token operator">=</span> event<span class="token punctuation">;</span>

    <span class="token function">setCoordinates</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token constant">COORDINATES_REGEX</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      <span class="token function">clearPointCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">      <span class="token keyword">return</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span></span>
    <span class="token keyword">const</span> <span class="token punctuation">[</span>latitudeStr<span class="token punctuation">,</span> longitudeStr<span class="token punctuation">]</span> <span class="token operator">=</span> value<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\s*,\s*</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> latitude <span class="token operator">=</span> <span class="token function">parseFloat</span><span class="token punctuation">(</span>latitudeStr<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> longitude <span class="token operator">=</span> <span class="token function">parseFloat</span><span class="token punctuation">(</span>longitudeStr<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">coordinatesWithinBounds</span><span class="token punctuation">(</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      <span class="token function">clearPointCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">      <span class="token keyword">return</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span></span>
    <span class="token function">setPointCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> zoom <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token constant">MIN_ZOOM</span><span class="token punctuation">,</span> map<span class="token punctuation">.</span><span class="token function">getZoom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    map<span class="token punctuation">.</span><span class="token function">setView</span><span class="token punctuation">(</span><span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span><span class="token punctuation">,</span> zoom<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">function</span> <span class="token function">handleClearChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setCoordinates</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">clearPointCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
        <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
        <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>15<span class="token punctuation">"</span></span>
        <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Go to coordinates<span class="token punctuation">"</span></span>
        <span class="token attr-name">placeholder</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">PLACEHOLDER</span><span class="token punctuation">}</span></span>
        <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>coordinates<span class="token punctuation">}</span></span>
        <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleCoordinatesChange<span class="token punctuation">}</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">      </span><span class="token punctuation">{</span>coordinates<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span></span><span class="gatsby-highlight-code-line">        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleClearChange<span class="token punctuation">}</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Καθαρισμός<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">          &amp;times;</span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></span><span class="gatsby-highlight-code-line">      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"></span></span><span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

GoToCoordinates<span class="token punctuation">.</span>propTypes <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">setPointCoordinates</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>func<span class="token punctuation">.</span>isRequired<span class="token punctuation">,</span>
  <span class="token literal-property property">clearPointCoordinates</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>func<span class="token punctuation">.</span>isRequired<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> GoToCoordinates<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Lines 36-39: If text is empty or not a valid coordinate pair, the marker is
removed</li>
<li>Lines 45-48: If text is a valid coordinate pair but not within the desired
bounds (in this example, Greece), the marker is removed</li>
<li>Lines 71-75: If text is present, a clear “x” button is shown that empties the
text field and removes the marker when clicked</li>
</ul>
<p>Then, the necessary styles for the clear “x” button:</p>
<div class="gatsby-highlight" data-language="css"><pre class="language-css"><code class="language-css"><span class="token selector">.leaflet-control.go-to-coordinates button</span> <span class="token punctuation">{</span>
  <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token property">background</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span>
  <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
  <span class="token property">margin-left</span><span class="token punctuation">:</span> -30px<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 30px<span class="token punctuation">;</span>
  <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  <span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span>
  <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
  <span class="token property">opacity</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>And finally, here’s how you’d use it in a map:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Marker <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-leaflet'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> GoToCoordinates <span class="token keyword">from</span> <span class="token string">'./GoToCoordinates'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token constant">MAP_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// Greece</span>

<span class="token keyword">function</span> <span class="token function">round</span><span class="token punctuation">(</span><span class="token parameter">number<span class="token punctuation">,</span> precision <span class="token operator">=</span> <span class="token number">0</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span>number <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> precision<span class="token punctuation">)</span> <span class="token operator">+</span> Number<span class="token punctuation">.</span><span class="token constant">EPSILON</span><span class="token punctuation">)</span> <span class="token operator">/</span>
    Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> precision<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">formatLatitude</span><span class="token punctuation">(</span><span class="token parameter">latitude</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> direction <span class="token operator">=</span> latitude <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'N'</span> <span class="token operator">:</span> <span class="token string">'S'</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">round</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>latitude<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">° </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>direction<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">formatLongitude</span><span class="token punctuation">(</span><span class="token parameter">longitude</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> direction <span class="token operator">=</span> longitude <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'E'</span> <span class="token operator">:</span> <span class="token string">'W'</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">round</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>longitude<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">° </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>direction<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">MyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>goToPoint<span class="token punctuation">,</span> setGoToPoint<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">function</span> <span class="token function">clearPointCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setGoToPoint</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MapContainer</span></span>
      <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">MAP_BOUNDS</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">'100vh'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
    <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TileLayer</span></span>
        <span class="token attr-name">url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://{s}.tile.osm.org/{z}/{x}/{y}.png<span class="token punctuation">"</span></span>
        <span class="token attr-name">attribution</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token entity named-entity" title="&copy;">&amp;copy;</span> &lt;a href="https://osm.org/copyright">OpenStreetMap&lt;/a> contributors<span class="token punctuation">'</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">      </span><span class="token punctuation">{</span>goToPoint <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span></span><span class="gatsby-highlight-code-line">        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Marker</span></span> <span class="token attr-name">position</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>goToPoint<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Tooltip</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">            </span><span class="token punctuation">{</span><span class="token function">formatLatitude</span><span class="token punctuation">(</span>goToPoint<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">, </span><span class="token punctuation">{</span><span class="token function">formatLongitude</span><span class="token punctuation">(</span>goToPoint<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">          </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Tooltip</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Marker</span></span><span class="token punctuation">></span></span></span><span class="gatsby-highlight-code-line">      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"></span></span><span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-bottom leaflet-left<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">GoToCoordinates</span></span></span><span class="gatsby-highlight-code-line">          <span class="token attr-name">setPointCoordinates</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setGoToPoint<span class="token punctuation">}</span></span></span><span class="gatsby-highlight-code-line">          <span class="token attr-name">clearPointCoordinates</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clearPointCoordinates<span class="token punctuation">}</span></span></span><span class="gatsby-highlight-code-line">        <span class="token punctuation">/></span></span><span class="token plain-text"></span></span><span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">MapContainer</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MyMap<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Lines 41-47: The marker is shown only if its coordinates are present. When
shown, its tooltip displays its formatted coordinates.</li>
<li>Lines 49-52: The component in action</li>
</ul>]]></description><link>https://angelos.dev//2021/10/go-to-coordinates-component-for-react-leaflet/</link><guid isPermaLink="false">https://angelos.dev//2021/10/go-to-coordinates-component-for-react-leaflet/</guid><pubDate>Sat, 02 Oct 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[MouseCoordinates component for React Leaflet]]></title><description><![CDATA[<p><a href="https://react-leaflet.js.org/">React Leaflet</a> is a <a href="https://reactjs.org/">React</a> library that exposes <a href="https://leafletjs.com/">Leaflet</a> classes as
React components, making it very easy to add interactive maps to React web apps.</p>
<p>This is the third in a <a href="/2021/07/bing-tile-layer-component-for-react-leaflet/">series of posts</a> on how I use the library. My
intention is to share back some of the things I’ve learned and implemented in
the hope of them being useful to others.</p>
<p>In this post, I present the <code class="language-text">MouseCoordinates</code> component which renders the
latitude and longitude that the mouse pointer hovers on and allows the user to
copy them by pressing Ctrl-C. You can see it in action on <a href="https://vouna.gr/">vouna.gr</a>.</p>
<p>First, the component:</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useMapEvents <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-leaflet'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">round</span><span class="token punctuation">(</span><span class="token parameter">number<span class="token punctuation">,</span> precision <span class="token operator">=</span> <span class="token number">0</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span>number <span class="token operator">*</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> precision<span class="token punctuation">)</span> <span class="token operator">+</span> Number<span class="token punctuation">.</span><span class="token constant">EPSILON</span><span class="token punctuation">)</span> <span class="token operator">/</span>
    Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> precision<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">formatLatitude</span><span class="token punctuation">(</span><span class="token parameter">latitude</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> direction <span class="token operator">=</span> latitude <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'N'</span> <span class="token operator">:</span> <span class="token string">'S'</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">round</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>latitude<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">° </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>direction<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">formatLongitude</span><span class="token punctuation">(</span><span class="token parameter">longitude</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> direction <span class="token operator">=</span> longitude <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'E'</span> <span class="token operator">:</span> <span class="token string">'W'</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">round</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>longitude<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">° </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>direction<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">MouseCoordinates</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>mousePoint<span class="token punctuation">,</span> setMousePoint<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> formattedCoordinates <span class="token operator">=</span>
    mousePoint <span class="token operator">===</span> <span class="token keyword">null</span>
      <span class="token operator">?</span> <span class="token string">''</span>
<span class="gatsby-highlight-code-line">      <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">formatLatitude</span><span class="token punctuation">(</span>mousePoint<span class="token punctuation">.</span>lat<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">formatLongitude</span><span class="token punctuation">(</span>mousePoint<span class="token punctuation">.</span>lng<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></span>
  React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span>
    <span class="token keyword">function</span> <span class="token function">copyToClipboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">function</span> <span class="token function">handleCtrlCKeydown</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>
<span class="gatsby-highlight-code-line">          event<span class="token punctuation">.</span>key <span class="token operator">===</span> <span class="token string">'c'</span> <span class="token operator">&amp;&amp;</span></span><span class="gatsby-highlight-code-line">          event<span class="token punctuation">.</span>ctrlKey <span class="token operator">&amp;&amp;</span></span>          formattedCoordinates<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span>
          navigator<span class="token punctuation">.</span>clipboard
        <span class="token punctuation">)</span> <span class="token punctuation">{</span>
          navigator<span class="token punctuation">.</span>clipboard<span class="token punctuation">.</span><span class="token function">writeText</span><span class="token punctuation">(</span>formattedCoordinates<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>

      document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'keydown'</span><span class="token punctuation">,</span> handleCtrlCKeydown<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">cleanup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        document<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'keydown'</span><span class="token punctuation">,</span> handleCtrlCKeydown<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">[</span>formattedCoordinates<span class="token punctuation">]</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">useMapEvents</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">mousemove</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">setMousePoint</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>latlng<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="gatsby-highlight-code-line">    <span class="token function">mouseout</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      <span class="token function">setMousePoint</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span><span class="token punctuation">,</span></span>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>formattedCoordinates<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span></span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="gatsby-highlight-code-line">    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control-attribution leaflet-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token plain-text"></span></span><span class="token plain-text">      </span><span class="token punctuation">{</span>formattedCoordinates<span class="token punctuation">}</span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MouseCoordinates<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 27: <code class="language-text">formatLatitude</code> and <code class="language-text">formatLongitude</code> are used to format the
coordinates</li>
<li>Lines 33-34: It is possible to copy the coordinates by pressing Ctrl-C</li>
<li>Lines 55-57, 60: When the mouse pointer leaves the map, no coordinates are
shown</li>
<li>Line 63: The component is agnostic regarding its corner/position in the map.
Instead, it is expected that you wrap it with a <code class="language-text">&lt;div></code> that has the desired
position classes.</li>
</ul>
<p>And here’s how you’d use it in a map:</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> MouseCoordinates <span class="token keyword">from</span> <span class="token string">'./MouseCoordinates'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token constant">GREECE_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">MyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MapContainer</span></span>
      <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">GREECE_BOUNDS</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">'100vh'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
    <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TileLayer</span></span>
        <span class="token attr-name">url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://{s}.tile.osm.org/{z}/{x}/{y}.png<span class="token punctuation">"</span></span>
        <span class="token attr-name">attribution</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token entity named-entity" title="&copy;">&amp;copy;</span> &lt;a href="https://osm.org/copyright">OpenStreetMap&lt;/a> contributors<span class="token punctuation">'</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-bottom leaflet-left<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token plain-text"></span></span><span class="token plain-text">        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MouseCoordinates</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">MapContainer</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MyMap<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 20: In this example, the coordinates are shown on the bottom-left corner
of the map</li>
</ul>]]></description><link>https://angelos.dev//2021/07/mouse-coordinates-component-for-react-leaflet/</link><guid isPermaLink="false">https://angelos.dev//2021/07/mouse-coordinates-component-for-react-leaflet/</guid><pubDate>Thu, 15 Jul 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[BingTileLayer component for React Leaflet]]></title><description><![CDATA[<p><strong>Update:</strong> I’ve since switched to <a href="/2025/05/esri-tile-layer-component-for-react-leaflet/">EsriTileLayer</a> since Bing Maps is being
discontinued in favor of Azure Maps.</p>
<p><a href="https://react-leaflet.js.org/">React Leaflet</a> is a <a href="https://reactjs.org/">React</a> library that exposes <a href="https://leafletjs.com/">Leaflet</a> classes as
React components, making it very easy to add interactive maps to React web apps.</p>
<p>This is the second in a <a href="/2021/07/zoom-control-with-reset-component-for-react-leaflet/">series of posts</a> on how I use the library. My
intention is to share back some of the things I’ve learned and implemented in
the hope of them being useful to others.</p>
<p>In this post, I present the <code class="language-text">BingTileLayer</code> component which exposes <a href="https://docs.microsoft.com/en-us/bingmaps/articles/bing-maps-tile-system">Bing Maps
tile layers</a> by wrapping <a href="https://github.com/digidem/leaflet-bing-layer">leaflet-bing-layer</a> as a React component.</p>
<p>First, the component:</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber 0" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createLayerComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@react-leaflet/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> bingTileLayer <span class="token keyword">from</span> <span class="token string">'leaflet-bing-layer'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> PropTypes <span class="token keyword">from</span> <span class="token string">'prop-types'</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line"><span class="token keyword">const</span> <span class="token constant">IMAGERY_SETS</span> <span class="token operator">=</span> <span class="token punctuation">[</span></span>  <span class="token string">'Aerial'</span><span class="token punctuation">,</span>
  <span class="token string">'AerialWithLabels'</span><span class="token punctuation">,</span>
  <span class="token string">'AerialWithLabelsOnDemand'</span><span class="token punctuation">,</span>
  <span class="token string">'CanvasDark'</span><span class="token punctuation">,</span>
  <span class="token string">'CanvasLight'</span><span class="token punctuation">,</span>
  <span class="token string">'CanvasGray'</span><span class="token punctuation">,</span>
  <span class="token string">'Road'</span><span class="token punctuation">,</span>
  <span class="token string">'RoadOnDemand'</span><span class="token punctuation">,</span>
  <span class="token string">'OrdnanceSurvey'</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line"><span class="token keyword">const</span> <span class="token constant">CULTURES</span> <span class="token operator">=</span> <span class="token punctuation">[</span></span>  <span class="token string">'af'</span><span class="token punctuation">,</span> <span class="token string">'am'</span><span class="token punctuation">,</span> <span class="token string">'ar-sa'</span><span class="token punctuation">,</span> <span class="token string">'as'</span><span class="token punctuation">,</span> <span class="token string">'az-Latn'</span><span class="token punctuation">,</span> <span class="token string">'be'</span><span class="token punctuation">,</span> <span class="token string">'bg'</span><span class="token punctuation">,</span> <span class="token string">'bn-BD'</span><span class="token punctuation">,</span>
  <span class="token string">'bn-IN'</span><span class="token punctuation">,</span> <span class="token string">'bs'</span><span class="token punctuation">,</span> <span class="token string">'ca'</span><span class="token punctuation">,</span> <span class="token string">'ca-ES-valencia'</span><span class="token punctuation">,</span> <span class="token string">'cs'</span><span class="token punctuation">,</span> <span class="token string">'cy'</span><span class="token punctuation">,</span> <span class="token string">'da'</span><span class="token punctuation">,</span> <span class="token string">'de'</span><span class="token punctuation">,</span>
  <span class="token string">'de-de'</span><span class="token punctuation">,</span> <span class="token string">'el'</span><span class="token punctuation">,</span> <span class="token string">'en-GB'</span><span class="token punctuation">,</span> <span class="token string">'en-US'</span><span class="token punctuation">,</span> <span class="token string">'es'</span><span class="token punctuation">,</span> <span class="token string">'es-ES'</span><span class="token punctuation">,</span> <span class="token string">'es-US'</span><span class="token punctuation">,</span> <span class="token string">'es-MX'</span><span class="token punctuation">,</span>
  <span class="token string">'et'</span><span class="token punctuation">,</span> <span class="token string">'eu'</span><span class="token punctuation">,</span> <span class="token string">'fa'</span><span class="token punctuation">,</span> <span class="token string">'fi'</span><span class="token punctuation">,</span> <span class="token string">'fil-Latn'</span><span class="token punctuation">,</span> <span class="token string">'fr'</span><span class="token punctuation">,</span> <span class="token string">'fr-FR'</span><span class="token punctuation">,</span> <span class="token string">'fr-CA'</span><span class="token punctuation">,</span> <span class="token string">'ga'</span><span class="token punctuation">,</span>
  <span class="token string">'gd-Latn'</span><span class="token punctuation">,</span> <span class="token string">'gl'</span><span class="token punctuation">,</span> <span class="token string">'gu'</span><span class="token punctuation">,</span> <span class="token string">'ha-Latn'</span><span class="token punctuation">,</span> <span class="token string">'he'</span><span class="token punctuation">,</span> <span class="token string">'hi'</span><span class="token punctuation">,</span> <span class="token string">'hr'</span><span class="token punctuation">,</span> <span class="token string">'hu'</span><span class="token punctuation">,</span> <span class="token string">'hy'</span><span class="token punctuation">,</span>
  <span class="token string">'id'</span><span class="token punctuation">,</span> <span class="token string">'ig-Latn'</span><span class="token punctuation">,</span> <span class="token string">'is'</span><span class="token punctuation">,</span> <span class="token string">'it'</span><span class="token punctuation">,</span> <span class="token string">'it-it'</span><span class="token punctuation">,</span> <span class="token string">'ja'</span><span class="token punctuation">,</span> <span class="token string">'ka'</span><span class="token punctuation">,</span> <span class="token string">'kk'</span><span class="token punctuation">,</span> <span class="token string">'km'</span><span class="token punctuation">,</span>
  <span class="token string">'kn'</span><span class="token punctuation">,</span> <span class="token string">'ko'</span><span class="token punctuation">,</span> <span class="token string">'kok'</span><span class="token punctuation">,</span> <span class="token string">'ku-Arab'</span><span class="token punctuation">,</span> <span class="token string">'ky-Cyrl'</span><span class="token punctuation">,</span> <span class="token string">'lb'</span><span class="token punctuation">,</span> <span class="token string">'lt'</span><span class="token punctuation">,</span> <span class="token string">'lv'</span><span class="token punctuation">,</span>
  <span class="token string">'mi-Latn'</span><span class="token punctuation">,</span> <span class="token string">'mk'</span><span class="token punctuation">,</span> <span class="token string">'ml'</span><span class="token punctuation">,</span> <span class="token string">'mn-Cyrl'</span><span class="token punctuation">,</span> <span class="token string">'mr'</span><span class="token punctuation">,</span> <span class="token string">'ms'</span><span class="token punctuation">,</span> <span class="token string">'mt'</span><span class="token punctuation">,</span> <span class="token string">'nb'</span><span class="token punctuation">,</span> <span class="token string">'ne'</span><span class="token punctuation">,</span>
  <span class="token string">'nl'</span><span class="token punctuation">,</span> <span class="token string">'nl-BE'</span><span class="token punctuation">,</span> <span class="token string">'nn'</span><span class="token punctuation">,</span> <span class="token string">'nso'</span><span class="token punctuation">,</span> <span class="token string">'or'</span><span class="token punctuation">,</span> <span class="token string">'pa'</span><span class="token punctuation">,</span> <span class="token string">'pa-Arab'</span><span class="token punctuation">,</span> <span class="token string">'pl'</span><span class="token punctuation">,</span>
  <span class="token string">'prs-Arab'</span><span class="token punctuation">,</span> <span class="token string">'pt-BR'</span><span class="token punctuation">,</span> <span class="token string">'pt-PT'</span><span class="token punctuation">,</span> <span class="token string">'qut-Latn'</span><span class="token punctuation">,</span> <span class="token string">'quz'</span><span class="token punctuation">,</span> <span class="token string">'ro'</span><span class="token punctuation">,</span> <span class="token string">'ru'</span><span class="token punctuation">,</span>
  <span class="token string">'rw'</span><span class="token punctuation">,</span> <span class="token string">'sd-Arab'</span><span class="token punctuation">,</span> <span class="token string">'si'</span><span class="token punctuation">,</span> <span class="token string">'sk'</span><span class="token punctuation">,</span> <span class="token string">'sl'</span><span class="token punctuation">,</span> <span class="token string">'sq'</span><span class="token punctuation">,</span> <span class="token string">'sr-Cyrl-BA'</span><span class="token punctuation">,</span>
  <span class="token string">'sr-Cyrl-RS'</span><span class="token punctuation">,</span> <span class="token string">'sr-Latn-RS'</span><span class="token punctuation">,</span> <span class="token string">'sv'</span><span class="token punctuation">,</span> <span class="token string">'sw'</span><span class="token punctuation">,</span> <span class="token string">'ta'</span><span class="token punctuation">,</span> <span class="token string">'te'</span><span class="token punctuation">,</span> <span class="token string">'tg-Cyrl'</span><span class="token punctuation">,</span>
  <span class="token string">'th'</span><span class="token punctuation">,</span> <span class="token string">'ti'</span><span class="token punctuation">,</span> <span class="token string">'tk-Latn'</span><span class="token punctuation">,</span> <span class="token string">'tn'</span><span class="token punctuation">,</span> <span class="token string">'tr'</span><span class="token punctuation">,</span> <span class="token string">'tt-Cyrl'</span><span class="token punctuation">,</span> <span class="token string">'ug-Arab'</span><span class="token punctuation">,</span> <span class="token string">'uk'</span><span class="token punctuation">,</span>
  <span class="token string">'ur'</span><span class="token punctuation">,</span> <span class="token string">'uz-Latn'</span><span class="token punctuation">,</span> <span class="token string">'vi'</span><span class="token punctuation">,</span> <span class="token string">'wo'</span><span class="token punctuation">,</span> <span class="token string">'xh'</span><span class="token punctuation">,</span> <span class="token string">'yo-Latn'</span><span class="token punctuation">,</span> <span class="token string">'zh-Hans'</span><span class="token punctuation">,</span>
  <span class="token string">'zh-Hant'</span><span class="token punctuation">,</span> <span class="token string">'zu'</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">createBingTileLayer</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">bingTileLayer</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// Fix attribution not showing initially.</span>
  <span class="token comment">// TODO: Remove once</span>
  <span class="token comment">// https://github.com/digidem/leaflet-bing-layer/pull/30</span>
  <span class="token comment">// is merged.</span>
<span class="gatsby-highlight-code-line">  instance<span class="token punctuation">.</span>_onAdd <span class="token operator">=</span> instance<span class="token punctuation">.</span>onAdd<span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">  instance<span class="token punctuation">.</span><span class="token function-variable function">onAdd</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">map</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_onAdd</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_updateAttribution</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span> instance<span class="token punctuation">,</span> context <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> BingTileLayer <span class="token operator">=</span>
  <span class="token function">createLayerComponent</span><span class="token punctuation">(</span>createBingTileLayer<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

BingTileLayer<span class="token punctuation">.</span>propTypes <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">bingMapsKey</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">.</span>isRequired<span class="token punctuation">,</span>
  <span class="token literal-property property">imagerySet</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span><span class="token function">oneOf</span><span class="token punctuation">(</span><span class="token constant">IMAGERY_SETS</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token literal-property property">culture</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span><span class="token function">oneOf</span><span class="token punctuation">(</span><span class="token constant">CULTURES</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token literal-property property">style</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

BingTileLayer<span class="token punctuation">.</span>defaultProps <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">  <span class="token literal-property property">imagerySet</span><span class="token operator">:</span> <span class="token constant">IMAGERY_SETS</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">  <span class="token literal-property property">culture</span><span class="token operator">:</span> <span class="token constant">CULTURES</span><span class="token punctuation">[</span><span class="token number">19</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span>  <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> BingTileLayer<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 5, 62: Possible values for <code class="language-text">imagerySet</code> prop (default: <code class="language-text">'Aerial'</code>)</li>
<li>Line 17, 63: Possible values for <code class="language-text">culture</code> prop (default: <code class="language-text">'en-US'</code>)</li>
<li>Line 42-46: A temporary hack to fix a bug in <a href="https://github.com/digidem/leaflet-bing-layer">leaflet-bing-layer</a> that
prevents the attribution text from showing. I’ve created a <a href="https://github.com/digidem/leaflet-bing-layer/pull/30">pull
request</a> for this but the project seems dead.</li>
</ul>
<p>And here’s how you’d use it in a map:</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> BingTileLayer <span class="token keyword">from</span> <span class="token string">'./BingTileLayer'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token constant">GREECE_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line"><span class="token keyword">const</span> <span class="token constant">BING_MAPS_KEY</span> <span class="token operator">=</span> <span class="token string">'mykey'</span><span class="token punctuation">;</span></span>
<span class="token keyword">function</span> <span class="token function">MyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MapContainer</span></span>
      <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">GREECE_BOUNDS</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">'100vh'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
    <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">      &lt;BingTileLayer </span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">        bingMapsKey=</span><span class="token punctuation">{</span><span class="token constant">BING_MAPS_KEY</span><span class="token punctuation">}</span><span class="token plain-text"> </span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">        imagerySet="AerialWithLabels" </span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">        culture="el" </span><span class="token plain-text"></span></span><span class="gatsby-highlight-code-line"><span class="token plain-text">      /> </span><span class="token plain-text"></span></span><span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">MapContainer</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MyMap<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 10: Replace <code class="language-text">mykey</code> with your <a href="https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/getting-a-bing-maps-key">Bing Maps key</a> that you can obtain for
free</li>
<li>Line 18-22: Example of using the component</li>
</ul>]]></description><link>https://angelos.dev//2021/07/bing-tile-layer-component-for-react-leaflet/</link><guid isPermaLink="false">https://angelos.dev//2021/07/bing-tile-layer-component-for-react-leaflet/</guid><pubDate>Wed, 14 Jul 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[ZoomControlWithReset component for React Leaflet]]></title><description><![CDATA[<p><a href="https://react-leaflet.js.org/">React Leaflet</a> is a <a href="https://reactjs.org/">React</a> library that exposes <a href="https://leafletjs.com/">Leaflet</a> classes as
React components, making it very easy to add interactive maps to React web apps.</p>
<p>This is the first in a series of posts on how I use the library. My intention is
to share back some of the things I’ve learned and implemented in the hope of
them being useful to others.</p>
<p>In this post, I present the <code class="language-text">ZoomControlWithReset</code> component which provides the
classic zoom +/- control with one extra button which resets the map view to fit
some bounds (usually calculated for map points).</p>
<p>First, the component:</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="gatsby-highlight-code-line"><span class="token keyword">import</span> PropTypes <span class="token keyword">from</span> <span class="token string">'prop-types'</span><span class="token punctuation">;</span></span><span class="token keyword">import</span> <span class="token punctuation">{</span> useMap <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-leaflet'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">ZoomControlWithReset</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> <span class="token punctuation">{</span> zoomInTitle <span class="token operator">=</span> <span class="token string">'Zoom in'</span><span class="token punctuation">,</span> zoomResetTitle <span class="token operator">=</span> <span class="token string">'Reset zoom'</span><span class="token punctuation">,</span> zoomOutTitle <span class="token operator">=</span> <span class="token string">'Zoom out'</span><span class="token punctuation">,</span> bounds <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">;</span></span>
  <span class="token keyword">const</span> map <span class="token operator">=</span> <span class="token function">useMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control leaflet-control-zoom leaflet-bar<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>
        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control-zoom-in<span class="token punctuation">"</span></span>
        <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span>
        <span class="token attr-name">title</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>zoomInTitle<span class="token punctuation">}</span></span>
        <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span>
        <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Zoom in<span class="token punctuation">"</span></span>
        <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          map<span class="token punctuation">.</span><span class="token function">zoomIn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
          e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
      <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">        +</span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>
        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control-zoom-out<span class="token punctuation">"</span></span>
        <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span>
        <span class="token attr-name">title</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>zoomOutTitle<span class="token punctuation">}</span></span>
        <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span>
        <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Zoom out<span class="token punctuation">"</span></span>
        <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          map<span class="token punctuation">.</span><span class="token function">zoomOut</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
          e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
      <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">        -</span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>
<span class="gatsby-highlight-code-line">        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-control-zoom-out<span class="token punctuation">"</span></span></span>        <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span>
        <span class="token attr-name">title</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>zoomResetTitle<span class="token punctuation">}</span></span>
        <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span>
        <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Reset zoom<span class="token punctuation">"</span></span>
        <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">          map<span class="token punctuation">.</span><span class="token function">fitBounds</span><span class="token punctuation">(</span>bounds<span class="token punctuation">)</span><span class="token punctuation">;</span></span>          e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
      <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="gatsby-highlight-code-line"><span class="token plain-text">        &amp;#x21ba;</span></span><span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

ZoomControlWithReset<span class="token punctuation">.</span>propTypes <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">zoomInTitle</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
  <span class="token literal-property property">zoomOutTitle</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
  <span class="token literal-property property">zoomResetTitle</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
  <span class="token literal-property property">bounds</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span><span class="token function">arrayOf</span><span class="token punctuation">(</span>PropTypes<span class="token punctuation">.</span><span class="token function">arrayOf</span><span class="token punctuation">(</span>PropTypes<span class="token punctuation">.</span>number<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>isRequired<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> ZoomControlWithReset<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 2: The component uses the <a href="https://reactjs.org/docs/typechecking-with-proptypes.html">prop-types</a> npm package for validating props
in development</li>
<li>Line 6: It’s possible to override the title attribute of the buttons with the
<code class="language-text">zoomInTitle</code>, <code class="language-text">zoomOutTitle</code> and <code class="language-text">zoomResetTitle</code> props</li>
<li>Line 39: Reuse the <code class="language-text">leaflet-control-zoom-out</code> class to style the reset button</li>
<li>Line 45: Reset the view</li>
<li>Line 49: Anti-clockwise open circle arrow</li>
</ul>
<p>And here’s how you’d use it in a map:</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber 0" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TileLayer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-leaflet'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> ZoomControlWithReset <span class="token keyword">from</span> <span class="token string">'./ZoomControlWithReset'</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line"><span class="token keyword">const</span> <span class="token constant">GREECE_BOUNDS</span> <span class="token operator">=</span> <span class="token punctuation">[</span></span><span class="gatsby-highlight-code-line">  <span class="token punctuation">[</span><span class="token number">31.08</span><span class="token punctuation">,</span> <span class="token number">14.26</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">  <span class="token punctuation">[</span><span class="token number">44.91</span><span class="token punctuation">,</span> <span class="token number">34.69</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line"><span class="token punctuation">]</span><span class="token punctuation">;</span></span>
<span class="token keyword">function</span> <span class="token function">MyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MapContainer</span></span>
      <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token constant">GREECE_BOUNDS</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token string">'100%'</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">'100vh'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
    <span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">TileLayer</span></span>
        <span class="token attr-name">url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://{s}.tile.osm.org/{z}/{x}/{y}.png<span class="token punctuation">"</span></span>
        <span class="token attr-name">attribution</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span><span class="token entity named-entity" title="&copy;">&amp;copy;</span> &lt;a href="https://osm.org/copyright">OpenStreetMap&lt;/a> contributors<span class="token punctuation">'</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>leaflet-top leaflet-left<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ZoomControlWithReset</span></span> <span class="token attr-name">bounds</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>bounds<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">MapContainer</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> MyMap<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>
<p>Lines 6-9: In <a href="https://leafletjs.com/">Leaflet</a>, bounds can be specified with two coordinate pairs,
in which the first element is the top-left corner and the second element is
the bottom-right. In your case, the bounds will probably be those of the
rectangle that contains all of your map’s points.</p>
<p>Here’s a function I’ve written to calculate the bounds of an array of points:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">boundsForPoints</span><span class="token punctuation">(</span><span class="token parameter">points</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>points<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span> <span class="token operator">=</span> points<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> bounds <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    points<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>latitude<span class="token punctuation">,</span> longitude<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      bounds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>bounds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> latitude<span class="token punctuation">)</span><span class="token punctuation">;</span>
      bounds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>bounds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> longitude<span class="token punctuation">)</span><span class="token punctuation">;</span>
      bounds<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>bounds<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> latitude<span class="token punctuation">)</span><span class="token punctuation">;</span>
      bounds<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>bounds<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> longitude<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> bounds<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
</li>
</ul>]]></description><link>https://angelos.dev//2021/07/zoom-control-with-reset-component-for-react-leaflet/</link><guid isPermaLink="false">https://angelos.dev//2021/07/zoom-control-with-reset-component-for-react-leaflet/</guid><pubDate>Sat, 10 Jul 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[Strip Exif metadata from images in Rails]]></title><description><![CDATA[<p>I recently wanted to strip the <a href="https://en.wikipedia.org/wiki/Exif">Exif</a> metadata from user-uploaded,
<a href="https://edgeguides.rubyonrails.org/active_storage_overview.html">ActiveStorage</a>-backed images in <a href="https://rubyonrails.org/">Rails</a>. The reason is that images taken
with mobile phones are usually <a href="https://en.wikipedia.org/wiki/Geotagging">geotagged</a> and contain the user’s location,
which is sensitive personal data.</p>
<p>Initially, I thought of implementing it in the model by calling the relevant
code with a <code class="language-text">before_save</code> callback, having it process the attached image.</p>
<p>Sadly, I wasn’t able to find a publicly-documented, usable API in ActiveStorage
that allows this kind of manipulation. After some digging, I did manage to make
it work, but the implementation was based on an internal, undocumented API, so I
quickly abandoned it.</p>
<p>Given this limitation, I quickly realized that the most viable solution would be
to take care of things before the image file ever reaches ActiveStorage. This
way, I wouldn’t have to mess with its quirky API.</p>
<h3>The solution</h3>
<p>Install <a href="https://imagemagick.org/">ImageMagick</a>:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> imagemagick <span class="token comment"># Debian/Ubuntu</span></code></pre></div>
<p>Install the <a href="https://github.com/minimagick/minimagick">mini_magick</a> Gem:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">bundle add mini_magick</code></pre></div>
<p>Add the following code in your controller (or a separate library file):</p>
<div class="gatsby-highlight" data-language="ruby"><pre style="counter-reset: linenumber 0" class="language-ruby line-numbers"><code class="language-ruby"><span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">strip_exif_from_attachment_params</span></span><span class="token punctuation">(</span>record<span class="token punctuation">,</span> record_params<span class="token punctuation">)</span>
  record<span class="token punctuation">.</span>attachment_reflections<span class="token punctuation">.</span>each_key <span class="token keyword">do</span> <span class="token operator">|</span>attachment_attribute<span class="token operator">|</span>
    <span class="token builtin">Array</span><span class="token punctuation">(</span>record_params<span class="token punctuation">[</span>attachment_attribute<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>file_param<span class="token operator">|</span>
      <span class="token keyword">next</span> <span class="token keyword">if</span> file_param<span class="token punctuation">.</span>content_type <span class="token operator">!=</span> <span class="token string-literal"><span class="token string">'image/jpeg'</span></span>

      uploaded_file <span class="token operator">=</span> file_param<span class="token punctuation">.</span>tempfile
      image <span class="token operator">=</span> MiniMagick<span class="token double-colon punctuation">::</span>Image<span class="token punctuation">.</span>read<span class="token punctuation">(</span>uploaded_file<span class="token punctuation">)</span>
      image<span class="token punctuation">.</span>auto_orient <span class="token comment"># Before stripping</span>
      image<span class="token punctuation">.</span>strip <span class="token comment"># Strip Exif</span>
      uploaded_file<span class="token punctuation">.</span>rewind
      image<span class="token punctuation">.</span>write<span class="token punctuation">(</span>uploaded_file<span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Line 1: Accept the record as a parameter</li>
<li>Line 2: Use reflection on the record to figure out its ActiveStorage
attachments (so it operates on all of them)</li>
<li>Line 4: Skip attachments that are not JPEG images</li>
<li>Line 6: Operate on the uploaded file’s <a href="https://ruby-doc.org/stdlib-2.7.2/libdoc/tempfile/rdoc/Tempfile.html">Tempfile</a></li>
<li>Line 8: Use Exif metadata, before stripping, to orient the image (e.g. fix it
if it’s rotated)</li>
<li>Line 9: Strip Exif metadata</li>
<li>Line 11: Persist changes to the file</li>
</ul>
<p>Assuming you have a user model with an attached avatar:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token operator">&lt;</span> ApplicationRecord
  has_one_attached <span class="token symbol">:avatar</span>
<span class="token keyword">end</span></code></pre></div>
<p>Here’s an example on how you’d use it:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">UsersController</span> <span class="token operator">&lt;</span> ApplicationController
  before_action <span class="token symbol">:set_user</span><span class="token punctuation">,</span> <span class="token symbol">only</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">%i[update]</span></span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">update</span></span>
<span class="gatsby-highlight-code-line">    strip_exif_from_attachment_params<span class="token punctuation">(</span><span class="token variable">@user</span><span class="token punctuation">,</span> update_params<span class="token punctuation">)</span></span>
    <span class="token keyword">if</span> <span class="token operator">!</span><span class="token variable">@user</span><span class="token punctuation">.</span>update<span class="token punctuation">(</span>update_params<span class="token punctuation">)</span>
      flash<span class="token punctuation">.</span>now<span class="token punctuation">[</span><span class="token symbol">:alert</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string-literal"><span class="token string">'Failed to update user'</span></span>
      render <span class="token symbol">:edit</span>
      <span class="token keyword">return</span>
    <span class="token keyword">end</span>

    redirect_to user_path<span class="token punctuation">(</span><span class="token variable">@user</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token symbol">notice</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">'User updated'</span></span>
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">set_user</span></span>
    <span class="token variable">@user</span> <span class="token operator">=</span> User<span class="token punctuation">.</span>find<span class="token punctuation">(</span>params<span class="token punctuation">.</span><span class="token keyword">require</span><span class="token punctuation">(</span><span class="token symbol">:id</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">update_params</span></span>
    params<span class="token punctuation">.</span><span class="token keyword">require</span><span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span><span class="token punctuation">.</span>permit<span class="token punctuation">(</span><span class="token symbol">:avatar</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code></pre></div>
<p>Have fun!</p>]]></description><link>https://angelos.dev//2021/03/strip-exif-metadata-from-images-in-rails/</link><guid isPermaLink="false">https://angelos.dev//2021/03/strip-exif-metadata-from-images-in-rails/</guid><pubDate>Sat, 06 Mar 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[Convert Greek to Greeklish in Ruby with greeklish_iso843]]></title><description><![CDATA[<p><strong>Update:</strong> Now available as an <a href="https://greeklish.xyz">online service</a>.</p>
<p>A common need for many Greek <a href="https://rubyonrails.org/">Rails</a> projects I work on is converting Greek
text to <a href="https://en.wikipedia.org/wiki/Greeklish">Greeklish</a>, a process known as <em>transliteration</em>. I use it mainly to
generate URL-safe slugs.</p>
<p>There are many ways to go about this, but the <a href="https://en.wikipedia.org/wiki/ISO_843">ISO 843</a> standard (based on the
“ΕΛΟΤ 743” Greek standard) is the one used by the Greek State.</p>
<p>A while back, I put in the effort and ported an <a href="http://www.passport.gov.gr/passports/GrElotConverter/GrElotConverter.html">official
implementation</a> from JavaScript to <a href="https://www.ruby-lang.org/en/">Ruby</a>, vastly improving it in the
process. I’ve been using it ever since, copying it from project to project.</p>
<p>Today I decided to open-source it and the result is a <a href="https://rubygems.org/gems/greeklish_iso843/">Gem</a> called
<a href="https://github.com/agorf/greeklish_iso843">greeklish_iso843</a>.</p>]]></description><link>https://angelos.dev//2021/03/converting-greek-to-greeklish-in-ruby-with-greeklish_iso843/</link><guid isPermaLink="false">https://angelos.dev//2021/03/converting-greek-to-greeklish-in-ruby-with-greeklish_iso843/</guid><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[Web server from scratch in Go]]></title><description><![CDATA[<p>I recently started learning <a href="https://golang.org/">Go</a> for the 3rd or 4th time. My previous attempts
did not succeed because, without any projects to use it for, I quickly forgot it
and lost motivation.</p>
<p>To make this time work, I’ve decided to write code while reading
<a href="https://golang.org/doc/">documentation</a>, <a href="https://www.manning.com/books/go-in-action">books</a>, and <a href="https://www.udemy.com/course/learn-how-to-code/">watching</a> <a href="https://www.udemy.com/course/go-programming-language/">courses</a>.</p>
<p>The first project I decided to implement is a GET-only web/HTTP server. Coming
from <a href="https://www.ruby-lang.org/">Ruby</a>, I based my implementation on <a href="https://www.macournoyer.com/">Marc-André Cournoyer</a>’s
<a href="http://j.mp/rebuildawebserver">Rebuilding a Web Server in Ruby</a> class.</p>
<p>Here is <code class="language-text">server/server.go</code>:</p>
<div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go"><span class="token keyword">package</span> server

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"bufio"</span>
	<span class="token string">"bytes"</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"strings"</span>
<span class="token punctuation">)</span>

<span class="token keyword">type</span> Env <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span>

<span class="token comment">// Part of Rack web server interface</span>
<span class="token keyword">type</span> Response <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	Status  <span class="token builtin">int</span>
	Headers <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span>
	Body    io<span class="token punctuation">.</span>Reader
<span class="token punctuation">}</span>

<span class="token comment">// Part of Rack web server interface</span>
<span class="token comment">// Applications are functions that accept Env and return Response</span>
<span class="token keyword">type</span> App <span class="token keyword">func</span><span class="token punctuation">(</span>Env<span class="token punctuation">)</span> <span class="token operator">*</span>Response

<span class="token keyword">type</span> Server <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	Port <span class="token builtin">int</span>
	App
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">NewServer</span><span class="token punctuation">(</span>port <span class="token builtin">int</span><span class="token punctuation">,</span> app App<span class="token punctuation">)</span> <span class="token operator">*</span>Server <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token operator">&amp;</span>Server<span class="token punctuation">{</span>Port<span class="token punctuation">:</span> port<span class="token punctuation">,</span> App<span class="token punctuation">:</span> app<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>s <span class="token operator">*</span>Server<span class="token punctuation">)</span> <span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
	ln<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">Listen</span><span class="token punctuation">(</span><span class="token string">"tcp"</span><span class="token punctuation">,</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">":%d"</span><span class="token punctuation">,</span> s<span class="token punctuation">.</span>Port<span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		<span class="token keyword">return</span> err
	<span class="token punctuation">}</span>

	<span class="token keyword">for</span> <span class="token punctuation">{</span>
		conn<span class="token punctuation">,</span> err <span class="token operator">:=</span> ln<span class="token punctuation">.</span><span class="token function">Accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
			<span class="token keyword">return</span> err
		<span class="token punctuation">}</span>

		c <span class="token operator">:=</span> <span class="token function">newConnection</span><span class="token punctuation">(</span>conn<span class="token punctuation">,</span> s<span class="token punctuation">.</span>App<span class="token punctuation">)</span>

		<span class="token comment">// Process request concurrently</span>
		<span class="token keyword">go</span> c<span class="token punctuation">.</span><span class="token function">process</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">type</span> connection <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	net<span class="token punctuation">.</span>Conn
	App
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">newConnection</span><span class="token punctuation">(</span>conn net<span class="token punctuation">.</span>Conn<span class="token punctuation">,</span> app App<span class="token punctuation">)</span> <span class="token operator">*</span>connection <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token operator">&amp;</span>connection<span class="token punctuation">{</span>Conn<span class="token punctuation">:</span> conn<span class="token punctuation">,</span> App<span class="token punctuation">:</span> app<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>connection<span class="token punctuation">)</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	reader <span class="token operator">:=</span> bufio<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span><span class="token operator">*</span>c<span class="token punctuation">)</span>
	lines <span class="token operator">:=</span> bytes<span class="token punctuation">.</span><span class="token function">NewBuffer</span><span class="token punctuation">(</span><span class="token boolean">nil</span><span class="token punctuation">)</span>

	<span class="token keyword">for</span> <span class="token punctuation">{</span>
		data<span class="token punctuation">,</span> err <span class="token operator">:=</span> reader<span class="token punctuation">.</span><span class="token function">ReadBytes</span><span class="token punctuation">(</span><span class="token char">'\n'</span><span class="token punctuation">)</span>
		<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
			<span class="token function">panic</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
		<span class="token punctuation">}</span>

		<span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> lines<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
		<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
			<span class="token function">panic</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
		<span class="token punctuation">}</span>

		<span class="token comment">// Headers-Body separator</span>
		<span class="token keyword">if</span> <span class="token function">string</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">"\r\n"</span> <span class="token punctuation">{</span>
			<span class="token keyword">break</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span>

	<span class="token comment">// Parse HTTP request (part of Go's stdlib)</span>
	req<span class="token punctuation">,</span> err <span class="token operator">:=</span> http<span class="token punctuation">.</span><span class="token function">ReadRequest</span><span class="token punctuation">(</span>bufio<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>lines<span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		<span class="token function">panic</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>

	<span class="token comment">// Part of Rack web server interface</span>
	env <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span>Env<span class="token punctuation">)</span>
	<span class="token keyword">for</span> name<span class="token punctuation">,</span> value <span class="token operator">:=</span> <span class="token keyword">range</span> req<span class="token punctuation">.</span>Header <span class="token punctuation">{</span>
		envName <span class="token operator">:=</span> name
		envName <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">ToUpper</span><span class="token punctuation">(</span>envName<span class="token punctuation">)</span>
		envName <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span>envName<span class="token punctuation">,</span> <span class="token string">"-"</span><span class="token punctuation">,</span> <span class="token string">"_"</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span>
		envName <span class="token operator">=</span> <span class="token string">"HTTP_"</span> <span class="token operator">+</span> envName
		env<span class="token punctuation">[</span>envName<span class="token punctuation">]</span> <span class="token operator">=</span> value<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>
	<span class="token punctuation">}</span>
	env<span class="token punctuation">[</span><span class="token string">"PATH_INFO"</span><span class="token punctuation">]</span> <span class="token operator">=</span> req<span class="token punctuation">.</span>URL<span class="token punctuation">.</span>Path
	env<span class="token punctuation">[</span><span class="token string">"REQUEST_METHOD"</span><span class="token punctuation">]</span> <span class="token operator">=</span> req<span class="token punctuation">.</span>Method

	c<span class="token punctuation">.</span><span class="token function">respond</span><span class="token punctuation">(</span>env<span class="token punctuation">)</span>
	c<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>connection<span class="token punctuation">)</span> <span class="token function">respond</span><span class="token punctuation">(</span>env Env<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	resp <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">App</span><span class="token punctuation">(</span>env<span class="token punctuation">)</span> <span class="token comment">// Run app and store Response</span>
	reason <span class="token operator">:=</span> <span class="token function">reasonFromStatus</span><span class="token punctuation">(</span>resp<span class="token punctuation">.</span>Status<span class="token punctuation">)</span>

	c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"HTTP/1.1 %d %s\r\n"</span><span class="token punctuation">,</span> resp<span class="token punctuation">.</span>Status<span class="token punctuation">,</span> reason<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

	<span class="token keyword">for</span> name<span class="token punctuation">,</span> value <span class="token operator">:=</span> <span class="token keyword">range</span> resp<span class="token punctuation">.</span>Headers <span class="token punctuation">{</span>
		c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"%s: %s\r\n"</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>

	<span class="token comment">// Headers-Body separator</span>
	c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"\r\n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

	scanner <span class="token operator">:=</span> bufio<span class="token punctuation">.</span><span class="token function">NewScanner</span><span class="token punctuation">(</span>resp<span class="token punctuation">.</span>Body<span class="token punctuation">)</span>

	<span class="token keyword">for</span> scanner<span class="token punctuation">.</span><span class="token function">Scan</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>scanner<span class="token punctuation">.</span><span class="token function">Text</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"\n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">reasonFromStatus</span><span class="token punctuation">(</span>status <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span>
	<span class="token comment">// A couple of codes for demonstration</span>
	<span class="token keyword">return</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">int</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>
		<span class="token number">200</span><span class="token punctuation">:</span> <span class="token string">"OK"</span><span class="token punctuation">,</span>
		<span class="token number">404</span><span class="token punctuation">:</span> <span class="token string">"Not Found"</span><span class="token punctuation">,</span>
	<span class="token punctuation">}</span><span class="token punctuation">[</span>status<span class="token punctuation">]</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>connection<span class="token punctuation">)</span> <span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre></div>
<p>And <code class="language-text">main.go</code> that uses it:</p>
<div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"strconv"</span>
	<span class="token string">"strings"</span>
	<span class="token string">"time"</span>

	<span class="token string">"github.com/agorf/fs-webserver/server"</span>
<span class="token punctuation">)</span>

<span class="token keyword">const</span> port <span class="token operator">=</span> <span class="token number">3000</span>

<span class="token keyword">func</span> <span class="token function">app</span><span class="token punctuation">(</span>env server<span class="token punctuation">.</span>Env<span class="token punctuation">)</span> <span class="token operator">*</span>server<span class="token punctuation">.</span>Response <span class="token punctuation">{</span>
	<span class="token keyword">if</span> env<span class="token punctuation">[</span><span class="token string">"PATH_INFO"</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">"/sleep"</span> <span class="token punctuation">{</span>
		time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">5</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>

	message <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span>
		<span class="token string">"Your user agent is %s and you asked for path %s\n"</span><span class="token punctuation">,</span>
		env<span class="token punctuation">[</span><span class="token string">"HTTP_USER_AGENT"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
		env<span class="token punctuation">[</span><span class="token string">"PATH_INFO"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
	<span class="token punctuation">)</span>

	<span class="token keyword">return</span> <span class="token operator">&amp;</span>server<span class="token punctuation">.</span>Response<span class="token punctuation">{</span>
		Status<span class="token punctuation">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
		Headers<span class="token punctuation">:</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>
			<span class="token string">"Content-Type"</span><span class="token punctuation">:</span>   <span class="token string">"text/plain"</span><span class="token punctuation">,</span>
			<span class="token string">"Content-Length"</span><span class="token punctuation">:</span> strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token punctuation">}</span><span class="token punctuation">,</span>
		Body<span class="token punctuation">:</span> strings<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"Listening on port %d...\n"</span><span class="token punctuation">,</span> port<span class="token punctuation">)</span>

	s <span class="token operator">:=</span> server<span class="token punctuation">.</span><span class="token function">NewServer</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> app<span class="token punctuation">)</span>

	err <span class="token operator">:=</span> s<span class="token punctuation">.</span><span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		<span class="token function">panic</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>The app we’re serving is very simple, responding with the user agent header and
the requested path.</p>
<p>The cool thing about this is that it serves requests concurrently. To verify
that, we can run the server in a terminal window:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ go run main.go
Listening on port 3000...</code></pre></div>
<p>Open a new terminal window and make a long-lived (5 seconds) request:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ curl http://localhost:3000/sleep</code></pre></div>
<p>Open another terminal window and make many short-lived requests, verifying
they’re immediately processed while the long-lived request is blocked:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ curl http://localhost:3000
Your user agent is curl/7.64.0 and you asked for path /
$ curl http://localhost:3000
Your user agent is curl/7.64.0 and you asked for path /
$ curl http://localhost:3000
Your user agent is curl/7.64.0 and you asked for path /</code></pre></div>
<p>Since I’m a <a href="https://golang.org/">Go</a> newbie, I’d love to have your feedback if you’re more
experienced.</p>]]></description><link>https://angelos.dev//2020/05/web-server-from-scratch-in-go/</link><guid isPermaLink="false">https://angelos.dev//2020/05/web-server-from-scratch-in-go/</guid><pubDate>Sat, 09 May 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[DIY Pomodoro with cron]]></title><description><![CDATA[<p>I recently wanted to employ the <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique">Pomodoro Technique</a> to remember to
take regular breaks from the computer.</p>
<p>Being a fan of <a href="https://en.wikipedia.org/wiki/Do_it_yourself">DIY</a> for simple things, I decided to implement it with cron,
desktop notifications and a subtle <a href="https://en.wiktionary.org/wiki/chime">chime</a> sound.</p>
<p>Execute <code class="language-text">crontab -e</code> from a terminal and write:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">0,30 * * * * DISPLAY=:0.0 notify-send -t 10000 Pomodoro 'Back to work!'; XDG_RUNTIME_DIR=/run/user/1000 paplay /usr/share/sounds/freedesktop/stereo/complete.oga
25,55 * * * * DISPLAY=:0.0 notify-send -t 10000 -u critical Pomodoro 'Time for a 5-minute break!'; XDG_RUNTIME_DIR=/run/user/1000 paplay /usr/share/sounds/freedesktop/stereo/complete.oga</code></pre></div>
<p>Things to note:</p>
<ul>
<li>At <code class="language-text">0</code> and <code class="language-text">30</code> minutes past you get a <code class="language-text">Back to work!</code> notification and sound</li>
<li>At <code class="language-text">25</code> and <code class="language-text">55</code> minutes past you get a <code class="language-text">Time for a 5-minute break!</code> notification and sound</li>
<li>Notifications are automatically dismissed after 10 seconds</li>
<li><code class="language-text">1000</code> is your user id. You can get it with <code class="language-text">echo $UID</code> from a terminal.</li>
<li><code class="language-text">paplay</code> plays sounds with PulseAudio</li>
<li><code class="language-text">/usr/share/sounds/freedesktop/stereo/complete.oga</code> is the path to the audio file and is part of the <a href="https://packages.debian.org/search?keywords=sound-theme-freedesktop">sound-theme-freedesktop</a> package in Debian (may be missing from your system)</li>
</ul>
<p>If you prefer 50 minutes of work followed by a 10-minute break:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">0 * * * * DISPLAY=:0.0 notify-send -t 10000 Pomodoro 'Back to work!'; XDG_RUNTIME_DIR=/run/user/1000 paplay /usr/share/sounds/freedesktop/stereo/complete.oga
50 * * * * DISPLAY=:0.0 notify-send -t 10000 -u critical Pomodoro 'Time for a 10-minute break!'; XDG_RUNTIME_DIR=/run/user/1000 paplay /usr/share/sounds/freedesktop/stereo/complete.oga</code></pre></div>
<p>Thanks to <a href="https://zorbash.com/">zorbash</a> for the idea to use desktop notifications.</p>]]></description><link>https://angelos.dev//2020/04/diy-pomodoro-with-cron/</link><guid isPermaLink="false">https://angelos.dev//2020/04/diy-pomodoro-with-cron/</guid><pubDate>Wed, 22 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[Painless PostgreSQL backups to Amazon S3]]></title><description><![CDATA[<p>I recently wanted to have backups of a PostgreSQL database running on a server
that backs a Rails app.</p>
<h3>Requirements &#x26; solutions</h3>
<h4>Stored off-site</h4>
<p>Otherwise it wouldn’t really be a backup.</p>
<p>For this, I use <a href="https://aws.amazon.com/s3/">Amazon S3</a>.</p>
<h4>Low transfer and storage cost</h4>
<p>Again, <a href="https://aws.amazon.com/s3/">Amazon S3</a> transfer and storage costs are reasonable in this context.</p>
<h4>Inacessible from the server</h4>
<p>So that even if someone broke into the server, they wouldn’t be able to access
the off-site location and mess with the backups.</p>
<p>For this, I created a write-only <a href="https://aws.amazon.com/s3/">Amazon S3</a> bucket (how to do is beyond the
scope of this article)</p>
<h4>Encrypted on the server</h4>
<p>This prevents Amazon or whoever might break into the bucket to read the data.</p>
<p>For this, I use <a href="https://gnupg.org/">GnuPG</a> with symmetric encryption (i.e. a passphrase for both
encryption and decryption, instead of a public-private keypair)</p>
<h4>Simple, with as few dependencies as possible</h4>
<p>I wrote a custom shell script that uses <a href="https://github.com/bloomreach/s4cmd">s4cmd</a> to handle the upload.</p>
<h4>Secure</h4>
<p>I hardcoded all credentials in the script file (readable only by my user), used
environment variables as little as possible and avoided passing passwords as
command-line arguments.</p>
<h4>Automatic</h4>
<p>I run the script daily with <a href="https://crontab.guru/">cron</a>.</p>
<h3>Script</h3>
<p>Important security points:</p>
<ul>
<li><em>Before</em> writing your credentials to this file, make sure it is not readable
by anyone else by setting its permissions: <code class="language-text">chmod go-rwx database-backup.sh</code></li>
<li>Make sure the editor you use does not keep temporary files of things you type
(e.g. for undo history)</li>
<li>Either write this file in the server directly or in your workstation and
transfer it securely with SSH.</li>
</ul>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token shebang important">#!/usr/bin/env bash</span>

<span class="token comment"># ---- BEGIN configuration ----</span>
<span class="token assign-left variable">database_host</span><span class="token operator">=</span>localhost
<span class="token assign-left variable">database_port</span><span class="token operator">=</span><span class="token number">5432</span>
<span class="token assign-left variable">database_username</span><span class="token operator">=</span>app
<span class="token assign-left variable">database_password</span><span class="token operator">=</span>
<span class="token assign-left variable">database_name</span><span class="token operator">=</span>app_production
<span class="token assign-left variable">database_backup_passphrase</span><span class="token operator">=</span>
<span class="token assign-left variable">database_backup_s3_access_key</span><span class="token operator">=</span>
<span class="token assign-left variable">database_backup_s3_secret_key</span><span class="token operator">=</span>
<span class="token assign-left variable">database_backup_s3_bucket_name</span><span class="token operator">=</span>
<span class="token comment"># ---- END configuration -----</span>

<span class="token assign-left variable">dump_file</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${database_name}</span>-<span class="token variable"><span class="token variable">$(</span><span class="token function">date</span> <span class="token parameter variable">--utc</span> +%FT%TZ<span class="token variable">)</span></span>.dump"</span>
<span class="token assign-left variable">dump_file_gpg</span><span class="token operator">=</span><span class="token variable">$dump_file</span>.gpg

<span class="token builtin class-name">trap</span> <span class="token string">"{ rm -f <span class="token variable">$dump_file</span>; rm -f <span class="token variable">$dump_file_gpg</span>; }"</span> EXIT

<span class="token assign-left variable">PGPASSWORD</span><span class="token operator">=</span><span class="token variable">$database_password</span> <span class="token punctuation">\</span>
  pg_dump <span class="token punctuation">\</span>
  <span class="token parameter variable">-Fc</span> --no-acl <span class="token punctuation">\</span>
  <span class="token parameter variable">-h</span> <span class="token variable">$database_host</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-p</span> <span class="token variable">$database_port</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-U</span> <span class="token variable">$database_username</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-f</span> <span class="token variable">$dump_file</span> <span class="token variable">$database_name</span>

<span class="token builtin class-name">echo</span> <span class="token variable">$database_backup_passphrase</span> <span class="token operator">|</span> <span class="token punctuation">\</span>
  gpg <span class="token parameter variable">-c</span> --cipher-algo AES256 --no-symkey-cache <span class="token parameter variable">--batch</span> --passphrase-fd <span class="token number">0</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-o</span> <span class="token variable">$dump_file_gpg</span> <span class="token variable">$dump_file</span>

<span class="token assign-left variable">S3_ACCESS_KEY</span><span class="token operator">=</span><span class="token variable">$database_backup_s3_access_key</span> <span class="token assign-left variable">S3_SECRET_KEY</span><span class="token operator">=</span><span class="token variable">$database_backup_s3_secret_key</span> <span class="token punctuation">\</span>
  s4cmd <span class="token punctuation">\</span>
  --sync-check <span class="token parameter variable">--retry</span> <span class="token number">3</span> --retry-delay <span class="token number">120</span> <span class="token punctuation">\</span>
  put <span class="token variable">$dump_file_gpg</span> s3://<span class="token variable">$database_backup_s3_bucket_name</span>/</code></pre></div>
<h3>Crontab</h3>
<p>Issue <code class="language-text">crontab -e</code> and write:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">0 3 * * * bash /usr/local/bin/database-backup.sh</code></pre></div>
<p>This will run the backup once per day at 3 AM.</p>
<h3>Does it work?</h3>
<p>One should regularly test the recovery of any backup system.</p>
<p>Ensure backup files appear in your S3 bucket shortly after cron runs.</p>
<p>Also, try decrypting a backup file:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">gpg <span class="token parameter variable">-d</span> app_production-2020-04-20T20:31:34Z.dump.gpg <span class="token operator">></span>app_production-2020-04-20T20:31:34Z.dump</code></pre></div>
<p>And restoring the resulting database dump file to a local, empty, development
database:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">pg_restore <span class="token parameter variable">-h</span> <span class="token variable">$database_host</span> <span class="token parameter variable">-d</span> <span class="token variable">$database_name</span> <span class="token parameter variable">-U</span> <span class="token variable">$database_user</span> app_production-2020-04-20T20:31:34Z.dump</code></pre></div>
<p>Run your app and make sure the data looks correct.</p>
<h3>Next steps</h3>
<p>I feel like the only thing missing is some kind of notification if something
goes wrong and backups fail. <a href="https://aws.amazon.com/">AWS</a> has a <a href="https://aws.amazon.com/sns/">notification service</a> but I
haven’t got around to making it work yet (ironically, it’s called “Simple
Notification Service”).</p>]]></description><link>https://angelos.dev//2020/04/painless-postgresql-backups-to-amazon-s3/</link><guid isPermaLink="false">https://angelos.dev//2020/04/painless-postgresql-backups-to-amazon-s3/</guid><pubDate>Mon, 20 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[DIY email validation in Rails]]></title><description><![CDATA[<p>I recently needed email validation in a Rails app and decided to roll my own for
the following reasons:</p>
<ul>
<li>I have a strong aversion to introducing external dependencies (i.e. Gems) for
simple things</li>
<li>What I wanted was pretty specific</li>
<li>Writing <a href="https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations">custom validators in Rails</a> is easy</li>
</ul>
<p>The implementation consists of 3 steps:</p>
<ol>
<li>Perform basic format validation</li>
<li>Block non-email domains</li>
<li>Block domains from disposable email services</li>
</ol>
<h3>Perform basic format validation</h3>
<p>It’s notoriously hard to <a href="https://www.regular-expressions.info/email.html">validate an email address with a regular
expression</a>. The reason is that both the local part (before the <code class="language-text">@</code>) and
the domain (after the <code class="language-text">@</code>) can accept a wide variety of characters and symbols,
so you may end up rejecting a perfectly valid email address you haven’t covered
for.</p>
<p>To keep things simple, I used a simple regular expression to check for basic
things, accepting the risk it might accept emails it shouldn’t.</p>
<h3>Block non-email domains</h3>
<p>In order to accept emails, a domain name must have the necessary MX nameserver
records.</p>
<h3>Block domains from disposable email services</h3>
<p>Disposable email services like <a href="https://www.mailinator.com/">Mailinator</a> are useful for users and a
headache for website authors.</p>
<p>Thankfully, there are projects that maintain <a href="https://github.com/martenson/disposable-email-domains/blob/master/disposable_email_blocklist.conf">a list of such domains</a>
we can check against.</p>
<h3>The solution</h3>
<p>First, we need to get the list of disposable email domains into our Rails app:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token builtin class-name">cd</span> railsapp/
<span class="token function">git</span> submodule <span class="token function">add</span> https://github.com/martenson/disposable-email-domains vendor/disposable-email-domains
<span class="token function">git</span> <span class="token function">add</span> vendor/disposable-email-domains
<span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">'Add disposable-email-domains as Git submodule'</span></code></pre></div>
<p>Whenever there’s an update in the repo, we can retrieve the new blacklisted
domains as follows:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token builtin class-name">cd</span> railsapp/
<span class="token function">git</span> submodule update vendor/disposable-email-domains
<span class="token function">git</span> <span class="token function">add</span> vendor/disposable-email-domains
<span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">'Update disposable-email-domains Git submodule'</span></code></pre></div>
<p>Now it’s time for the validator code which I place under
<code class="language-text">app/lib/email_validator.rb</code> so that it gets autoloaded:</p>
<div class="gatsby-highlight" data-language="ruby"><pre style="counter-reset: linenumber 0" class="language-ruby line-numbers"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string-literal"><span class="token string">'resolv'</span></span>

<span class="token keyword">class</span> <span class="token class-name">EmailValidator</span> <span class="token operator">&lt;</span> ActiveModel<span class="token double-colon punctuation">::</span>EachValidator
  <span class="token constant">EMAIL_REGEX</span> <span class="token operator">=</span> <span class="token regex-literal"><span class="token regex">/[a-z0-9._-]{1,64}@[a-z0-9._-]{1,255}/</span></span><span class="token punctuation">.</span>freeze
  private_constant <span class="token symbol">:EMAIL_REGEX</span>

<span class="gatsby-highlight-code-line">  <span class="token constant">DISPOSABLE_EMAIL_DOMAINS</span> <span class="token operator">=</span> <span class="token builtin">File</span><span class="token punctuation">.</span>readlines<span class="token punctuation">(</span></span>    Rails<span class="token punctuation">.</span>root<span class="token punctuation">.</span>join<span class="token punctuation">(</span>
      <span class="token string-literal"><span class="token string">'vendor'</span></span><span class="token punctuation">,</span>
      <span class="token string-literal"><span class="token string">'disposable-email-domains'</span></span><span class="token punctuation">,</span>
      <span class="token string-literal"><span class="token string">'disposable_email_blocklist.conf'</span></span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token symbol">chomp</span><span class="token operator">:</span> <span class="token boolean">true</span>
  <span class="token punctuation">)</span><span class="token punctuation">.</span>each_with_object<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">|</span>domain<span class="token punctuation">,</span> hash<span class="token operator">|</span> hash<span class="token punctuation">[</span>domain<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">.</span>freeze
  private_constant <span class="token symbol">:DISPOSABLE_EMAIL_DOMAINS</span>

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">validate_each</span></span><span class="token punctuation">(</span>record<span class="token punctuation">,</span> attribute<span class="token punctuation">,</span> value<span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token keyword">if</span> value<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span> <span class="token operator">&amp;&amp;</span> options<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:allow_nil</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token keyword">if</span> value<span class="token punctuation">.</span>blank<span class="token operator">?</span> <span class="token operator">&amp;&amp;</span> options<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:allow_blank</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> options<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:format</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
        <span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span> <span class="token operator">||</span> <span class="token operator">!</span>valid_format<span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span>
      record<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>add<span class="token punctuation">(</span>attribute<span class="token punctuation">,</span> <span class="token symbol">:invalid</span><span class="token punctuation">)</span>
<span class="gatsby-highlight-code-line">      <span class="token keyword">return</span></span>    <span class="token keyword">end</span>

    <span class="token keyword">if</span> options<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:non_disposable_domain</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
        <span class="token operator">!</span>value<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span> <span class="token operator">&amp;&amp;</span>
        disposable_domain<span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
      record<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>add<span class="token punctuation">(</span>attribute<span class="token punctuation">,</span> <span class="token symbol">:disposable_domain</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>

    <span class="token keyword">if</span> options<span class="token punctuation">.</span>fetch<span class="token punctuation">(</span><span class="token symbol">:mail_domain</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span>
        <span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token keyword">nil</span><span class="token operator">?</span> <span class="token operator">||</span> <span class="token operator">!</span>mail_domain<span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span>
      record<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>add<span class="token punctuation">(</span>attribute<span class="token punctuation">,</span> <span class="token symbol">:no_mail_domain</span><span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">valid_format</span></span><span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    value<span class="token punctuation">.</span>match<span class="token operator">?</span><span class="token punctuation">(</span><span class="token regex-literal"><span class="token regex">/\A</span><span class="token interpolation"><span class="token delimiter punctuation">#{</span><span class="token content"><span class="token constant">EMAIL_REGEX</span></span><span class="token delimiter punctuation">}</span></span><span class="token regex">\z/</span></span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">email_domain</span></span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    value<span class="token punctuation">.</span>split<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'@'</span></span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span>last
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">disposable_domain</span></span><span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    <span class="token constant">DISPOSABLE_EMAIL_DOMAINS</span><span class="token punctuation">.</span>key<span class="token operator">?</span><span class="token punctuation">(</span>email_domain<span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token keyword">end</span>

  <span class="token keyword">private</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">mail_domain</span></span><span class="token operator">?</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    Resolv<span class="token double-colon punctuation">::</span><span class="token constant">DNS</span><span class="token punctuation">.</span>open <span class="token keyword">do</span> <span class="token operator">|</span>dns<span class="token operator">|</span>
      dns<span class="token punctuation">.</span>getresources<span class="token punctuation">(</span>
        email_domain<span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span>
        Resolv<span class="token double-colon punctuation">::</span><span class="token constant">DNS</span><span class="token double-colon punctuation">::</span>Resource<span class="token double-colon punctuation">::</span><span class="token constant">IN</span><span class="token double-colon punctuation">::</span><span class="token constant">MX</span>
      <span class="token punctuation">)</span><span class="token punctuation">.</span>any<span class="token operator">?</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Some things to note:</p>
<ul>
<li>Line 1: The <code class="language-text">resolv</code> built-in Ruby library is used to check for MX nameserver
records</li>
<li>Line 4: The email regular expression</li>
<li>Lines 7-14: The list of blacklisted emails is kept in a constant as a <code class="language-text">Hash</code>
to speed up lookups (versus iterating over an <code class="language-text">Array</code>)</li>
<li>Lines 18 and 19: The <code class="language-text">allow_nil</code> and <code class="language-text">allow_blank</code> options are supported, much
like the <code class="language-text">validates_format_of</code> Rails validation. If set to <code class="language-text">true</code> with a
presence validation and the email is missing, the only error will be that from
the presence validation.</li>
<li>Lines 21-25: Format validation</li>
<li>Line 24: If the email fails the format validation, we early-<code class="language-text">return</code> since
the later checks are more expensive and we already know the email is invalid.</li>
<li>Lines 27-31: Disposable email domain validation</li>
<li>Lines 33-37: Mail domain validation</li>
</ul>
<p>To use it in a hypothetical model:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token operator">&lt;</span> ApplicationRecord
  validates<span class="token punctuation">(</span>
    <span class="token symbol">:email</span><span class="token punctuation">,</span>
    <span class="token symbol">presence</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    <span class="token symbol">uniqueness</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">case_sensitive</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token symbol">email</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span>
  <span class="token punctuation">)</span>

  <span class="token comment"># ...</span>
<span class="token keyword">end</span></code></pre></div>
<p>To skip checking disposable email domains:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby">email<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token symbol">non_disposable_domain</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span></code></pre></div>
<p>To skip checking non-email domains:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby">email<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token symbol">mail_domain</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span></code></pre></div>
<p>Update: An earlier version of this post used <code class="language-text">git pull</code> instead of <code class="language-text">git
submodule update</code> to update the submodule. Thanks to Fotis Alexandrou for
<a href="https://twitter.com/falexandrou/status/1251900610653425664">pointing it out</a>.</p>]]></description><link>https://angelos.dev//2020/04/diy-email-validation-in-rails/</link><guid isPermaLink="false">https://angelos.dev//2020/04/diy-email-validation-in-rails/</guid><pubDate>Sun, 19 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[DIY method memoization in Ruby]]></title><description><![CDATA[<p>I recently needed to have methods memoized in a Ruby class. The standard way to
do this is to use an instance variable and the <code class="language-text">||=</code> operator:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">Foo</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">bar</span></span>
    <span class="token variable">@bar</span> <span class="token operator">||=</span> some_expensive_call
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code></pre></div>
<p>But I didn’t want to pollute my class like this, so I ended up rolling my own
module with some metaprogramming to provide a nice DSL instead:</p>
<div class="gatsby-highlight" data-language="ruby"><pre style="counter-reset: linenumber 0" class="language-ruby line-numbers"><code class="language-ruby"><span class="token keyword">module</span> <span class="token class-name">Memoizable</span>
  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">memoize</span></span><span class="token punctuation">(</span>method_name<span class="token punctuation">)</span>
    original_method_name <span class="token operator">=</span> <span class="token string-literal"><span class="token string">"__original_</span><span class="token interpolation"><span class="token delimiter punctuation">#{</span><span class="token content">method_name</span><span class="token delimiter punctuation">}</span></span><span class="token string">"</span></span>

    alias_method<span class="token punctuation">(</span>original_method_name<span class="token punctuation">,</span> method_name<span class="token punctuation">)</span>

    instance_variable_name <span class="token operator">=</span> <span class="token string-literal"><span class="token string">"@</span><span class="token interpolation"><span class="token delimiter punctuation">#{</span><span class="token content">method_name</span><span class="token delimiter punctuation">}</span></span><span class="token string">"</span></span>

    <span class="token keyword">define_method</span> method_name <span class="token keyword">do</span>
      <span class="token keyword">if</span> <span class="token operator">!</span>instance_variable_defined<span class="token operator">?</span><span class="token punctuation">(</span>instance_variable_name<span class="token punctuation">)</span>
        instance_variable_set<span class="token punctuation">(</span>
          instance_variable_name<span class="token punctuation">,</span>
          send<span class="token punctuation">(</span>original_method_name<span class="token punctuation">)</span>
        <span class="token punctuation">)</span>
      <span class="token keyword">end</span>

      instance_variable_get<span class="token punctuation">(</span>instance_variable_name<span class="token punctuation">)</span>
    <span class="token keyword">end</span>
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>What the method does:</p>
<ul>
<li>Line 5: Back up the original method before overwriting it with the memoized
version</li>
<li>Line 9: Overwrite method with the memoized version</li>
<li>Line 10: If it’s never been called before</li>
<li>Lines 11-14: Call it and memoize the result</li>
<li>Line 17: Return the memoized result</li>
</ul>
<p>Using it is very easy:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">Foo</span>
  <span class="token keyword">extend</span> Memoizable

  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">bar</span></span>
    some_expensive_call
  <span class="token keyword">end</span>
  memoize <span class="token symbol">:bar</span>
<span class="token keyword">end</span></code></pre></div>
<p>And since <code class="language-text">def bar</code> returns <code class="language-text">:bar</code>, you can even do this:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">Foo</span>
  <span class="token keyword">extend</span> Memoizable

  memoize <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">bar</span></span>
    some_expensive_call
  <span class="token keyword">end</span>
<span class="token keyword">end</span></code></pre></div>
<p>That’s it!</p>]]></description><link>https://angelos.dev//2020/04/diy-method-memoization-in-ruby/</link><guid isPermaLink="false">https://angelos.dev//2020/04/diy-method-memoization-in-ruby/</guid><pubDate>Sat, 18 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[/username URLs in Rails]]></title><description><![CDATA[<p>I recently wanted to have <code class="language-text">/:username</code> URLs in a <a href="https://rubyonrails.org/">Rails</a> app. This post
describes how I did it.</p>
<p>In your User model:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token operator">&lt;</span> ApplicationRecord</code></pre></div>
<p>Add a format validation for the allowed characters in a username:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="gatsby-highlight-code-line"><span class="token constant">USERNAME_REGEX</span> <span class="token operator">=</span> <span class="token regex-literal"><span class="token regex">/\w+/</span></span><span class="token punctuation">.</span>freeze</span>
validates<span class="token punctuation">(</span>
  <span class="token symbol">:username</span><span class="token punctuation">,</span>
  <span class="token symbol">presence</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="gatsby-highlight-code-line">  <span class="token symbol">format</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">with</span><span class="token operator">:</span> <span class="token constant">USERNAME_REGEX</span><span class="token punctuation">,</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span><span class="token punctuation">)</span></code></pre></div>
<p>Add an exclusion validation to automatically reserve usernames that match
existing routes (e.g. to prevent a <code class="language-text">posts</code> username when a <code class="language-text">/posts</code> route
exists):</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="gatsby-highlight-code-line"><span class="token constant">RESERVED_USERNAMES</span> <span class="token operator">=</span> Rails<span class="token punctuation">.</span>application<span class="token punctuation">.</span>routes<span class="token punctuation">.</span>routes<span class="token punctuation">.</span>map <span class="token punctuation">{</span> <span class="token operator">|</span>route<span class="token operator">|</span></span><span class="gatsby-highlight-code-line">  route<span class="token punctuation">.</span>path<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>to_s<span class="token punctuation">[</span><span class="token regex-literal"><span class="token regex">%r{(?&lt;=\A/)\w+}</span></span><span class="token punctuation">]</span></span><span class="gatsby-highlight-code-line"><span class="token punctuation">}</span><span class="token punctuation">.</span>compact<span class="token punctuation">.</span>uniq<span class="token punctuation">.</span>freeze</span>
validates<span class="token punctuation">(</span>
  <span class="token symbol">:username</span><span class="token punctuation">,</span>
  <span class="token symbol">presence</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token symbol">format</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">with</span><span class="token operator">:</span> <span class="token constant">USERNAME_REGEX</span><span class="token punctuation">,</span> <span class="token symbol">allow_blank</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="gatsby-highlight-code-line">  <span class="token symbol">exclusion</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">in</span><span class="token operator">:</span> <span class="token constant">RESERVED_USERNAMES</span> <span class="token punctuation">}</span></span><span class="token punctuation">)</span></code></pre></div>
<p>Define <code class="language-text">to_param</code> so that the username (instead of id) is used for route path
and URL helpers:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">to_param</span></span>
  username
<span class="token keyword">end</span></code></pre></div>
<p>Finally, place the necessary route at the end of your routes file at
<code class="language-text">config/routes.rb</code> so that it matches last:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby">get<span class="token punctuation">(</span>
<span class="gatsby-highlight-code-line">  <span class="token string-literal"><span class="token string">':username'</span></span><span class="token punctuation">,</span></span>  <span class="token symbol">to</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">'users#show'</span></span><span class="token punctuation">,</span>
  <span class="token symbol">as</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">'user'</span></span><span class="token punctuation">,</span>
  <span class="token symbol">constraints</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">username</span><span class="token operator">:</span> User<span class="token double-colon punctuation">::</span><span class="token constant">USERNAME_REGEX</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span></code></pre></div>
<p>You can link to a user with:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token operator">&lt;</span><span class="token operator">%=</span> link_to <span class="token variable">@user</span><span class="token punctuation">.</span>username<span class="token punctuation">,</span> user_path<span class="token punctuation">(</span><span class="token variable">@user</span><span class="token punctuation">)</span> <span class="token operator">%</span><span class="token operator">></span></code></pre></div>
<p>That’s it!</p>]]></description><link>https://angelos.dev//2020/04/slash-username-urls-in-rails/</link><guid isPermaLink="false">https://angelos.dev//2020/04/slash-username-urls-in-rails/</guid><pubDate>Mon, 06 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[Beautiful system fonts in Debian]]></title><description><![CDATA[<p>Debian comes with <a href="https://dejavu-fonts.github.io/">DejaVu</a> as the default
system font which looks terrible (especially in Greek and <strong>bold</strong>).</p>
<p><a href="https://design.ubuntu.com/font/">Ubuntu</a> looks much nicer and ships with all
Ubuntu installations, so it’s somewhat considered a standard Linux font.
Microsoft’s standard fonts (Arial, Verdana, Georgia, Times New Roman, etc) are
also available in Debian.</p>
<p>This post provides instructions for setting Ubuntu as your default system font
and adding support for Microsoft’s fonts (many websites use them).</p>
<p>Note: All commands are to be issued in a terminal.</p>
<p>Edit your system’s <a href="https://wiki.debian.org/SourcesList">APT sources</a> file as
root (or with <code class="language-text">sudo</code>) located at <code class="language-text">/etc/apt/sources.list</code> and make sure it lists
<code class="language-text">non-free</code> (for the Ubuntu font) and <code class="language-text">contrib</code> (for Microsoft’s fonts). For
Debian Buster (current stable), you need something that looks like this:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">deb http://ftp.debian.org/debian/ buster main contrib non-free
deb-src http://ftp.debian.org/debian/ buster main contrib non-free</code></pre></div>
<p>Update the apt repository index:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> <span class="token function">apt</span> update</code></pre></div>
<p>Install the font packages:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> fonts-ubuntu fonts-liberation2 ttf-mscorefonts-installer</code></pre></div>
<p>Create the directory to host the config file:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">mkdir</span> <span class="token parameter variable">-p</span> ~/.config/fontconfig/</code></pre></div>
<p>Place the following configuration under <code class="language-text">~/.config/fontconfig/fonts.conf</code>:</p>
<div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml"><span class="token prolog">&lt;?xml version="1.0"?></span>
<span class="token doctype"><span class="token punctuation">&lt;!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">fontconfig</span> <span class="token name">SYSTEM</span> <span class="token string">"fonts.dtd"</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>fontconfig</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>alias</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>serif<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>prefer</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>Liberation Serif<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>prefer</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>alias</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>alias</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>sans-serif<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>prefer</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>Ubuntu<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>prefer</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>alias</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>alias</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>monospace<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>prefer</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>family</span><span class="token punctuation">></span></span>Ubuntu Mono<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>family</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>prefer</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>alias</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>fontconfig</span><span class="token punctuation">></span></span></code></pre></div>
<p>Go to your web browser settings and under font customization pick:</p>
<ul>
<li><em>sans-serif</em> (or <em>sans</em>) as default font</li>
<li><em>sans-serif</em> (or <em>sans</em>) as sans-serif font</li>
<li><em>serif</em> as serif font</li>
</ul>
<p>To check everything works as expected issue:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token keyword">for</span> <span class="token for-or-select variable">family</span> <span class="token keyword">in</span> serif sans-serif monospace Arial Helvetica Verdana <span class="token string">"Times New Roman"</span> <span class="token string">"Courier New"</span><span class="token punctuation">;</span> <span class="token keyword">do</span>
  <span class="token builtin class-name">echo</span> <span class="token parameter variable">-n</span> <span class="token string">"<span class="token variable">$family</span>: "</span>
  fc-match <span class="token string">"<span class="token variable">$family</span>"</span>
<span class="token keyword">done</span></code></pre></div>
<p>The output should look something like:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">serif: LiberationSerif-Regular.ttf: "Liberation Serif" "Regular"
sans-serif: Ubuntu-R.ttf: "Ubuntu" "Regular"
monospace: UbuntuMono-R.ttf: "Ubuntu Mono" "Regular"
Arial: Arial.ttf: "Arial" "Regular"
Helvetica: Arial.ttf: "Arial" "Regular"
Verdana: Verdana.ttf: "Verdana" "Regular"
Times New Roman: Times_New_Roman.ttf: "Times New Roman" "Regular"
Courier New: Courier_New.ttf: "Courier New" "Regular"</code></pre></div>
<p>Note:</p>
<ol>
<li>Helvetica is not a free font and you need to buy it online and install it.</li>
</ol>
<p>Microsoft’s Arial is a good approximation of Helvetica and Debian comes with
configuration to use it instead.</p>
<ol>
<li>You may need to logout and login again and/or restart programs for the</li>
</ol>
<p>changes to take effect.</p>]]></description><link>https://angelos.dev//2020/03/beautiful-fonts-in-debian-gnu-linux/</link><guid isPermaLink="false">https://angelos.dev//2020/03/beautiful-fonts-in-debian-gnu-linux/</guid><pubDate>Wed, 25 Mar 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[A simple access log middleware for Koa.js]]></title><description><![CDATA[<p>I recently needed a simple access log middleware for <a href="https://koajs.com/">Koa.js</a> to log incoming
requests, similar to what all web servers have:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">127.0.0.1 - - [19/Nov/2019:23:28:20 +0200] "GET /place/autocomplete HTTP/1.1" 200 1055</code></pre></div>
<p>I found <a href="https://github.com/koajs/accesslog">koa-accesslog</a> but was disappointed to find out it’s unmaintained and
written for <a href="https://koajs.com/">Koa.js</a> 1.x, using its soon-to-be-deprecated generator syntax and
throwing warnings about it in 2.x. It’s also hard-coding the HTTP version and
uses <a href="https://momentjs.com/">Moment.js</a> for formatting the date, which is not ideal.</p>
<p>So I decided to rewrite it to address all of the above issues. Here’s the
result:</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber 0" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> util <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'util'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="gatsby-highlight-code-line"><span class="token keyword">const</span> format <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'date-fns/format'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token comment">// ip, date, method, path, http version, status and length</span>
<span class="token keyword">const</span> <span class="token constant">LOG_FORMAT</span> <span class="token operator">=</span> <span class="token string">'%s - - [%s] "%s %s HTTP/%s" %d %s\n'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">DATE_FORMAT</span> <span class="token operator">=</span> <span class="token string">'d/MMM/yyyy:HH:mm:ss xx'</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">stream</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>  stream <span class="token operator">=</span> stream <span class="token operator">||</span> process<span class="token punctuation">.</span>stdout<span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">return</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">accesslog</span><span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
    stream<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>
      util<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>
        <span class="token constant">LOG_FORMAT</span><span class="token punctuation">,</span>
        ctx<span class="token punctuation">.</span>ip<span class="token punctuation">,</span>
<span class="gatsby-highlight-code-line">        <span class="token function">format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token constant">DATE_FORMAT</span><span class="token punctuation">)</span><span class="token punctuation">,</span></span>        ctx<span class="token punctuation">.</span>method<span class="token punctuation">,</span>
        ctx<span class="token punctuation">.</span>path<span class="token punctuation">,</span>
<span class="gatsby-highlight-code-line">        ctx<span class="token punctuation">.</span>req<span class="token punctuation">.</span>httpVersion<span class="token punctuation">,</span></span>        ctx<span class="token punctuation">.</span>status<span class="token punctuation">,</span>
        ctx<span class="token punctuation">.</span>length <span class="token operator">?</span> ctx<span class="token punctuation">.</span>length<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token string">'-'</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>It’s using <a href="https://date-fns.org/">date-fns</a> to format the date and time of the request (lines 2,
18). So you will need to run <code class="language-text">yarn add date-fns</code> or <code class="language-text">npm install date-fns</code> in
your project.</li>
<li>It accepts an optional <code class="language-text">stream</code> argument to log each request to (defaults to
<code class="language-text">process.stdout</code>)</li>
<li>It’s using the new 2.x <code class="language-text">async</code>/<code class="language-text">await</code> middleware syntax of <a href="https://koajs.com/">Koa.js</a> 2.x
(lines 11, 12)</li>
<li>It logs the HTTP version of the request (line 21)</li>
</ul>
<p>Here’s how to use it:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> accesslog <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./accesslog'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// local middleware file</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token function">accesslog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// ...</span>
app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p>Since I’m currently still experimenting with <a href="https://koajs.com/">Koa.js</a> and trying to decide
between it and <a href="https://expressjs.com/">Express.js</a>, I haven’t released this as a library yet. If
you’d be interested, please let me know.</p>]]></description><link>https://angelos.dev//2019/11/a-simple-access-log-middleware-for-koajs/</link><guid isPermaLink="false">https://angelos.dev//2019/11/a-simple-access-log-middleware-for-koajs/</guid><pubDate>Tue, 19 Nov 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Pass data between middleware in Koa.js]]></title><description><![CDATA[<p>I recently started playing with <a href="https://koajs.com/">Koa.js</a>, a minimal <a href="https://nodejs.org/">Node.js</a> framework by
the team behind <a href="https://expressjs.com/">Express.js</a>.</p>
<p>From the <a href="https://github.com/koajs/koa/blob/master/docs/koa-vs-express.md">official documentation</a>:</p>
<blockquote>
<p>Philosophically, Koa aims to “fix and replace node”, whereas Express “augments
node”. Koa uses promises and async functions to rid apps of callback hell and
simplify error handling. It exposes its own ctx.request and ctx.response
objects instead of node’s req and res objects.</p>
<p>Express, on the other hand, augments node’s req and res objects with
additional properties and methods and includes many other “framework”
features, such as routing and templating, which Koa does not.</p>
<p>Thus, Koa can be viewed as an abstraction of node.js’s http modules, where as
Express is an application framework for node.js.</p>
</blockquote>
<p>One thing I initially struggled with is how data is passed between middleware.
Once I figured it out, I came up with the following example to demonstrate it:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// First middleware (1)</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1</span>
<span class="gatsby-highlight-code-line">  ctx<span class="token punctuation">.</span>state<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'John'</span><span class="token punctuation">;</span> <span class="token comment">// 2</span></span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> age <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 4 (await), 9 (assignment)</span></span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 10</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>state<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>state<span class="token punctuation">.</span>surname<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>age<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> years old</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token comment">// 11</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Second middleware (2)</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 5</span>
<span class="gatsby-highlight-code-line">  ctx<span class="token punctuation">.</span>state<span class="token punctuation">.</span>surname <span class="token operator">=</span> <span class="token string">'Doe'</span><span class="token punctuation">;</span> <span class="token comment">// 6</span></span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 7</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">return</span> <span class="token number">33</span><span class="token punctuation">;</span> <span class="token comment">// 8</span></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p>Things to note:</p>
<ul>
<li>Numbers in comments indicate execution order</li>
<li>Middleware execution happens in a “stack order”</li>
<li><code class="language-text">ctx.state</code> can be used to share state between middleware</li>
<li>If a middleware returns a value, that value is returned to the parent
middleware that <code class="language-text">await</code>ed it</li>
</ul>
<p>Visit <a href="http://localhost:3000">http://localhost:3000</a> and you should see:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">John Doe is 33 years old</code></pre></div>
<p>With the following logs:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1 {}
1 { name: 'John' }
2 { name: 'John' }
2 { name: 'John', surname: 'Doe' }
1 { name: 'John', surname: 'Doe' }</code></pre></div>
<p>Enjoy!</p>]]></description><link>https://angelos.dev//2019/11/passing-data-between-middleware-in-koajs/</link><guid isPermaLink="false">https://angelos.dev//2019/11/passing-data-between-middleware-in-koajs/</guid><pubDate>Sun, 17 Nov 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[A better way to fetch and render records with "load more"]]></title><description><![CDATA[<p>I recently changed the index page of this blog to list only the 5 latest blog
posts and show a link to all posts if there are more.</p>
<p>How would you do it?</p>
<p>The most straightforward approach is to make a query to fetch 5 records and then
make another count query to check whether there are more.</p>
<p>A better way is to fetch 6 (N+1) records, render 5 (N), and check whether you
got that extra 6th (Nth) record to determine whether to show the “load more”
action. This saves you the count query.</p>
<p>An apparently very simple trick that never occurred to me before reading it
somewhere!</p>]]></description><link>https://angelos.dev//2019/10/a-better-way-to-fetch-and-render-records-with-load-more/</link><guid isPermaLink="false">https://angelos.dev//2019/10/a-better-way-to-fetch-and-render-records-with-load-more/</guid><pubDate>Tue, 01 Oct 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Add support for modification times in Gatsby]]></title><description><![CDATA[<p><strong>Note:</strong> This assumes you’ve already <a href="https://www.gatsbyjs.org/docs/adding-markdown-pages/">added support for Markdown
pages</a>.</p>
<p>What if you want to display the date and time a page or a post was last
modified?</p>
<p><a href="https://www.gatsbyjs.org/">Gatsby</a> exposes the date and time a file was last modified, as recorded in
the filesystem, through its <a href="https://www.gatsbyjs.org/docs/graphql/">GraphQL</a> API:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="graphql"><pre class="language-graphql"><code class="language-graphql"><span class="token punctuation">{</span>
  <span class="token object">markdownRemark</span> <span class="token punctuation">{</span>
    <span class="token object">parent</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span> <span class="token keyword">on</span> <span class="token class-name">File</span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">        <span class="token property">modifiedTime</span></span>      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>However, <a href="https://git-scm.com/">Git</a> does not retain file timestamp information. So if you ever <code class="language-text">git
clone</code> the repo (usually happens on deployment in services such as <a href="https://git-scm.com/">Netlify</a>),
the timestamp will be set to the current date and time. Obviously, this is not
what we want.</p>
<p>The solution lies in the fact that <a href="https://git-scm.com/">Git</a> records the date, time and modified
files of each commit. So we can pull the necessary information from the last
commit each file was modified in.</p>
<p>Open <code class="language-text">gatsby-node.js</code> and create a <code class="language-text">gitAuthorTime</code> node field:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="gatsby-highlight-code-line"><span class="token keyword">const</span> <span class="token punctuation">{</span> execSync <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'child_process'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
exports<span class="token punctuation">.</span><span class="token function-variable function">onCreateNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> node<span class="token punctuation">,</span> actions <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// ...</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>internal<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'MarkdownRemark'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">    <span class="token keyword">const</span> gitAuthorTime <span class="token operator">=</span> <span class="token function">execSync</span><span class="token punctuation">(</span></span><span class="gatsby-highlight-code-line">      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">git log -1 --pretty=format:%aI </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>node<span class="token punctuation">.</span>fileAbsolutePath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    actions<span class="token punctuation">.</span><span class="token function">createNodeField</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      node<span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">      <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'gitAuthorTime'</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">      <span class="token literal-property property">value</span><span class="token operator">:</span> gitAuthorTime</span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">  <span class="token punctuation">}</span></span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p>Modify the page query of your template (e.g. <code class="language-text">src/templates/post.jsx</code>) to fetch
the <code class="language-text">gitAuthorTime</code> node field:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="graphql"><pre class="language-graphql"><code class="language-graphql"><span class="token keyword">query</span><span class="token punctuation">(</span><span class="token variable">$slug</span><span class="token punctuation">:</span> <span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token property-query">markdownRemark</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">slug</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">eq</span><span class="token punctuation">:</span> <span class="token variable">$slug</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment"># ...</span>
<span class="gatsby-highlight-code-line">    <span class="token object">fields</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      <span class="token property">gitAuthorTime</span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span></span>    <span class="token comment"># ...</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Finally, in the same template file, you can access <code class="language-text">gitAuthorTime</code> through
props:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">Post</span> <span class="token operator">=</span> <span class="token parameter">props</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">  <span class="token keyword">const</span> <span class="token punctuation">{</span> gitAuthorTime <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">.</span>data<span class="token punctuation">.</span>markdownRemark<span class="token punctuation">.</span>fields<span class="token punctuation">;</span></span>
  <span class="token function">render</span> <span class="token punctuation">(</span>
<span class="gatsby-highlight-code-line">    <span class="token operator">&lt;</span>p<span class="token operator">></span>Modified at<span class="token operator">:</span> $<span class="token punctuation">{</span>gitAuthorTime<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> Post<span class="token punctuation">;</span></code></pre></div>]]></description><link>https://angelos.dev//2019/09/add-support-for-modification-times-in-gatsby/</link><guid isPermaLink="false">https://angelos.dev//2019/09/add-support-for-modification-times-in-gatsby/</guid><pubDate>Sat, 28 Sep 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Add support for drafts in Gatsby]]></title><description><![CDATA[<p><a href="https://www.gatsbyjs.org/">Gatsby</a> does not ship with support for drafts in Markdown posts, but it’s
pretty easy to implement it.</p>
<p><strong>Note:</strong> This assumes you’ve already <a href="https://www.gatsbyjs.org/docs/adding-markdown-pages/">added support for Markdown
pages</a>.</p>
<p>Add the following to <code class="language-text">gatsby-node.js</code>:</p>
<div class="gatsby-highlight has-highlighted-lines" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> env <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'process'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// eslint-disable-line no-undef</span>

exports<span class="token punctuation">.</span><span class="token function-variable function">onCreateNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> node<span class="token punctuation">,</span> actions <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// ...</span>

<span class="gatsby-highlight-code-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>internal<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'MarkdownRemark'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">    <span class="token keyword">const</span> listed <span class="token operator">=</span></span><span class="gatsby-highlight-code-line">      node<span class="token punctuation">.</span>frontmatter<span class="token punctuation">.</span>draft <span class="token operator">!==</span> <span class="token boolean">true</span> <span class="token operator">||</span> env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">'development'</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">    actions<span class="token punctuation">.</span><span class="token function">createNodeField</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      node<span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">      <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'listed'</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">      <span class="token literal-property property">value</span><span class="token operator">:</span> listed</span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="gatsby-highlight-code-line">  <span class="token punctuation">}</span></span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p>Modify the page query of your index page in <code class="language-text">src/pages/index.js</code> to include
draft posts in development and exclude them in production:</p>
<div class="gatsby-highlight" data-language="graphql"><pre class="language-graphql"><code class="language-graphql"><span class="token keyword">query</span> <span class="token punctuation">{</span>
  <span class="token property-query">allMarkdownRemark</span><span class="token punctuation">(</span>
<span class="gatsby-highlight-code-line">    <span class="token attr-name">filter</span><span class="token punctuation">:</span> <span class="token punctuation">{</span></span><span class="gatsby-highlight-code-line">      <span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">listed</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">eq</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></span><span class="gatsby-highlight-code-line">    <span class="token punctuation">}</span></span>    <span class="token attr-name">sort</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">frontmatter</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token attr-name">date</span><span class="token punctuation">:</span> <span class="token constant">DESC</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token object">edges</span> <span class="token punctuation">{</span>
      <span class="token object">node</span> <span class="token punctuation">{</span>
        <span class="token comment"># ...</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Repeat the process in <code class="language-text">gatsby-config.js</code> if you’ve <a href="https://www.gatsbyjs.org/docs/adding-an-rss-feed/">added an RSS feed</a>.</p>
<p>Finally, add <code class="language-text">draft: true</code> to the frontmatter of your draft posts and change it
to <code class="language-text">false</code> (or remove it altogether) when you want to publish them.</p>]]></description><link>https://angelos.dev//2019/09/add-support-for-drafts-in-gatsby/</link><guid isPermaLink="false">https://angelos.dev//2019/09/add-support-for-drafts-in-gatsby/</guid><pubDate>Fri, 27 Sep 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Render React components from Rails views]]></title><description><![CDATA[<p>I recently needed a way to render <a href="https://reactjs.org/">React</a> components from <a href="https://rubyonrails.org/">Rails</a> controller
views, without caring about <abbr title="Server Side Rendering">SSR</abbr>.</p>
<p>After searching a bit, I found <a href="https://github.com/renchap/webpacker-react">webpacker-react</a> but it seemed a bit
unmaintained. I was also reluctanct to add a dependency for something that (in
my mind) could be solved easily. In the end, I decided to go with a minimal,
custom implementation.</p>
<p>A good practice when you want to implement something is to first <em>think about
the ideal API and work backwards</em>. In my case this was the following helper call
inside a Rails controller view:</p>
<div class="gatsby-highlight" data-language="erb"><pre class="language-erb"><code class="language-erb"><span class="token erb language-erb"><span class="token delimiter punctuation">&lt;%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">'User'</span></span><span class="token punctuation">,</span> <span class="token symbol">name</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">'Angelos'</span></span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span></code></pre></div>
<p>Let’s define the helper in <code class="language-text">app/helpers/application_helper.rb</code>:</p>
<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">def</span> <span class="token method-definition"><span class="token function">react_component</span></span><span class="token punctuation">(</span>component<span class="token punctuation">,</span> props <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
  content_tag<span class="token punctuation">(</span>
    <span class="token string-literal"><span class="token string">'div'</span></span><span class="token punctuation">,</span>
    <span class="token keyword">nil</span><span class="token punctuation">,</span>
    <span class="token string-literal"><span class="token string">'data-react-component'</span></span> <span class="token operator">=></span> component<span class="token punctuation">,</span>
    <span class="token string-literal"><span class="token string">'data-react-props'</span></span> <span class="token operator">=></span> props<span class="token punctuation">.</span>to_json
  <span class="token punctuation">)</span>
<span class="token keyword">end</span></code></pre></div>
<p>The idea is simple: take a <code class="language-text">String</code> component name and an optional props <code class="language-text">Hash</code>
and return a <code class="language-text">&lt;div></code> element with the component name in a <code class="language-text">data-react-component</code>
attribute and its serialized props in a <code class="language-text">data-react-props</code> attribute. Then let
<a href="https://en.wikipedia.org/wiki/Unobtrusive_JavaScript">UJS</a> handle the rest.</p>
<p>So the helper’s output for our ideal API example mentioned above would be:</p>
<div class="gatsby-highlight" data-language="html"><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
  <span class="token attr-name">data-react-component</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span>
  <span class="token attr-name">data-react-props</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{ <span class="token entity named-entity" title="&quot;">&amp;quot;</span>name<span class="token entity named-entity" title="&quot;">&amp;quot;</span>: <span class="token entity named-entity" title="&quot;">&amp;quot;</span>Angelos<span class="token entity named-entity" title="&quot;">&amp;quot;</span> }<span class="token punctuation">"</span></span>
<span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre></div>
<p>Things to note:</p>
<ul>
<li><code class="language-text">"</code> inside HTML attributes are escaped as <code class="language-text">&amp;quot;</code></li>
<li>We want to mount the rendered component to this <code class="language-text">&lt;div></code> (its contents between
<code class="language-text">&lt;div></code> and <code class="language-text">&lt;/div></code>)</li>
</ul>
<p>And that’s it for the Rails part.</p>
<p>We now need some JavaScript code that finds all elements with a
<code class="language-text">data-react-component</code> attribute (a technique called <a href="https://en.wikipedia.org/wiki/Unobtrusive_JavaScript">UJS</a>), renders the
component of each with its props, and mounts it to the target element.</p>
<p>Here it is in <code class="language-text">app/javascript/support/react_ujs.js</code>:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">  components<span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">  renderComponent<span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">  <span class="token function-variable function">onBeforeMount</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">null</span><span class="token punctuation">,</span></span><span class="gatsby-highlight-code-line">  loadEventName <span class="token operator">=</span> <span class="token string">'DOMContentLoaded'</span></span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token function-variable function">mountComponentNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">node</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> componentAttr <span class="token operator">=</span> node<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>reactComponent<span class="token punctuation">;</span>
    <span class="token keyword">const</span> component <span class="token operator">=</span> components<span class="token punctuation">[</span>componentAttr<span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>component<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>componentAttr<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: not registered</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> propsAttr <span class="token operator">=</span> node<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>reactProps<span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>propsAttr<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>componentAttr<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: missing props</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> props <span class="token operator">=</span> propsAttr <span class="token operator">?</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>propsAttr<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token function">renderComponent</span><span class="token punctuation">(</span>component<span class="token punctuation">,</span> props<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> <span class="token function-variable function">mountComponentNodes</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">nodes</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    nodes<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>mountComponentNode<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> <span class="token function-variable function">mountComponents</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> componentNodes <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'[data-react-component]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">mountComponentNodes</span><span class="token punctuation">(</span>componentNodes<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>loadEventName<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">onBeforeMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">mountComponents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p>A function is exported that takes an <code class="language-text">Object</code> argument with the following
options:</p>
<ol>
<li><code class="language-text">components</code>: an <code class="language-text">Object</code> mapping <code class="language-text">String</code> component names to their
respective imported component objects</li>
<li><code class="language-text">renderComponent</code>: a function that takes an imported component object, its
props and the target element to mount it to</li>
<li><code class="language-text">onBeforeMount</code>: an optional callback to call after the DOM has loaded and
before doing (mounting) anything</li>
<li><code class="language-text">loadEventName</code>: the <code class="language-text">document</code> event name to listen to for mounting the
components (defaults to <code class="language-text">'DOMContentLoaded'</code>)</li>
</ol>
<p>Finally, the remaining “glue” logic in <code class="language-text">app/javascript/packs/application.js</code>:</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber 0" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> ReactDOM <span class="token keyword">from</span> <span class="token string">'react-dom'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> reactUJS <span class="token keyword">from</span> <span class="token string">'../support/react_ujs'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> User <span class="token keyword">from</span> <span class="token string">'../components/User'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> components <span class="token operator">=</span> <span class="token punctuation">{</span>
  User <span class="token comment">// equivalent to `User: User` in ES6</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">renderComponent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">component<span class="token punctuation">,</span> props<span class="token punctuation">,</span> target</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> element <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span>component<span class="token punctuation">,</span> props<span class="token punctuation">)</span><span class="token punctuation">;</span>
  ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">reactUJS</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  components<span class="token punctuation">,</span>
  renderComponent<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Things to note:</p>
<ul>
<li>Lines 1, 2, 4: Import React, <a href="https://reactjs.org/docs/react-dom.html">ReactDOM</a> and <code class="language-text">react_ujs.js</code></li>
<li>Lines 5, 7-9: Import and assign components</li>
<li>Lines 11-14: Define a <code class="language-text">renderComponent</code> function that renders a component to the DOM</li>
<li>Lines 16-19: Call <code class="language-text">reactUJS</code></li>
</ul>
<p>A possible use of <code class="language-text">onBeforeMount</code> is to set up some context before the component
is rendered.</p>
<p>For example, to set up <a href="https://github.com/FormidableLabs/urql">urql</a> for performing <a href="https://graphql.org/">GraphQL</a> queries:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> ReactDOM <span class="token keyword">from</span> <span class="token string">'react-dom'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Provider<span class="token punctuation">,</span> createClient<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'urql'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> reactUJS <span class="token keyword">from</span> <span class="token string">'../support/react_ujs'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> User <span class="token keyword">from</span> <span class="token string">'../components/User'</span><span class="token punctuation">;</span>

<span class="gatsby-highlight-code-line"><span class="token keyword">let</span> client<span class="token punctuation">;</span></span>
<span class="token keyword">const</span> components <span class="token operator">=</span> <span class="token punctuation">{</span>
  User
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">renderComponent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">component<span class="token punctuation">,</span> props<span class="token punctuation">,</span> target</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> element <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span>component<span class="token punctuation">,</span> props<span class="token punctuation">)</span><span class="token punctuation">;</span>

  ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>client<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span>
<span class="token plain-text">      </span><span class="token punctuation">{</span>element<span class="token punctuation">}</span><span class="token plain-text"></span>
<span class="token plain-text">    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Provider</span></span><span class="token punctuation">></span></span><span class="token punctuation">,</span>
    target
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">onBeforeMount</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> csrfToken <span class="token operator">=</span>
    document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'meta[name="csrf-token"]'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'content'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  client <span class="token operator">=</span> <span class="token function">createClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="gatsby-highlight-code-line">    <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">'http://localhost:3000/graphql'</span><span class="token punctuation">,</span></span>    <span class="token literal-property property">fetchOptions</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">'X-CSRF-Token'</span><span class="token operator">:</span> csrfToken
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">reactUJS</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  components<span class="token punctuation">,</span>
  renderComponent<span class="token punctuation">,</span>
  onBeforeMount
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p>Things to note:</p>
<ul>
<li><code class="language-text">client</code> is accessible as a closure both by <code class="language-text">renderComponent</code> and
<code class="language-text">onBeforeMount</code></li>
<li>You’ll want to change the <code class="language-text">client</code>’s <code class="language-text">url</code> in production</li>
</ul>
<p>Finally, if you have <a href="https://github.com/turbolinks/turbolinks">Turbolinks</a> enabled, you’ll want to mount the components
each time Turbolinks loads instead of on <code class="language-text">DOMContentLoaded</code>. So the final call
to <code class="language-text">reactUJS</code> becomes:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token function">reactUJS</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  components<span class="token punctuation">,</span>
  renderComponent<span class="token punctuation">,</span>
  <span class="token literal-property property">loadEventName</span><span class="token operator">:</span> <span class="token string">'turbolinks:load'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>]]></description><link>https://angelos.dev//2019/09/render-react-components-from-rails-views/</link><guid isPermaLink="false">https://angelos.dev//2019/09/render-react-components-from-rails-views/</guid><pubDate>Sat, 21 Sep 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[More fun with custom React Hooks]]></title><description><![CDATA[<p>Inspired by <a href="https://reactjs.org/docs/hooks-faq.html#can-i-run-an-effect-only-on-updates">this FAQ</a> on React Hooks, I came up with a custom hook that
lets you provide separate callbacks for when the component is mounted,
unmounted, updated, as well as a clean callback for the update:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useGranularEffect</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onMount<span class="token punctuation">,</span> onUnmount<span class="token punctuation">,</span> onUpdate<span class="token punctuation">,</span> onUpdateClean <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> updateRef <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useRef</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>updateRef<span class="token punctuation">.</span>current <span class="token operator">&amp;&amp;</span> onUpdate<span class="token punctuation">)</span> <span class="token function">onUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    updateRef<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> onUpdateClean<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>onMount<span class="token punctuation">)</span> <span class="token function">onMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> onUnmount<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// eslint-disable-line react-hooks/exhaustive-deps</span>
<span class="token punctuation">}</span></code></pre></div>
<p>For the demo, I also wrote a custom <code class="language-text">useForceUpdate</code> hook to force the example
component to update:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useForceUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> setUpdate <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setUpdate</span><span class="token punctuation">(</span><span class="token parameter">update</span> <span class="token operator">=></span> <span class="token operator">!</span>update<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Toggle to trigger update</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>And a custom hook to mount/unmount a node:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useToggleNode</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> initialState <span class="token operator">=</span> <span class="token boolean">true</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>toggledNode<span class="token punctuation">,</span> setToggledNode<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span>
    initialState <span class="token operator">?</span> node <span class="token operator">:</span> <span class="token keyword">null</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">function</span> <span class="token function">toggleNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setToggledNode</span><span class="token punctuation">(</span><span class="token parameter">toggledNode</span> <span class="token operator">=></span> <span class="token punctuation">(</span>toggledNode <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">[</span>toggledNode<span class="token punctuation">,</span> toggleNode<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Finally, the rest of the demo code:</p>
<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> ReactDOM <span class="token keyword">from</span> <span class="token string">'react-dom'</span><span class="token punctuation">;</span>

<span class="token comment">// function useGranularEffect(...</span>

<span class="token comment">// function useForceUpdate(...</span>

<span class="token comment">// function useToggleNode(...</span>

<span class="token keyword">function</span> <span class="token function">Example</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> forceUpdate <span class="token operator">=</span> <span class="token function">useForceUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">useGranularEffect</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function-variable function">onMount</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'mount!'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function-variable function">onUnmount</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'unmount!'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function-variable function">onUpdate</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'update!'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function-variable function">onUpdateClean</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clean!'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>forceUpdate<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">render</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>toggledNode<span class="token punctuation">,</span> toggleNode<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useToggleNode</span><span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Example</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span>toggledNode<span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token string">' '</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>toggleNode<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>toggledNode <span class="token operator">?</span> <span class="token string">'unmount'</span> <span class="token operator">:</span> <span class="token string">'mount'</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> rootElement <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">App</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">,</span> rootElement<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>]]></description><link>https://angelos.dev//2019/05/more-fun-with-custom-react-hooks-usegranulareffect-useforceupdate-and-usetogglenode/</link><guid isPermaLink="false">https://angelos.dev//2019/05/more-fun-with-custom-react-hooks-usegranulareffect-useforceupdate-and-usetogglenode/</guid><pubDate>Fri, 10 May 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Wrapping my head around React's useRef]]></title><description><![CDATA[<p>While learning about <a href="https://reactjs.org/docs/hooks-intro.html">React Hooks</a>, I had a hard time understanding the case
for <a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>. Then I ran into a tweet by Dan Abramov (no longer available)
that helped me realize <code class="language-text">useRef(initialValue)</code> is essentially
<code class="language-text">useState({ current: initialValue })[0]</code>. <a href="https://reactjs.org/docs/hooks-reference.html#usestate">useState</a> returns an array where
the first element is the state and the second is its setter. <code class="language-text">[0]</code> means the
setter is discarded.</p>
<p>This is useful in cases where we want to persist state between renders and
modify it <em>without</em> triggering a render (contrary to <code class="language-text">useState</code>). It works
because the state (an object) is modified directly (i.e. <code class="language-text">myRef.current = 42</code>)
and not through its setter (which triggers the render).</p>
<p>That’s also why the returned state is <code class="language-text">{ current: initialValue }</code> (an object)
and not just <code class="language-text">initialValue</code> (a primitive value). Without a setter, it’s not
possible to modify it:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// State is a primitive value</span>
<span class="token keyword">let</span> name <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">'Angelos'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// Discard setter method</span>
<span class="token comment">// name === 'Angelos'</span>
name <span class="token operator">=</span> <span class="token string">'John'</span><span class="token punctuation">;</span> <span class="token comment">// This just modifies the local variable "name"</span>

<span class="token comment">// State is a reference to an object</span>
<span class="token keyword">const</span> nameRef <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">current</span><span class="token operator">:</span> <span class="token string">'Angelos'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// Discard setter method</span>
<span class="token comment">// nameRef.current === 'Angelos'</span>
nameRef<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token string">'John'</span><span class="token punctuation">;</span> <span class="token comment">// This modifies the object's "current" property</span></code></pre></div>]]></description><link>https://angelos.dev//2019/05/wrapping-my-head-around-reacts-useref/</link><guid isPermaLink="false">https://angelos.dev//2019/05/wrapping-my-head-around-reacts-useref/</guid><pubDate>Wed, 08 May 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Custom React hook to prevent window unload]]></title><description><![CDATA[<p>I recently needed a way to prevent the window/tab from closing if there were
unsaved changes in an application and came up with the following simple, <a href="https://reactjs.org/docs/hooks-custom.html">custom
React hook</a>:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">usePreventWindowUnload</span><span class="token punctuation">(</span><span class="token parameter">preventDefault</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>preventDefault<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token function-variable function">handleBeforeUnload</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"beforeunload"</span><span class="token punctuation">,</span> handleBeforeUnload<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">"beforeunload"</span><span class="token punctuation">,</span> handleBeforeUnload<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>preventDefault<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>If <code class="language-text">preventDefault</code> is <code class="language-text">true</code>, the window will not close. If it’s <code class="language-text">false</code>, it
will.</p>
<p>I use it in my <code class="language-text">App</code> component, passing it a <code class="language-text">savedChanges</code> prop:</p>
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> savedChanges <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">usePreventWindowUnload</span><span class="token punctuation">(</span><span class="token operator">!</span>savedChanges<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre></div>
<p>I hope it helps you!</p>]]></description><link>https://angelos.dev//2019/05/custom-react-hook-to-prevent-window-unload/</link><guid isPermaLink="false">https://angelos.dev//2019/05/custom-react-hook-to-prevent-window-unload/</guid><pubDate>Tue, 07 May 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Install the latest Firefox securely in Debian]]></title><description><![CDATA[<p><em>Update: There’s an <a href="https://support.mozilla.org/en-US/kb/install-firefox-linux">official APT repository</a></em></p>
<p>Since Debian does not provide an easy way to install the latest version of
Firefox, I’ve written a script to install the latest official binary from
Mozilla.</p>
<p>To make the process as secure as possible, the relevant binary is downloaded and
its checksum is verified against the published checksum by Mozilla. The digital
signature of the checksum file is also verified using Mozilla’s GPG key.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/usr/bin/env bash</span>

<span class="token builtin class-name">trap</span> <span class="token builtin class-name">exit</span> INT

<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-n</span> <span class="token variable"><span class="token variable">$(</span>pgrep firefox<span class="token variable">)</span></span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token builtin class-name">echo</span> <span class="token string">'error: Firefox is running'</span>
  <span class="token builtin class-name">exit</span> <span class="token number">1</span>
<span class="token keyword">fi</span>

<span class="token assign-left variable">target_version</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>
  <span class="token function">wget</span> <span class="token parameter variable">-qS</span> <span class="token parameter variable">--spider</span> https://www.mozilla.org/en-US/firefox/notes/ <span class="token operator"><span class="token file-descriptor important">2</span>></span><span class="token file-descriptor important">&amp;1</span> <span class="token operator">|</span> <span class="token punctuation">\</span>
    <span class="token function">grep</span> <span class="token parameter variable">-i</span> location: <span class="token operator">|</span> <span class="token punctuation">\</span>
    <span class="token function">cut</span> <span class="token parameter variable">-d</span> / <span class="token parameter variable">-f</span> <span class="token number">4</span>
<span class="token variable">)</span></span>

<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-z</span> <span class="token string">"<span class="token variable">$target_version</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token builtin class-name">echo</span> <span class="token string">'error: Failed to parse latest Firefox release version'</span>
  <span class="token builtin class-name">exit</span> <span class="token number">2</span>
<span class="token keyword">fi</span>

<span class="token assign-left variable">mozilla_key</span><span class="token operator">=</span>BBBEBDBB24C6F355
<span class="gatsby-highlight-code-line"><span class="token assign-left variable">platform</span><span class="token operator">=</span>linux-x86_64</span><span class="gatsby-highlight-code-line"><span class="token assign-left variable">locale</span><span class="token operator">=</span>en-US</span><span class="token assign-left variable">base_name</span><span class="token operator">=</span>firefox-<span class="token variable">$target_version</span>.tar.bz2
<span class="token assign-left variable">file_name</span><span class="token operator">=</span><span class="token variable">$platform</span>/<span class="token variable">$locale</span>/<span class="token variable">$base_name</span>
<span class="token assign-left variable">base_url</span><span class="token operator">=</span>https://releases.mozilla.org/pub/firefox/releases/<span class="token variable">$target_version</span>
<span class="token assign-left variable">tmp_dir</span><span class="token operator">=</span>/tmp/firefox-update-<span class="token variable">$target_version</span>

<span class="token function">rm</span> <span class="token parameter variable">-rf</span> <span class="token variable">$tmp_dir</span>
<span class="token function">mkdir</span> <span class="token variable">$tmp_dir</span>
<span class="token function">pushd</span> <span class="token variable">$tmp_dir</span>

<span class="token function">wget</span> <span class="token variable">$base_url</span>/SHA512SUMS <span class="token variable">$base_url</span>/SHA512SUMS.asc

gpg --recv-keys <span class="token variable">$mozilla_key</span> <span class="token operator">&amp;&amp;</span> gpg <span class="token parameter variable">--verify</span> SHA512SUMS.asc SHA512SUMS

<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$?</span> <span class="token parameter variable">-ne</span> <span class="token number">0</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token builtin class-name">echo</span> <span class="token string">'error: Failed to verify signature for SHA512SUMS'</span>
  <span class="token builtin class-name">exit</span> <span class="token number">3</span>
<span class="token keyword">fi</span>

<span class="token function">wget</span> <span class="token variable">$base_url</span>/<span class="token variable">$file_name</span>

<span class="token assign-left variable">expected_sum</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">grep</span> $file_name SHA512SUMS <span class="token operator">|</span> <span class="token function">cut</span> <span class="token parameter variable">-d</span> <span class="token string">' '</span> <span class="token parameter variable">-f</span> <span class="token number">1</span> <span class="token operator">|</span> <span class="token function">sed</span> <span class="token string">"s/^b'\|'$//g"</span><span class="token variable">)</span></span>
<span class="token builtin class-name">echo</span> <span class="token string">"<span class="token variable">$expected_sum</span>  <span class="token variable">$base_name</span>"</span> <span class="token operator">|</span> sha512sum <span class="token parameter variable">-c</span> -

<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$?</span> <span class="token parameter variable">-ne</span> <span class="token number">0</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token builtin class-name">echo</span> <span class="token string">"error: Invalid checksum for <span class="token variable">$base_name</span>"</span>
  <span class="token builtin class-name">exit</span> <span class="token number">4</span>
<span class="token keyword">fi</span>

<span class="token function">sudo</span> <span class="token function">rm</span> <span class="token parameter variable">-rf</span> /opt/firefox

<span class="gatsby-highlight-code-line"><span class="token function">pushd</span> /opt</span><span class="token function">sudo</span> <span class="token function">tar</span> <span class="token parameter variable">-xvjf</span> <span class="token variable">$tmp_dir</span>/<span class="token variable">$base_name</span>
<span class="token function">popd</span>

<span class="token function">popd</span>
<span class="token function">rm</span> <span class="token parameter variable">-rf</span> <span class="token variable">$tmp_dir</span></code></pre></div>
<p>Things to note:</p>
<ul>
<li>Platform is <code class="language-text">x86_64</code> (configurable)</li>
<li>Locale is <code class="language-text">en-US</code> (configurable)</li>
<li>Installation path is <code class="language-text">/opt/firefox/</code></li>
</ul>]]></description><link>https://angelos.dev//2019/05/install-the-latest-firefox-securely-in-debian/</link><guid isPermaLink="false">https://angelos.dev//2019/05/install-the-latest-firefox-securely-in-debian/</guid><pubDate>Thu, 02 May 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Γράφοντας ελληνικά αποδοτικά στον Vim]]></title><description><![CDATA[<p>Ένα συχνό παράπονο στον <a href="http://www.vim.org/">Vim</a> είναι ότι το να γράφει κανείς ελληνικά είναι πολύ κουραστικό γιατί πρέπει συνεχώς να βγαίνει σε normal mode και να αλλάζει σε αγγλικά για να εκτελέσει οποιαδήποτε εντολή (και μετά να ακολουθήσει την αντίστροφη διαδικασία για να συνεχίσει να γράφει ελληνικά).</p>
<p>Προσθέτοντας τις ακόλουθες γραμμές στο <code class="language-text">~/.vimrc</code>:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">set keymap=greek_utf-8
set iminsert=0
set imsearch=-1</code></pre></div>
<p>… μπορεί κανείς να γράφει ελληνικά σε insert mode και να γυρνάει σε normal mode για να εκτελέσει μια εντολή <em>χωρίς</em> να χρειάζεται να αλλάξει σε αγγλικά.</p>
<p>Αυτό γίνεται ως εξής:</p>
<ul>
<li>Βρισκόμαστε σε normal mode και πατάμε <code class="language-text">i</code> για να αλλάξουμε σε insert mode</li>
<li>Πατάμε <code class="language-text">Ctrl+6</code> και η γλώσσα <em>στον <a href="http://www.vim.org/">Vim</a></em> (και όχι του συστήματος) γυρνάει σε ελληνικά</li>
<li>Γράφουμε ελληνικά</li>
<li>Αν κάποια στιγμή θέλουμε να αλλάξουμε σε normal mode για να εκτελέσουμε μια εντολή απλά πατάμε <code class="language-text">Esc</code>, εκτελούμε την εντολή και μετά ξανά <code class="language-text">i</code> για να επιστρέψουμε σε insert mode</li>
<li>Αν κάποια στιγμή, ενώ γράφουμε ελληνικά, θέλουμε να γράψουμε κάτι στα αγγλικά, απλά ξαναπατάμε <code class="language-text">Ctrl+6</code> και η γλώσσα αλλάζει σε αγγλικά</li>
</ul>
<p>That’s it! Ουσιαστικά αντί να πατάμε το κλασικό <code class="language-text">Alt+Shift</code> για να αλλάζουμε τη γλώσσα του συστήματος, πατάμε <code class="language-text">Ctrl+6</code> και αλλάζουμε γλώσσα μόνο μέσα στον <a href="http://www.vim.org/">Vim</a>.</p>]]></description><link>https://angelos.dev//2016/09/grafontas-ellinika-apodotika-ston-vim/</link><guid isPermaLink="false">https://angelos.dev//2016/09/grafontas-ellinika-apodotika-ston-vim/</guid><pubDate>Fri, 16 Sep 2016 00:00:00 GMT</pubDate></item><item><title><![CDATA[Low battery notification in i3wm]]></title><description><![CDATA[<p>I have been a happy <a href="https://i3wm.org/">i3wm</a> user since 2012.</p>
<p>A problem I faced early on was that I would often forget to connect my laptop’s charger so it would eventually shut down. Even though <a href="https://i3wm.org/i3status/">i3status</a> can display the current battery level, I have found that it is very easy to miss.</p>
<p>After many unexpected reboots, I decided to write a simple Bash script that displays a notification when the battery level percentage reaches or drops below a configured threshold:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/usr/bin/env bash</span>

<span class="gatsby-highlight-code-line"><span class="token assign-left variable">THRESHOLD</span><span class="token operator">=</span><span class="token number">10</span></span>
<span class="token assign-left variable">lock_path</span><span class="token operator">=</span><span class="token string">'/tmp/batmon.lock'</span>

<span class="gatsby-highlight-code-line">lockfile-create <span class="token parameter variable">-r</span> <span class="token number">0</span> <span class="token parameter variable">-l</span> <span class="token variable">$lock_path</span> <span class="token operator">||</span> <span class="token builtin class-name">exit</span></span>
<span class="token assign-left variable">acpi_path</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">find</span> /sys/class/power_supply/ <span class="token parameter variable">-name</span> <span class="token string">'BAT*'</span> <span class="token operator">|</span> <span class="token function">head</span> <span class="token parameter variable">-1</span><span class="token variable">)</span></span>
<span class="token assign-left variable">charge_status</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">cat</span> <span class="token string">"<span class="token variable">$acpi_path</span>/status"</span><span class="token variable">)</span></span>
<span class="token assign-left variable">charge_percent</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>acpi <span class="token operator">|</span> <span class="token function">cut</span> <span class="token parameter variable">-d</span> <span class="token string">' '</span> <span class="token parameter variable">-f</span> <span class="token number">4</span> <span class="token operator">|</span> <span class="token function">sed</span> <span class="token string">'s/[^0-9]//g'</span><span class="token variable">)</span></span>

<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$charge_status</span> <span class="token operator">==</span> <span class="token string">'Discharging'</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$charge_percent</span> <span class="token parameter variable">-le</span> <span class="token variable">$THRESHOLD</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token assign-left variable">message</span><span class="token operator">=</span><span class="token string">"Battery running critically low at <span class="token variable">$charge_percent</span>%!"</span>
<span class="gatsby-highlight-code-line">  <span class="token assign-left variable"><span class="token environment constant">DISPLAY</span></span><span class="token operator">=</span>:0.0 /usr/bin/notify-send <span class="token parameter variable">-u</span> critical <span class="token string">'Low battery'</span> <span class="token string">"<span class="token variable">$message</span>"</span></span><span class="token keyword">fi</span>

<span class="token function">rm</span> <span class="token parameter variable">-f</span> <span class="token variable">$lock_path</span></code></pre></div>
<p><code class="language-text">lockfile-create</code> and <code class="language-text">notify-send</code> are two dependencies. In Debian, you can install them with:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> lockfile-progs libnotify-bin</code></pre></div>
<p>Some things to note:</p>
<ul>
<li>The threshold percentage is configurable</li>
<li>The script will exit if it’s already running to avoid multiple notifications e.g. when run by Cron</li>
<li>A notification is shown using <em>notify-send</em> (ships with <a href="https://packages.debian.org/search?keywords=libnotify-bin">libnotify-bin</a> on Debian)</li>
</ul>
<p>I run the script every 5 minutes with Cron by writing the following after running <code class="language-text">crontab -e</code>:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">*/5 * * * * eval "export $(egrep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -ou agorf dbus-daemon)/environ)"; bash ~/work/scripts/batmon.sh</code></pre></div>]]></description><link>https://angelos.dev//2016/06/low-battery-notification-in-i3wm/</link><guid isPermaLink="false">https://angelos.dev//2016/06/low-battery-notification-in-i3wm/</guid><pubDate>Wed, 29 Jun 2016 00:00:00 GMT</pubDate></item></channel></rss>