tag:coreyward.svbtle.com,2014:/feedCorey Ward2023-03-07T18:42:31-08:00Corey Wardhttps://coreyward.svbtle.comcorey.atx@gmail.comSvbtle.comtag:coreyward.svbtle.com,2014:Post/reordering-presets-in-lightroom-classic2023-03-07T18:42:31-08:002023-03-07T18:42:31-08:00Reordering Presets and Preset Groups in Lightroom Classic<p>Lightroom presets are an incredibly powerful tool for managing and editing your photos quickly and consistently, but as your catalog of presets grows, so too does the time you spend scrolling through those presets looking for the one you want. </p>
<p>Lightroom offers favorites as a way to elevate your most frequently used presets to the top, but stops short of providing the tools to organize a library of presets (especially user-defined presets) via the user-interface. Thankfully, though, there is a way to do just this! In this article, we’ll take a closer look at how presets are organized in Lightroom Classic and how you can reorder them to better suit your workflow.</p>
<h2 id="presets-panel-anatomy_2">Presets Panel Anatomy <a class="head_anchor" href="#presets-panel-anatomy_2">#</a>
</h2>
<p><a href="https://svbtleusercontent.com/4CbKPGv1ML7iE2dREcZcZG0xspap.png"><img src="https://svbtleusercontent.com/4CbKPGv1ML7iE2dREcZcZG0xspap_small.png" alt="Anatomy of the Presets Panel in Lightroom Classic.png"></a></p>
<p>The Presets Panel is located on the left side of the photo in the Develop module. Within the panel, you’ll notice a series of sections divided by horizontal lines. Each of these sections is referred to internally by Lightroom as a “cluster”. Each cluster contains one or more preset groups, each containing the presets you’ve added to Lightroom.</p>
<p>In part, these clusters are defined by Lightroom, but the rest is up to the presets themselves. Let’s break this down section by section for clarity:</p>
<ol>
<li>The first cluster is always “Favorites”*</li>
<li>The second cluster is always “User Presets”**</li>
<li>The next clusters are user-provided presets:
a. First, presets that have a group specified and no cluster specified
b. Next, presets that have both a cluster and group specified</li>
</ol>
<p>Preset-defined clusters are ordered alphabetically by their name (that is not shown in Lightroom). Groups within each cluster are also ordered alphabetically by their name. Presets in each group are ordered alphabetically by name, with the one caveat that they can also have a separate <code class="prettyprint">SortName</code> that is only used for sorting. If this is confusing, keep reading.</p>
<h2 id="preset-files-xmp_2">Preset Files (XMP) <a class="head_anchor" href="#preset-files-xmp_2">#</a>
</h2>
<p>Presets are defined using the XMP file format, which contains information about the preset’s settings and its name, group, and cluster. By editing the XMP files, you can change the preset’s name, group, cluster, and even its order within the group and cluster.</p>
<p>The XMP format is a superset of XML, which is a text-based markup language that’s fairly approachable and easy to edit, particularly for the purposes of this guide. If you’re not comfortable doing so, a 5-minute YouTube video on editing XML files should be all you need to proceed.</p>
<p>Here is an example preset of mine called “-30 Sat”, which sets the saturation values for each HSL slider to -30, in its entirety:</p>
<pre><code class="prettyprint lang-xml"><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.0-c000 1.000000, 0000/00/00-00:00:00 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/"
crs:PresetType="Normal"
crs:Cluster=""
crs:UUID="A2F1825ED7A04243B1766E7E1A2E5963"
crs:SupportsAmount2="True"
crs:SupportsAmount="True"
crs:SupportsColor="True"
crs:SupportsMonochrome="True"
crs:SupportsHighDynamicRange="True"
crs:SupportsNormalDynamicRange="True"
crs:SupportsSceneReferred="True"
crs:SupportsOutputReferred="True"
crs:RequiresRGBTables="False"
crs:Version="14.4.1"
crs:SaturationAdjustmentRed="-30"
crs:SaturationAdjustmentOrange="-30"
crs:SaturationAdjustmentYellow="-30"
crs:SaturationAdjustmentGreen="-30"
crs:SaturationAdjustmentAqua="-30"
crs:SaturationAdjustmentBlue="-30"
crs:SaturationAdjustmentPurple="-30"
crs:SaturationAdjustmentMagenta="-30"
crs:HasSettings="True">
<crs:Name>
<rdf:Alt>
<!-- The name of the preset -->
<rdf:li xml:lang="x-default">-30 Sat</rdf:li>
</rdf:Alt>
</crs:Name>
<crs:ShortName>
<rdf:Alt>
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:ShortName>
<crs:SortName>
<rdf:Alt>
<!-- The “SortName” for the preset (currently not set) -->
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:SortName>
<crs:Group>
<rdf:Alt>
<!-- The “Group” name for the preset (currently not set) -->
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:Group>
<crs:Description>
<rdf:Alt>
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:Description>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
</code></pre>
<p>There are a lot of lines here, but there are only a few that are relevant. </p>
<h3 id="cluster_3">Cluster <a class="head_anchor" href="#cluster_3">#</a>
</h3>
<pre><code class="prettyprint lang-xml">crs:Cluster=""
</code></pre>
<p>This specifies the cluster that the preset belongs to. As far as I can tell, the cluster name is never shown inside of Lightroom, but it <em>is</em> used to sort clusters that all belong at the same level. From my testing, the cluster name cannot contain special characters like “&” or it will cause the preset to not show up at all. </p>
<p>This preset doesn’t have a cluster. If we wanted to set one named “Utilities”, that would look like this:</p>
<pre><code class="prettyprint lang-xml">crs:Cluster="Utilities"
</code></pre>
<h3 id="name_3">Name <a class="head_anchor" href="#name_3">#</a>
</h3>
<pre><code class="prettyprint lang-xml"> <crs:Name>
<rdf:Alt>
<rdf:li xml:lang="x-default">-30 Sat</rdf:li>
</rdf:Alt>
</crs:Name>
</code></pre>
<p>The name is what the preset is called in Lightroom, and assuming your presets are in your user folder, you can actually change this value in Lightroom by right clicking on the preset and choosing rename (more on that later). </p>
<h3 id="sortname_3">SortName <a class="head_anchor" href="#sortname_3">#</a>
</h3>
<pre><code class="prettyprint lang-xml"><crs:SortName>
<rdf:Alt>
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:SortName>
</code></pre>
<p>The <code class="prettyprint">SortName</code> field isn’t shown to the user, but if it’s set, it’ll be used instead of the <code class="prettyprint">Name</code> field to determine the order of your preset. This can be useful for groups of presets that should be listed in a particular order. Adding a hyphen (<code class="prettyprint">-</code>) to the beginning of the name, for example, can cause it to show up below other presets starting with letters—maybe you don’t want that to happen, so you could provide the name without the hyphen at the beginning for the <code class="prettyprint">SortName</code> (or vice versa).</p>
<p>Note that in the example above the value is empty. To set a value, replace <code class="prettyprint"><rdf:li xml:lang="x-default"/></code> with <code class="prettyprint"><rdf:li xml:lang="x-default">Your value here</rdf:li></code>.</p>
<pre><code class="prettyprint lang-diff"> <crs:SortName>
<rdf:Alt>
- <rdf:li xml:lang="x-default"/>
+ <rdf:li xml:lang="x-default">Your value here</rdf:li>
</rdf:Alt>
</crs:SortName>
</code></pre>
<h3 id="group_3">Group <a class="head_anchor" href="#group_3">#</a>
</h3>
<pre><code class="prettyprint lang-xml"><crs:Group>
<rdf:Alt>
<rdf:li xml:lang="x-default"/>
</rdf:Alt>
</crs:Group>
</code></pre>
<p>As you can probably guess by now, this is the name of the preset group that the preset belongs to within the cluster. This is shown to the user, if it’s set, and if not, the preset will be shown in the “User Presets” group.</p>
<h2 id="editing-your-presets_2">Editing your presets <a class="head_anchor" href="#editing-your-presets_2">#</a>
</h2>
<p>Armed with this information you’re ready to start editing your presets. But first you have to find them. Depending on how you installed them, they can show up in a number of different places.</p>
<p><strong>In your user Library folder:</strong><br>
<code class="prettyprint">/Users/username/Library/Application Support/Adobe/CameraRaw/Settings</code><br>
<code class="prettyprint">/Users/username/Library/Application Support/Adobe/CameraRaw/ImportedSettings</code></p>
<p><strong>In your system Library folder:</strong><br>
<code class="prettyprint">/Library/Application Support/Adobe/CameraRaw/Settings</code><br>
<code class="prettyprint">/Library/Application Support/Adobe/CameraRaw/ImportedSettings</code></p>
<p>You can also find the old <code class="prettyprint">.lrtemplate</code> files in <code class="prettyprint">Adobe/Lightroom/Develop Presets</code>, but those use a different format and should be automatically converted to XMP files by Lightroom, so you can ignore them if you stumble across them.</p>
<p>Once you find your presets, I suggest you move them all into the user-library <code class="prettyprint">Settings</code> folder in Finder, then group them into subfolders matching how you want them to show up in Lightroom, and only then go about editing the preset files themselves. Lightroom will ignore how you’ve organized the presets in Finder and instead just look at the XMP data, but having them organized can help you find your way as you accumulate hundreds (or thousands) of presets.</p>
<p>After making changes to presets, you will need to restart Lightroom to pick up the changes, so it’s best to make all of your changes in one swoop if you can. A text-editor like <a href="https://www.sublimetext.com/">Sublime Text</a> that allows you to do find-and-replace across an entire folder can come in handy for this.</p>
<h2 id="fin_2">Fin <a class="head_anchor" href="#fin_2">#</a>
</h2>
<p>Lightroom Classic may not have a built-in way to reorder preset groups and clusters, you can still customize the organization of your presets by editing their XMP files. By doing so, you can create a more streamlined workflow and make it easier to find the presets you use most often.</p>
<p>Let me know if you have any questions or feedback <a href="https://twitter.com/coreyward">on Twitter here</a>!</p>
<hr>
<p>*Provided you have marked 1+ presets as favorite<br>
**Provided you have at least one preset not in a cluster or group</p>
tag:coreyward.svbtle.com,2014:Post/case-sensitivity-with-localecompare-in-javascript2021-06-30T08:27:59-07:002021-06-30T08:27:59-07:00Case Sensitivity with `localeCompare` in JavaScript<p>I recently ran into a bug using code like the following: </p>
<pre><code class="prettyprint lang-js">someArray.sort((a, b) => a.name.localeCompare(b.name))
</code></pre>
<p>At a glance this seems fine—<code class="prettyprint">localCompare</code> enjoys <a href="https://caniuse.com/localecompare">fantastic browser support</a>, and as the name suggests, it isn’t English-centric. However, this is still the web we’re building for, and the familiar gotcha of inconsistent behavior amongst browsers is lurking here. This can lead to hard-to-debug inconsistencies and bugs.</p>
<p>Most browsers—including Chrome, Edge, and Firefox—and Node treat <code class="prettyprint">localeCompare</code> as case-insensitive by default, treating an uppercase “A” as the same as a lowercase “a”. Safari, however, does something different. </p>
<p>Safari uses a variant of case-sensitive sorting, but not in the usual ASCII order where “Z” sorts above “a”. Instead Safari sorts lowercase letters above their uppercase companions, but in alphabetical order otherwise, like “aAbBcC”.</p>
<p>In many cases this is fine—sorting based on the user’s locale is not only acceptable, it’s preferable. But that’s not always true, particularly in scenarios where data is expected to match the order from a server, such as when sorting properties before hashing for verification or as a checksum, or when hydrating server-side rendered React. In these cases, consistent behavior across environments is important and can be achieved by specifying a few additional options to <code class="prettyprint">localeCompare</code>:</p>
<pre><code class="prettyprint lang-js">a.name.localeCompare(b.name, "en", { sensitivity: "base" })
</code></pre>
<p>The <code class="prettyprint">sensitivity: "base"</code> option tells browsers to use the base character for comparison (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare">MDN</a>), effectively making the comparison case-insensitive. This results in consistent sorting behavior across Node and all common browsers (including IE11).</p>
tag:coreyward.svbtle.com,2014:Post/eslint-crashing-on-rest-operator-cannot-read-property-type-of-undefined2020-10-16T13:33:03-07:002020-10-16T13:33:03-07:00Fix ESLint crashing on rest operator: “Cannot read property 'type' of undefined”<p>This is a bit of an obscure one, but I tend to use the same dev tooling configuration (Prettier, ESLint, Babel, etc) for multiple projects so I’m likely to run into this again. </p>
<p>I encountered this in the middle of a project after a VSCode restart (to resolve other troublesome behavior). I was doing some refactoring and noticed that the magic that tells me what I’ve borked was conspicuously quiet, and a glance at the ESLint “output channel” in VSCode showed the titular error. </p>
<p>In my case, it wound up that I had been using an older version of ESLint (5.x) even as related dependencies (<code class="prettyprint">babel-eslint</code>, <code class="prettyprint">eslint-plugin-react</code>, and the ESLint extension for VSCode, among others) were updated. Fixing the issue was as easy as updating ESLint to the latest version (v7.11.0 at the moment of publication) and restarting VSCode again. </p>
tag:coreyward.svbtle.com,2014:Post/websters-unabridged-dictionary-1913-on-macos-catalina2020-06-15T13:16:06-07:002020-06-15T13:16:06-07:00Using Webster's 1913 Dictionary on macOS Catalina, Big Sur, or Monterey<p>I recently had the pleasure of discovering <a href="http://jsomers.net/blog/">James Somer’s blog</a> through <a href="https://www.instapaper.com/">Instapaper’s</a> weekly “top highlights” email. I found James’ <a href="http://jsomers.net/blog/dictionary">post about the dictionary</a> particularly compelling, and it seems I’m not alone.</p>
<p><a href="https://svbtleusercontent.com/9ynP342L7XvRDRA6Qt33Pv0xspap.png"><img src="https://svbtleusercontent.com/9ynP342L7XvRDRA6Qt33Pv0xspap_small.png" alt="Screen Shot 2021-12-27 at 2.59.00 PM.png"></a></p>
<p>Alas, the instructions provided for installing the 1913 edition of Webster’s Revised Unabridged Dictionary on Apple devices doesn’t work for macOS Catalina. Between the application and the source files being over 10 years old and Catalina changing things significantly for security, this isn’t surprising. After a few initial stabs at finding a Catalina-compatible version of the DictUnifier app (of which I tried two), I found an even better workaround.</p>
<h2 id="how-to-install-websters-revised-unabridged-di_2">How to install Webster’s Revised Unabridged Dictionary (1913 revision) on macOS Catalina <a class="head_anchor" href="#how-to-install-websters-revised-unabridged-di_2">#</a>
</h2>
<ol>
<li>Open the Dictionary app on your computer, and select File > Open Dictionaries Folder from the menu, or navigate manually to <code class="prettyprint">~/Library/Dictionaries</code>.</li>
<li>Head to the <a href="https://github.com/ponychicken/WebsterParser/releases">Github Releases page for WebsterParser</a> and download the file titled <code class="prettyprint">websters-1913.dictionary.zip</code><sup id="fnref1"><a href="#fn1">1</a></sup>.</li>
<li>Unzip the file, and move the resulting <code class="prettyprint">websters-1913.dictionary</code> file into the dictionaries folder that you opened in step 1 (it might look like a folder—move it over all the same). The dictionary file should sit adjacent to <code class="prettyprint">CoreDataUbiquitySupport</code> if you’ve done this correctly. </li>
<li>Restart the Dictionary app if it is open (important), then open the preferences (<code class="prettyprint">⌘,</code>). At the bottom of the list of dictionaries you should now see something like “websters-1913” in the list. Check the box, and optionally drag it up in the list to the order you’d like.</li>
</ol>
<p><a href="https://svbtleusercontent.com/8q5itJSebXQ3KH9zUarEgQ0xspap.png"><img src="https://svbtleusercontent.com/8q5itJSebXQ3KH9zUarEgQ0xspap_small.png" alt="Screen Shot 2021-12-27 at 2.57.21 PM.png"></a></p>
<p>That’s all there is to it, thanks to the work of other folks on the internet. No need to run an unverified app on your computer or install developer tools like glib just to use a 107 year old dictionary!</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>I also <a href="http://www.mediafire.com/?3xojow2hnxy">found this alternate version</a> to work, but it has some formatting issues that make it harder to read, and it is derived from an older file format that results in it being multiple times larger. If you have trouble with the first link, though, I wouldn’t hesitate to use this version. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
tag:coreyward.svbtle.com,2014:Post/fixing-installation-fsevents-install-errors-when-using-gatsby2020-02-28T09:46:47-08:002020-02-28T09:46:47-08:00Fixing installation fsevents install errors when using Gatsby<p>If you’re using Gatsby, you might have noticed a rather lengthy, unhelpful message in your terminal when you run <code class="prettyprint">yarn install</code> or <code class="prettyprint">yarn add</code> that relates to <code class="prettyprint">fsevents</code>. While Yarn will tell you this is okay because the dependency is “optional”, it’s really not.</p>
<p>Running Gatsby on macOS without <code class="prettyprint">fsevents</code> installed will result in much higher CPU usage (worse battery life) and slower refresh performance in your browser. So while technically it is optional, it’s really in your best interest to get <code class="prettyprint">fsevents</code> installed.</p>
<h2 id="so-whats-the-issue_2">So what’s the issue? <a class="head_anchor" href="#so-whats-the-issue_2">#</a>
</h2>
<p>Without getting too into the weeds, there are a few issues causing this:</p>
<ol>
<li>Versions of <code class="prettyprint">fsevents</code> below 1.2.9 aren’t compatible with Node v12+</li>
<li>The tool <code class="prettyprint">fsevents</code> uses to compile (<code class="prettyprint">node-gyp</code>) has issues with compiling on macOS Catalina</li>
<li>Compatible versions of <code class="prettyprint">fsevents</code> <u>above</u> 1.2.9 aren’t available as binaries</li>
</ol>
<h2 id="two-short-sweet-fixes_2">Two short, sweet fixes: <a class="head_anchor" href="#two-short-sweet-fixes_2">#</a>
</h2><h3 id="option-1-force-upgrade-code-classprettyprintc_3">Option 1: Force upgrade <code class="prettyprint">chokidar</code> <a class="head_anchor" href="#option-1-force-upgrade-code-classprettyprintc_3">#</a>
</h3>
<p>Gatsby doesn’t depend on <code class="prettyprint">fsevents</code> directly; it depends on a collection of other packages that depend on yet more packages. Follow the list down far enough and you find multiple dependencies on <code class="prettyprint">chokidar</code> resolving to the <code class="prettyprint">2.x.x</code> major version. This is the sole package dependent on the <code class="prettyprint">1.x.x</code> version of <code class="prettyprint">fsevents</code>, so if we upgrade it to a version with a more recent dependency, we can sidestep the <code class="prettyprint">fsevents</code> issue.</p>
<p>To do this, we can use Yarn resolutions. Place this in your <code class="prettyprint">package.json</code> and re-run <code class="prettyprint">yarn install</code>:</p>
<pre><code class="prettyprint">{
// ...
“resolutions”: {
“chokidar”: “^3.4.0”
}
}
</code></pre>
<p>This will result in all dependencies on <code class="prettyprint">chokidar</code> being resolved to <code class="prettyprint">3.4.0</code> or higher. This is unlikely to break your app—despite the major version changing, the most common interfaces are backwards compatible with the <code class="prettyprint">2.x</code> major releases.</p>
<h3 id="option-2-require-code-classprettyprintfsevent_3">Option 2: Require <code class="prettyprint">fsevents@1.2.9</code> <a class="head_anchor" href="#option-2-require-code-classprettyprintfsevent_3">#</a>
</h3>
<p>This uses the same resolutions feature as above, but there’s a caveat: if you run your code on platforms other than macOS that aren’t on a recent version of Yarn, they will fail. This is because earlier versions of Yarn considered resolutions to override the <u>optional</u> nature of a package. Since <code class="prettyprint">fsevents</code> only runs on macOS, trying to install it on Linux or Windows is a no-go.</p>
<p>Thankfully, you don’t need to actually commit the change to your <code class="prettyprint">package.json</code> because once you run <code class="prettyprint">yarn install</code> locally, the resolution gets committed to your <code class="prettyprint">yarn.lock</code> file. If someone on your team does a broad <code class="prettyprint">yarn upgrade</code> or takes the nuclear option of deleting <code class="prettyprint">yarn.lock</code> (which you should almost never do), this change might get blown away. Just be aware of it, or I’ll see you back here in a few months.</p>
<pre><code class="prettyprint">{
// ...
“resolutions”: {
“fsevents”: “1.2.9”
}
}
</code></pre>
<h2 id="parting-words_2">Parting Words <a class="head_anchor" href="#parting-words_2">#</a>
</h2>
<p>If neither of these work for you or if you have a better solution, please reach out and let me know. You can <a href="https://twitter.com/coreyward">follow me on Twitter here</a>; my DMs are open.</p>
tag:coreyward.svbtle.com,2014:Post/exporting-an-embeddable-react-component-from-a-gatsby-app2020-01-28T07:56:19-08:002020-01-28T07:56:19-08:00Exporting an embeddable React component from a Gatsby app using Webpack<p>A recent project called for the header and footer—implemented as <a href="https://reactjs.org/">React</a> components in <a href="https://gatsbyjs.org/">Gatsby</a>—to be embedded on third-party sites (think support documentation). Since both components rely on Gatsby’s <code class="prettyprint">Link</code> component as well as its GraphQL-backed Static Query (<code class="prettyprint">useStaticQuery</code> in this case), some creativity was required. </p>
<p>The solution: a separate Webpack configuration that would import the relevant file (e.g. the <code class="prettyprint">Header</code> component) into a lightweight wrapper that included an explicit call to <code class="prettyprint">ReactDOM.render</code>. To avoid a direct dependency on Gatsby, Webpack’s <code class="prettyprint">resolve</code> directive was used to direct imports from Gatsby to a shim. </p>
<p>Here’s the <code class="prettyprint">Header</code> component that needs to be embedded. Note the multiple imports from Gatsby:</p>
<pre><code class="prettyprint">// src/components/Header.jsx
import React from "react"
import { Link, useStaticQuery, graphql } from "gatsby"
const query = `…` // query fetching link data from CMS
const Header = () => {
const { navigation } = useStaticQuery(query)
return (
<div>Markup here making use of navigation data.</div>
)
}
</code></pre>
<p>This is the embedded widget’s <code class="prettyprint">index.js</code> where we import the <code class="prettyprint">Header</code> component and render it to a predetermined element on the page:</p>
<pre><code class="prettyprint">import React from "react"
import ReactDOM from "react-dom"
import Header from "components/Header"
const baseStyles = {
fontFamily: "Helvetica, Arial, sans-serif",
lineHeight: 1.4,
background: theme.white,
color: "#444",
fontSize: 16,
boxSizing: "border-box",
[["& *", "& *:before", "& *:after"]]: { boxSizing: "inherit" },
}
const HeaderEmbed = () => (
<div css={baseStyles}>
<Header />
</div>
)
const headerTarget = document.querySelector("[data-render='embed-header']")
if (headerTarget) {
ReactDOM.render(<HeaderEmbed />, headerTarget)
}
</code></pre>
<h2 id="getting-data-from-gatsby-into-the-embed-compo_2">Getting Data from Gatsby into the Embed Component <a class="head_anchor" href="#getting-data-from-gatsby-into-the-embed-compo_2">#</a>
</h2>
<p>Gatsby renders out the result of static queries to JSON files at build time. These files each live at <code class="prettyprint">public/page-data/sq/d/${queryHash}.json</code>, where the <code class="prettyprint">queryHash</code> is a <a href="https://github.com/gatsbyjs/gatsby/blob/610b5812a815f9ecff422e9087c851cd103c8e7e/packages/babel-plugin-remove-graphql-queries/src/murmur.js">murmurhash</a> of the query text itself <a href="https://stackoverflow.com/questions/58942573/access-json-chunk-exported-from-gatsby-static-query/58944342">using a seed of “abc”</a>. This same hash is used to retrieve the data client-side when <code class="prettyprint">useStaticQuery</code> is called (Gatsby replaces the query string itself with the hash).</p>
<pre><code class="prettyprint"># hash-query.js
const {
stripIgnoredCharacters,
} = require("graphql/utilities/stripIgnoredCharacters")
const { murmurhash } = require("babel-plugin-remove-graphql-queries/murmur")
const GATSBY_HASH_SEED = "abc"
// This must match Gatsby's internal hashing
// https://github.com/gatsbyjs/gatsby/blob/eafb8c6e346188f27cf1687d26544c582ed5952a/packages/babel-plugin-remove-graphql-queries/src/index.js#L144
function hashQuery(query) {
return murmurhash(stripIgnoredCharacters(query), GATSBY_HASH_SEED).toString()
}
module.exports = hashQuery
</code></pre>
<p>To dynamically import this JSON file into the embed, the query needs to be extracted from the <code class="prettyprint">src/components/Header.jsx</code> file much the same way Gatsby does. Since this usage is narrower, the solution can take a shortcut from the full static analysis that Gatsby performs and use a simple regular expression to match and extract the query. This is all performed in the <code class="prettyprint">extractGraphQLQuery</code> function in the below file. </p>
<p>With the query text determined, it’s just a matter of computing the <code class="prettyprint">queryHash</code>, reading the JSON file, and writing that data to a non-dynamic path that can be provided to the <code class="prettyprint">Header</code> component when it calls <code class="prettyprint">useStaticQuery</code>. This script (<code class="prettyprint">prepare-imports.js</code>) will be run as a prebuild step for our embed, which is built after Gatsby has finished building:</p>
<pre><code class="prettyprint">// embed/prepare-imports.js
const fs = require("fs")
const path = require("path")
const hashQuery = require("./hash-query")
// Quick and rough routine to extract a single
// graphql tagged template literal from a JS file
function extractGraphQLQuery(sourcePath) {
const data = fs.readFileSync(
path.resolve(__dirname, "..", sourcePath),
"utf8"
)
const match = /graphql`([^`]+)`/.exec(data)
if (match) {
return match[1]
}
}
// Copy a cached graphql query result from `public`
// to the embed plugin folder and rename to ensure
// stable imports.
function prepareQueryForImport(
name,
sourcePath,
targetFilename = `${name}.json`
) {
const query = extractGraphQLQuery(sourcePath)
const hash = hashQuery(query)
const source = path.resolve(
__dirname,
"..",
"public",
"page-data",
"sq",
"d",
`${hash}.json`
)
const dest = path.resolve(__dirname, "data", targetFilename)
fs.copyFileSync(source, dest)
return { name, hash, dest }
}
const queries = [
prepareQueryForImport("header", "src/components/Header.js"),
// Additional files can be added here as needed
]
// Export query data including hashes for reference in gatsby-shim.js
fs.writeFileSync(
path.resolve(__dirname, "data", "queries.json"),
JSON.stringify(queries)
)
</code></pre>
<p>This builds a file at <code class="prettyprint">embed/data/queries.json</code> that looks like this:</p>
<pre><code class="prettyprint">[{
"name": "header",
"hash": "1287877988",
"dest": "/absolute/path/to/example-project/embed/data/header.json"
}]
</code></pre>
<p>This <code class="prettyprint">queries.json</code> dictionary can then be used to substitute in the correct data in the shimmed Gatsby imports:</p>
<pre><code class="prettyprint">// embed/gatsby-shim.js
// This file acts as a shim for components of Gatsby that
// are in use in shared components but need to be used outside
// of Gatsby in the embed.
import React from "react"
import hashQuery from "./hash-query"
import queries from "./data/queries.json"
// Import each query data file (as referenced in queries.json)
import headerData from "./data/header.json"
// Build a reference map between the query name (e.g. header)
// and the imported data for it.
const queryData = {
header: headerData,
}
// Since Gatsby isn't being run, the query data actually
// gets passed to the `graphql` function, which allows us
// to compute its hash similar to how Gatsby does. The
// return value ends up being passed to `useStaticQuery`.
export const graphql = query => hashQuery(query.raw[0])
// We're using the query hash to determine which cached
// result data to return. This needs to match the return
// value from the native Gatsby function for compatibility.
export const useStaticQuery = queryHash => {
const query = queries.find(query => query.hash === queryHash)
if (!query) {
throw new Error(`No matching query for ${queryHash}`)
}
return queryData[query.name].data
}
export const Link = ({
activeClassName,
activeStyle,
getProps,
innerRef,
partiallyActive,
ref,
replace,
to,
...rest
}) =>
React.createElement("a", {
...rest,
href: to,
})
export default {}
</code></pre>
<p>With all of that established, Webpack can be configured with the following goals:</p>
<ol>
<li>Import the <code class="prettyprint">embed/index.js</code> embed wrapper and output at <code class="prettyprint">dist/widget.js</code>
</li>
<li>Resolve imports from <code class="prettyprint">gatsby</code> to the <code class="prettyprint">gatsby-shim.js</code> file</li>
<li>Make React and ReactDOM externals (third-party website should have them available as globals)</li>
<li>Use a Gatsby compatible Babel configuration</li>
</ol>
<p>This will vary somewhat for each application, but for reference this is the <code class="prettyprint">webpack.config.js</code> used in this solution:</p>
<pre><code class="prettyprint">const path = require("path")
const Dotenv = require("dotenv-webpack")
module.exports = {
mode: "production",
entry: "./embed/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "widget.js",
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
plugins: ["@babel/plugin-proposal-optional-chaining"],
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@emotion/babel-preset-css-prop",
],
},
},
},
{
test: /\.svg$/,
use: ["@svgr/webpack", "url-loader"],
},
],
},
resolve: {
modules: [path.resolve(__dirname, "../src"), "node_modules"],
extensions: [".js", ".jsx", ".json"],
alias: {
gatsby: path.resolve(__dirname, "gatsby-shim.js"),
},
},
externals: {
react: "React",
"react-dom": "ReactDOM",
},
plugins: [
new Dotenv({
systemvars: true,
}),
],
optimization: {
usedExports: true,
},
devtool: "nosources-source-map",
}
</code></pre>
<h2 id="tying-it-all-together_2">Tying it all together <a class="head_anchor" href="#tying-it-all-together_2">#</a>
</h2>
<p>The final piece of the puzzle is orchestrating these pieces at build time. To do that, the <code class="prettyprint">scripts</code> are configured as below in <code class="prettyprint">package.json</code>:</p>
<pre><code class="prettyprint">{
"scripts": {
"prebuild:embed": "node embed/prepare-imports.js",
"build:embed": "webpack --config embed/webpack.config.js",
"postbuild:embed": "cp -R embed/dist/widget.* ./public/embed/",
"build": "npm-run-all build:gatsby build:embed",
"build:gatsby": "gatsby build",
}
}
</code></pre>
<p>Since Yarn will automatically run <code class="prettyprint">prebuild:</code> and <code class="prettyprint">postbuild:</code> prefixed scripts, this recipe results in the following:</p>
<ol>
<li>
<code class="prettyprint">gatsby build</code> is executed like normal</li>
<li>
<code class="prettyprint">prebuild:embed</code> is run, executing the <code class="prettyprint">prepare-imports</code> script that makes the GraphQL query data available to the embed at a consistent locaiton</li>
<li>
<code class="prettyprint">build:embed</code> invokes the isolated Webpack bundling of the embed itself</li>
<li>
<code class="prettyprint">postbuild:embed</code> copies the output from the embed build into the Gatsby <code class="prettyprint">public</code> folder for consumption by other applications</li>
</ol>
<p>Here’s a simple example of how this embed gets used by downstream applications:</p>
<pre><code class="prettyprint"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Header Embed Widget</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<!-- Header will be rendered inside this container -->
<div data-render="embed-header"></div>
<!--
React and ReactDOM are dependencies; these tags will ensure
they're available on the page, but they can be removed if compatible
versions are already available.
-->
<script
src="https://unpkg.com/react@16/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"
crossorigin
></script>
<!-- This loads the embed code with all other dependencies and content -->
<script
src="/embed/widget.js"
defer
crossorigin
></script>
</body>
</html>
</code></pre>
<h2 id="conclusion_2">Conclusion <a class="head_anchor" href="#conclusion_2">#</a>
</h2>
<p>It’s not as cut and dry as a lot of the tasks React and Gatsby make easy for developers, but this solution has made it possible to reuse a fully featured Gatsby-dependent React component outside of the application without requiring any additional tooling for those implementing it.</p>
tag:coreyward.svbtle.com,2014:Post/fixing-homebrew-postgres-installation-on-macos-catalina2020-01-15T14:09:14-08:002020-01-15T14:09:14-08:00Fixing Homebrew Postgres Installation on macOS Catalina<p>I recently upgraded to macOS Catalina and needed to reinstall PostgreSQL via Homebrew. The usual process is simple enough: <code class="prettyprint">brew install postgresql</code> does the bulk of the work, and then running <code class="prettyprint">brew services start postgres</code> would <em>normally</em> result in Homebrew’s service manager loading the appropriate launch agent for you. </p>
<p>Unfortunately Catalina’s various file access protection schemes seem to get in the way of this. I saw a handful of different errors when looking for a solution, but the specific error I was receiving was a little different:</p>
<blockquote class="short">
<p>Permission denied @ rb_sysopen - /Users/corey/Library/LaunchAgents/homebrew.mxcl.postgresql.plist</p>
</blockquote>
<p>I assumed that the plist file had merely been given incorrect permissions, but the file wasn’t even there. Homebrew wasn’t able to create the file because the directory, <code class="prettyprint">~/Library/LaunchAgents</code> has its permissions set to <code class="prettyprint">555</code> (i.e. <code class="prettyprint">r-xr-xr-x</code>), so despite being owned by my login user, I wasn’t able to write to the folder directly. </p>
<p>The solution is to change the directory permissions to allow writing by the owner, then copy the missing plist file over manually from the postgres install:</p>
<pre><code class="prettyprint">% chmod 755 ~/Library/LaunchAgents
% cp /usr/local/opt/postgresql/homebrew.mxcl.postgresql.plist ~/Library/LaunchAgents/
</code></pre>
<p>From there, re-running <code class="prettyprint">brew services start postgresql</code> should operate correctly.</p>
tag:coreyward.svbtle.com,2014:Post/usetypedtext-react-hook2019-10-10T15:50:25-07:002019-10-10T15:50:25-07:00useTypedText React Hook<p>I needed to add a text-being-typed effect to a complex animation recently, and instead of cluttering up the more nuanced parts of the animation with the typing animation, I wrote a quick React Hook that takes the final string and returns the currently typed text. This seems like the sort of thing I’ll use again, and hopefully it’ll help someone else as well:</p>
<pre><code class="prettyprint lang-js">import { useState, useEffect } from "react"
export default (finalText, delay = 250) => {
const [state, setState] = useState(finalText.slice(0, 1))
useEffect(() => {
const deferredActions = finalText.split("").map((char, index) => {
if (index) {
return window.setTimeout(() => {
setState(finalText.slice(0, index + 1))
}, delay * index)
}
})
return () => {
deferredActions.forEach(timeout => {
timeout && window.clearTimeout(timeout)
})
}
}, [finalText, delay])
return state
}
</code></pre>
<p>That’s all!</p>
tag:coreyward.svbtle.com,2014:Post/how-to-fix-react-devtools-encountered-an-error-rangeerror-invalid-array-length2019-09-26T13:54:59-07:002019-09-26T13:54:59-07:00How to fix `React DevTools encountered an error: RangeError: Invalid array length`<p>I recently found this rather obscure message in my dev tools console when working on a React + Gatsby based website. It took a bit of digging to figure out where it was coming from since the stack trace was all React internals. </p>
<p>Turns out I had made a simple mistake some weeks prior when <a href="https://reactjs.org/docs/forwarding-refs.html#displaying-a-custom-name-in-devtools">setting the display name on a component that was being wrapped with <code class="prettyprint">forwardRef</code></a>: I had passed in the component itself as the value of the <code class="prettyprint">displayName</code> property instead of a string. </p>
<pre><code class="prettyprint lang-diff"> const Something = React.forwardRef((props, ref) => <div>…</div>)
-Something.displayName = Something
+Something.displayName = "Something"
</code></pre>
<p>Turns out React DevTools isn’t so fond of that.</p>
<p>Hopefully this will save someone (probably me) time in the future!</p>
tag:coreyward.svbtle.com,2014:Post/consistent-date-formatting-in-ruby-on-rails-52018-11-30T09:49:09-08:002018-11-30T09:49:09-08:00Consistent Date Formatting in Ruby on Rails 5+<p>If you’ve ever dealt with dates in Rails, particularly accepting dates from user input and then displaying them, you’re familiar with two clunky experiences: </p>
<ol>
<li>The awful default month, day, year, hour, minute, second dropdowns</li>
<li>The complete mess of a date that you get when you force that field to be represented as a text input in a form.</li>
</ol>
<blockquote class="short">
<p><strong>In a hurry?</strong> Scroll on down to the bottom for the copy-paste-ready code snippet to make everything better like magic.</p>
</blockquote><h3 id="part-1-getting-code-classprettyprintdatecode_3">Part 1: Getting <code class="prettyprint">Date</code> to format itself as desired <a class="head_anchor" href="#part-1-getting-code-classprettyprintdatecode_3">#</a>
</h3>
<p>There are a bunch of <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/conversions.rb">core extensions to Date in ActiveSupport</a>, but none of them create a new date class. If you’re working with <code class="prettyprint">Date</code>, which is what Rails returns when you have a <code class="prettyprint">date</code> column in your database, the method converting that date into a string is <code class="prettyprint">Date#to_formatted_s</code>. The <code class="prettyprint">ActiveSupport</code> extensions add this method, and they alias the <code class="prettyprint">to_s</code> method to it, so when you call <code class="prettyprint">Date#to_s</code> (probably implicitly), you get <code class="prettyprint">Date.to_formatted_s</code>. </p>
<p>There are two ways to change the format used by <code class="prettyprint">to_formatted_s</code>:</p>
<ol>
<li>Pass the name of the format you would like used (this arg will be used to lookup the format in <code class="prettyprint">Date::DATE_FORMATS</code>).</li>
<li>
<p>Change the format that <code class="prettyprint">:default</code> maps to. Since <code class="prettyprint">to_formatted_s</code> sets a default value of <code class="prettyprint">:default</code>, when you call <code class="prettyprint">Date#to_s</code> without passing an argument you get format specified by <code class="prettyprint">Date::DATE_FORMATS[:default]</code>. You can override the default on an application-wide basis like this:</p>
<pre><code class="prettyprint">Date::DATE_FORMATS[:default] = "%m/%d/%Y"
</code></pre>
</li>
</ol>
<p>I have this set in an initializer like <code class="prettyprint">config/initializers/date_formats.rb</code>. It’s crude and doesn’t support changing with your localizations, but gets <code class="prettyprint">Date#to_s</code> to return the desired format without any monkey patching or explicitly converting your dates to strings and passing in an argument. </p>
<h2 id="part-2-getting-userinputted-dates-converted-t_2">Part 2: Getting user-inputted dates converted to Date objects <a class="head_anchor" href="#part-2-getting-userinputted-dates-converted-t_2">#</a>
</h2>
<p>By default, your <code class="prettyprint">ActiveRecord</code> instance will <a href="https://github.com/rails/rails/blob/master/activemodel/lib/active_model/type/date.rb">cast date columns</a> using good ol’ ISO 8601 that looks like this: <code class="prettyprint">yyyy-mm-dd</code>.</p>
<p>If it doesn’t match that format exactly (e.g., it’s slash delimited), it falls back to using Ruby’s <a href="https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/Date.html#method-c-_parse">Date._parse</a>, which is a little more lenient, but still expects to see days, months, then years: <code class="prettyprint">dd/mm/yyyy</code>. </p>
<p>To get Rails to parse the dates in the format you’re collecting them, you just need to replace the <code class="prettyprint">cast_value</code> method with one of your own. You <em>can</em> monkeypatch <code class="prettyprint">ActiveRecord::Types::Date</code>, but it’s not much harder to roll your own type inheriting from it. </p>
<p>I like using <a href="https://github.com/mojombo/chronic">Chronic</a> to parse date strings from user input because it puts up with more shit, like “Tomorrow”, or “Jan 1 99”, or even bonkers input like “5/6-99” (May 6, 1999). Since Chronic returns an instance of <code class="prettyprint">Time</code> and we’re overriding the supplied <code class="prettyprint">cast_value</code> method, we need to call <code class="prettyprint">to_date</code> on it.</p>
<pre><code class="prettyprint">class EasyDate < ActiveRecord::Type::Date
def cast_value(value)
default = super
parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
parsed || default
end
end
</code></pre>
<p>Now we just need to tell ActiveRecord to use <code class="prettyprint">EasyDate</code> instead of <code class="prettyprint">ActiveRecord::Type::Date</code>:</p>
<pre><code class="prettyprint">class Pony < ApplicationRecord
attribute :birth_date, EasyDate.new
end
</code></pre>
<p>That works, but if you’re like me, you want to take on a bunch more work right now so you can save 3 seconds in the future. What if we could tell ActiveRecord to always use EasyDate for columns that are just dates? We can. </p>
<h2 id="part-3-make-rails-handle-this-automatically_2">Part 3: Make Rails handle this automatically <a class="head_anchor" href="#part-3-make-rails-handle-this-automatically_2">#</a>
</h2>
<p>ActiveRecord gives you all sorts of information about your model that it uses to handle all of the “magic” behind the scenes. One of the methods it gives you us <code class="prettyprint">attribute_types</code>. If you run that in your console, you get back vomit — it’s kind of scary, and you just go “oh nevermind”. If you dig into that vomit like some sort of weirdo, though, it’s just a hash that looks like this: </p>
<pre><code class="prettyprint">{ column_name: instance_of_a_type_object, ... }
</code></pre>
<p>Not scary stuff, but since we’re starting to poke at internals, the inspected output of these <code class="prettyprint">instance_of_a_type_object</code> can end up looking obtuse and “closed”. Like this: </p>
<pre><code class="prettyprint">ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Money
</code></pre>
<p>Thankfully, we really only care about one: <code class="prettyprint">ActiveRecord::Types::Date</code>, so we can narrow it down:</p>
<pre><code class="prettyprint">date_attributes =
attribute_types.select { |_, type| ActiveRecord::Type::Date === type }
</code></pre>
<p>That gives us a list of the attributes that we want to upcycle to use <code class="prettyprint">EasyDate</code>. Now we just need to tell ActiveRecord to use EasyDate on those attributes like we did above: </p>
<pre><code class="prettyprint">date_attributes.each do |attr_name, _type|
attribute attr_name, EasyDate.new
end
</code></pre>
<p>And that basically does it, but we need to glue it all together. If you’re using <code class="prettyprint">ApplicationRecord</code> you can drop this right into it and be off to the races:</p>
<pre><code class="prettyprint">def inherited(klass)
super
klass.attribute_types
.select { |_, type| ActiveRecord::Type::Date === type }
.each do |name, _type|
klass.attribute name, EasyDate.new
end
end
</code></pre>
<p>If you don’t want to “pollute” <code class="prettyprint">ApplicationRecord</code>, you can do like I did and create a module and extend <code class="prettyprint">ApplicationRecord</code> with it. If you’re <em>not</em> using <code class="prettyprint">ApplicationRecord</code>, you’ll need to create the module and extend <code class="prettyprint">ActiveRecord::Base</code>. </p>
<pre><code class="prettyprint">module FixDateFormatting
# the above `inherited` method here
end
# Either this...
class ApplicationRecord < ActiveRecord::Base
extend FixDateFormatting
end
# ...or this:
ActiveRecord::Base.extend(FixDateFormatting)
</code></pre>
<h1 id="tldrwhat-to-copy-paste_1">TL;DR — What to copy paste <a class="head_anchor" href="#tldrwhat-to-copy-paste_1">#</a>
</h1>
<p>If you aren’t concerned with the <em>how</em> and just want this to work, you can: </p>
<ol>
<li>Add <code class="prettyprint">gem "chronic"</code> to your Gemfile and <code class="prettyprint">bundle install</code>
</li>
<li>Make sure your models inherit from <code class="prettyprint">ApplicationRecord</code>
</li>
<li>Copy the code below a file at <code class="prettyprint">config/initializers/date_formatting.rb</code>
</li>
<li>Restart</li>
<li>Everything should now Just Work™</li>
</ol>
<p>Here’s the code to copy:</p>
<pre><code class="prettyprint">Date::DATE_FORMATS[:default] = "%m/%d/%Y"
module FixDateFormatting
class EasyDate < ActiveRecord::Type::Date
def cast_value(value)
default = super
parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
parsed || default
end
end
def inherited(subclass)
super
date_attributes = subclass.attribute_types.select { |_, type| ActiveRecord::Type::Date === type }
date_attributes.each do |name, _type|
subclass.attribute name, EasyDate.new
end
end
end
Rails.application.config.to_prepare do
ApplicationRecord.extend(FixDateFormatting)
end
</code></pre>