<?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" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[fs.blog]]></title><description><![CDATA[thoughts about the web]]></description><link>https://blog.fschoenfeldt.de/</link><image><url>https://blog.fschoenfeldt.de/favicon.png</url><title>fs.blog</title><link>https://blog.fschoenfeldt.de/</link></image><generator>Ghost 5.42</generator><lastBuildDate>Tue, 28 Apr 2026 01:52:21 GMT</lastBuildDate><atom:link href="https://blog.fschoenfeldt.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Space Haven Manager]]></title><description><![CDATA[Space Haven Manager: a lightweight webapp to manager ressources and trading prices for the game Space Haven.]]></description><link>https://blog.fschoenfeldt.de/space-haven-manager/</link><guid isPermaLink="false">64620b6b2764cc2c8f96cc97</guid><category><![CDATA[development]]></category><category><![CDATA[gaming]]></category><category><![CDATA[app]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Mon, 15 May 2023 16:00:21 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2023/05/Bildschirmfoto-2023-05-15-um-12.38.48.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2023/05/Bildschirmfoto-2023-05-15-um-12.38.48.png" alt="Space Haven Manager"><p>Space Haven is a RimWorld inspired spaceship colony sim, you can find it <a href="https://store.steampowered.com/app/979110/Space_Haven/?ref=blog.fschoenfeldt.de">on steam here</a>. While the main goal is to explore the space and gather ressources, trading is also a valuable feature in the game to progress the game.</p><hr><p>With Space Haven Manager, I built a small webapp that allows you to manage trading prices. It turns out to be helpful to estimate costs for a new spaceship, for example, or to just get a feeling how good a deal from a trader actually is.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2023/05/spacehaven_promo.png" class="kg-image" alt="Space Haven Manager" loading="lazy" width="2000" height="1307" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2023/05/spacehaven_promo.png 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2023/05/spacehaven_promo.png 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2023/05/spacehaven_promo.png 1600w, https://blog.fschoenfeldt.de/content/images/size/w2400/2023/05/spacehaven_promo.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>the interface of Space Haven Manager</figcaption></figure><p>From a technical perspective, <a href="https://alpinejs.dev/?ref=blog.fschoenfeldt.de">AlpineJS</a> is used as a thin layer around events and state management. For persistance, I&apos;m using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage?ref=blog.fschoenfeldt.de">Local Storage API</a>. Testing is done via <a href="https://playwright.dev/?ref=blog.fschoenfeldt.de">playwright</a> and some automatic accessibility checks are done via <a href="https://github.com/dequelabs/axe-core?ref=blog.fschoenfeldt.de">axe-core</a>. The tests are run on every push to the repository via <a href="https://github.com/fschoenfeldt/website/blob/main/.github/workflows/playwright.yml?ref=blog.fschoenfeldt.de">GitHub Actions</a>, this ensures consistent quality.</p><p>You can try it out and find the manager here: <a href="https://fschoenfeldt.de/project_manager/?ref=blog.fschoenfeldt.de">https://fschoenfeldt.de/project_manager/</a></p>]]></content:encoded></item><item><title><![CDATA[Writing an image compression app with NodeJS, imagemin & pngquant (Part III)]]></title><description><![CDATA[Part III into writing an image compression app with NodeJS, imagemin & pngquant.]]></description><link>https://blog.fschoenfeldt.de/writing-an-image-compression-app-with-nodejs-imagemin-pngquant-part-iii/</link><guid isPermaLink="false">6432af39251b5639a9276319</guid><category><![CDATA[node]]></category><category><![CDATA[nodejs]]></category><category><![CDATA[ghost]]></category><category><![CDATA[image]]></category><category><![CDATA[compression]]></category><category><![CDATA[app]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Mon, 25 Jan 2021 10:14:30 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2021/01/tim-mossholder-GmvH5v9l3K4-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2021/01/tim-mossholder-GmvH5v9l3K4-unsplash.jpg" alt="Writing an image compression app with NodeJS, imagemin &amp; pngquant (Part III)"><p>Hey guys, welcome back to Part III of my mini-app-cli-ish image compression series! If you haven&apos;t read part Part II, <a href="https://blog.fschoenfeldt.de/writing-an-image-compression-app-for-ghost-with-nodejs-imagemin-pngquant-part-ii/">you can do so here</a>. By the way, <a href="https://ghost.org/changelog/image-galleries/?ref=blog.fschoenfeldt.de">ghost added image compression to their core</a> so your images will be compressed automatically by default!</p><p>Anyway, I&apos;ll continue this tutorial series because I&apos;m starting a new Fotohaecker Project from scratch with <a href="https://elixir-lang.org/?ref=blog.fschoenfeldt.de">Elixir</a> &amp; <a href="https://www.phoenixframework.org/?ref=blog.fschoenfeldt.de">Phoenix Framework</a> and I need a compression CLI to process users photo uploads.</p><h2 id="updating-our-dependencies">Updating our dependencies</h2><p>Because my last post is a bit old, we first want to check for dependency updates. For this, I find two libraries very useful:<a href="https://www.npmjs.com/package/npm-check-updates?ref=blog.fschoenfeldt.de"> npm-check-updates</a> and &#xA0;<a href="https://www.npmjs.com/package/npm-check?ref=blog.fschoenfeldt.de">npm-check</a>. The first one is more popular but the second has a graphical User-Interface - so you decide!</p><p>npm-check-updates: <code>ncu</code> </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-11.51.54.png" class="kg-image" alt="Writing an image compression app with NodeJS, imagemin &amp; pngquant (Part III)" loading="lazy" width="2000" height="1212" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2021/01/Bildschirmfoto-2021-01-23-um-11.51.54.png 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2021/01/Bildschirmfoto-2021-01-23-um-11.51.54.png 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2021/01/Bildschirmfoto-2021-01-23-um-11.51.54.png 1600w, https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-11.51.54.png 2092w" sizes="(min-width: 1200px) 1200px"><figcaption>with ncu, you can preview the dependencies that are out of date. After that, you can run `ncu -u` to update all dependencies.</figcaption></figure><p>npm-check: <code>npm-check</code> <em>(note, I added the <code>-u</code> flag to skip unused dependencies warnings, as we haven&apos;t actually used mozjpeg etc., yet.)</em></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-11.54.34.png" class="kg-image" alt="Writing an image compression app with NodeJS, imagemin &amp; pngquant (Part III)" loading="lazy" width="2000" height="1118" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2021/01/Bildschirmfoto-2021-01-23-um-11.54.34.png 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2021/01/Bildschirmfoto-2021-01-23-um-11.54.34.png 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2021/01/Bildschirmfoto-2021-01-23-um-11.54.34.png 1600w, https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-11.54.34.png 2268w" sizes="(min-width: 1200px) 1200px"><figcaption>With npm-check, dependencies are grouped by the changes of the outdated dependencies. You can select those dependencies to update with Spacebar and install them via Enter.</figcaption></figure><h2 id="into-compression">Into compression</h2><h3 id="compression-model">Compression model</h3><p>Now, lets get started with the actual compression of our images. It&apos;s triggered by the CLI command <code>node run.js foo.jpg --compress [compression-level]</code> and evaluated in our code <code>src/cli-controller.js:18~25</code>.</p><p>We can start adding the <code>imagemin</code> calls right in the controller but I&apos;d much rather prefer a model for this. First, let&apos;s download a sample image to compress and place it into &#xA0;<code>/images/test.jpg</code>. Also, add a <code>./src/compression-model.js</code> : </p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">&quot;use strict&quot;;

const imagemin = require(&quot;imagemin&quot;);
const imageminMozjpeg = require(&quot;imagemin-mozjpeg&quot;);
const fs = require(&quot;fs&quot;);

module.exports.compress = async (options) =&gt; {
  const { sourcePath } = options;
  // Check if file exists
  fs.promises
    .access(sourcePath)
    .then(compressImage(options))
    .catch((err) =&gt; console.error(err));
};

const compressImage = async ({ sourcePath, destinationPath }) =&gt; {
  const compressed_image = await imagemin([sourcePath], {
    destination: destinationPath,
    plugins: [imageminMozjpeg()],
  });

  console.debug(compressed_image);
};
</code></pre><figcaption>basic <code>./src/compression-model.js</code> that throws an error if the file doesn&apos;t exist.</figcaption></figure><p>Note the following things:</p><ul><li>with <code><a href="https://nodejs.org/api/fs.html?ref=blog.fschoenfeldt.de#fs_fspromises_access_path_mode">fs.promises.access</a></code>, we check if the <code>sourcePath</code>-File exists. <em>Technically, we&apos;re introducing race-conditions with that but that&apos;s fine for me.</em></li><li>We&apos;re passing <code>options</code> to our &#xA0;<code>compressImage</code> function but only use <code>sourcePath</code> &amp; <code>destinationPath</code> for now.</li><li><code>imagemin</code> is asynchronous so our <code>compressImage</code> function needs to be <code>async</code> aswell to call <code>await imagemin</code>. We&apos;ll come to that later.</li><li>Finally, we&apos;re printing out the result of imagemin with <code>console.debug()</code>.</li></ul><h3 id="updating-our-cli-controller">updating our CLI controller</h3><p>Then, we want to expand our CLI with a parameter that determines the destination to save the compressed image as. Our CLI will look like this then: <code>node run.js [sourceFile] [desinationFile] [--compress] [compression-level]</code>. With this, we&apos;ll also refactor our code a bit so missing parameters are catched earlier:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">&quot;use strict&quot;;

const compressionModel = require(&quot;./compression-model.js&quot;);

// function that decides what do do
// remember the syntax: `node run.js [sourceFile] [desinationFile] [--compress/--resize] [compression-level]`
module.exports.pass = async (args) =&gt; {
  const errors = await checkArgsForErrors(args);

  if (errors) {
    console.error(errors);
  } else {
    const [sourcePath, destinationPath, command, ...options] = args;
    // only run this if args where provided
    switch (command) {
      case &quot;--compress&quot;: // fall-through to case &quot;compress&quot;
      case &quot;compress&quot;:
        compressionModel.compress({
          sourcePath: sourcePath,
          destinationPath: destinationPath,
          options: options,
        });
        break;
      case &quot;--resize&quot;: // fall-through to case &quot;resize&quot;
      case &quot;resize&quot;:
        console.log(`** Insert resizing here, using options ${options} **`);
        break;
      default:
        console.log(
          `${command} is not a valid action, either use --resize or --compress.`
        );
    }
  }
};

const checkArgsForErrors = async (args) =&gt; {
  let errors = &quot;&quot;;
  if (args.length &lt; 1) {
    // warning the user that he did not provide a source file. !TODO we might want to print out our apps syntax here.
    errors += `You did not provide a source file. `;
  }
  if (args.length &lt; 2) {
    // warning the user that he did not provide a destination file. !TODO we might want to print out our apps syntax here.
    errors += `You did not provide a destination file. `;
  }
  if (args.length &lt; 3) {
    // warning the user that he did not provide an action
    errors += ` You didn&apos;t provide an action, such as --compress or --resize. `;
  }

  return errors;
};
</code></pre><figcaption><code>./src/cli-controller.js</code> that now can get more parameters</figcaption></figure><p>Note the following things:</p><ul><li>As <code>imagemin</code> is asynchronous <em>(That means that we have to wait for imagemin to finish processing our image before we can actually say that it succeeded and furthermore process it)</em>, we also needed to change all functions upstream to be <code>async</code>. <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Concepts?ref=blog.fschoenfeldt.de#asynchronous">You can find a nice article about asynchronous concepts on MDN</a></li><li>I added a <code>checkArgsForErrors</code> function that adds up errors that might occur. When we start resizing, we can also move the check if the source file exists here</li><li>With <code>const [sourcePath, destinationPath, command, ...options] = args;</code>, <code>args</code> are destructured into more meaningful variables that we can use in the switch.</li></ul><p>Make sure to also update the controller function call in <code>run.js:9~12</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">// function to handle user input, args is the array containing the arguments
const handleCliInput = async (args) =&gt; {
  await controller.pass(args);
};</code></pre><figcaption>`run.js:9~12`</figcaption></figure><h2 id="run-it-">Run it!</h2><p>You can now run our CLI with <code>node run.js ./images/test.jpg ./images/compressed/test.jpg --compress</code></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-13.49.50.png" class="kg-image" alt="Writing an image compression app with NodeJS, imagemin &amp; pngquant (Part III)" loading="lazy" width="2000" height="633" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2021/01/Bildschirmfoto-2021-01-23-um-13.49.50.png 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2021/01/Bildschirmfoto-2021-01-23-um-13.49.50.png 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2021/01/Bildschirmfoto-2021-01-23-um-13.49.50.png 1600w, https://blog.fschoenfeldt.de/content/images/size/w2400/2021/01/Bildschirmfoto-2021-01-23-um-13.49.50.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>It works!</figcaption></figure><p>If the source file can&apos;t be found, our CLI will print out an error just like this:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2021/01/Bildschirmfoto-2021-01-23-um-13.50.48.png" class="kg-image" alt="Writing an image compression app with NodeJS, imagemin &amp; pngquant (Part III)" loading="lazy" width="2000" height="633" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2021/01/Bildschirmfoto-2021-01-23-um-13.50.48.png 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2021/01/Bildschirmfoto-2021-01-23-um-13.50.48.png 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2021/01/Bildschirmfoto-2021-01-23-um-13.50.48.png 1600w, https://blog.fschoenfeldt.de/content/images/size/w2400/2021/01/Bildschirmfoto-2021-01-23-um-13.50.48.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>If the file doesn&apos;t exist, we print an error!</figcaption></figure><h2 id="wrapping-it-up">Wrapping it up</h2><p>Today, we refactored our <code>cli-controller</code> and added an asynchronous <code>compression-model</code> that takes different options to pass it to <code>imagemin</code>.</p><p><strong>If you want to clone the project, you can: <a href="https://github.com/fschoenfeldt/ghost-compression?ref=blog.fschoenfeldt.de">https://github.com/fschoenfeldt/ghost-compression</a></strong></p><p>Thanks for checking out my guide series for writing an image compression app with NodeJS. If you have any questions, feel free to contact me via <a href="https://fschoenfeldt.de/?ref=blog.fschoenfeldt.de">https://fschoenfeldt.de</a> - Part IV is coming, very soon! x</p><p><em>Photo by <a href="https://unsplash.com/@timmossholder?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Tim Mossholder</a> on <a href="https://unsplash.com/s/photos/gears?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></p>]]></content:encoded></item><item><title><![CDATA[Fotohäcker Update: Account Settings]]></title><description><![CDATA[talking about fotohaecker and recent changes]]></description><link>https://blog.fschoenfeldt.de/fotohacker-update-account-settings/</link><guid isPermaLink="false">6432af39251b5639a927631c</guid><category><![CDATA[nodejs]]></category><category><![CDATA[Portfolio]]></category><category><![CDATA[changelog]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Thu, 09 Apr 2020 17:03:00 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2020/04/fotohaecker-accountsettings.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2020/04/fotohaecker-accountsettings.jpg" alt="Fotoh&#xE4;cker Update: Account Settings"><p>I&apos;m still working on <a href="https://www.fschoenfeldt.de/fotohaecker?ref=blog.fschoenfeldt.de">Fotoh&#xE4;cker</a> and it just had an update! You can now customize and edit your account:</p><ul><li>Change you profile picture &amp; profile cover</li><li>Change your email address &amp; password</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2020/04/fotohaecker-github.png" class="kg-image" alt="Fotoh&#xE4;cker Update: Account Settings" loading="lazy"><figcaption>lots of commits and changes under the hood, the source will be public, soon!</figcaption></figure><p>Also it had a lot of changes under the hood, now having almost <strong>400 commits</strong>:</p><ul><li>Automated testing with jest &amp; playwright</li><li>ESM modules</li><li>Big refactoring of the code</li></ul><p>Stay tuned for more features, and the source code, coming soon!</p><p>Cheers x fred</p>]]></content:encoded></item><item><title><![CDATA[My best performing photos on Unsplash]]></title><description><![CDATA[Have a look on my best performing photos on @unsplash :-)]]></description><link>https://blog.fschoenfeldt.de/my-best-performing-photos-on-unsplash/</link><guid isPermaLink="false">6432af39251b5639a927631b</guid><category><![CDATA[Design]]></category><category><![CDATA[photography]]></category><category><![CDATA[licensefree]]></category><category><![CDATA[cc0]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Sat, 22 Feb 2020 12:47:06 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2020/02/frederik-schonfeldt-vSnK92lYckw-unsplash--1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2020/02/frederik-schonfeldt-vSnK92lYckw-unsplash--1-.jpg" alt="My best performing photos on Unsplash"><p>If you&apos;re interested in photography, or if you&apos;re a (web-)designer looking for photography/image materials that are <a href="https://unsplash.com/license?ref=blog.fschoenfeldt.de">License Free (CC0)</a>, you surely came across unsplash.com.</p><p>I&apos;m having a profile on unsplash for some years now and I&apos;m contributing photos once a month or so. And they&apos;re very good performing! So if you&apos;re interested, <a href="https://unsplash.com/@fschoenfeldt?ref=blog.fschoenfeldt.de">check out my profile!</a></p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2020/02/unsplash_performance.png" class="kg-image" alt="My best performing photos on Unsplash" loading="lazy"><figcaption>My unsplash stats for my most viewed photos, see them here:&#xA0;</figcaption></figure><p>Unsplash is such an awesome website! <a href="https://unsplash.com/history?ref=blog.fschoenfeldt.de">It started as a small community, evolving over years</a> to one of the best ressources for license free photography. The community is open-minded and dedicated to bringing the best photos to everyone. They even have an own Slack channel for critique and suggestions.</p><p>If you want to see all of my most viewed (and best performing photos), have a look here: <a href="https://unsplash.com/collections/9586076/most-viewed?ref=blog.fschoenfeldt.de">https://unsplash.com/collections/9586076/most-viewed</a></p><p>As I said, use them for anything you like, <a href="https://unsplash.com/license?ref=blog.fschoenfeldt.de">they&apos;re in the CC0 License.</a></p>]]></content:encoded></item><item><title><![CDATA[3D Graphic Experiments from 2015-2017]]></title><description><![CDATA[Some graphical experiments I did back in the days]]></description><link>https://blog.fschoenfeldt.de/3d-experiments/</link><guid isPermaLink="false">6432af39251b5639a927631a</guid><category><![CDATA[3D]]></category><category><![CDATA[Design]]></category><category><![CDATA[Graphicdesign]]></category><category><![CDATA[Cinema4D]]></category><category><![CDATA[Graphics]]></category><category><![CDATA[Portfolio]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Sat, 11 Jan 2020 13:49:18 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2020/01/proj11_2.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2020/01/proj11_2.jpg" alt="3D Graphic Experiments from 2015-2017"><p>Happy new year guys! :) For today, I want to show you something different from my portfolio.</p><p>Even if I&apos;m mainly a web developer now, back in the days, I used to do a lot of graphical work. From Photoshop to Illustrator and even a little bit Cinema 4D (now I really much prefer Blender).</p><p>Below I added some experiments from back in the days :)</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/Moonrise.jpg" width="800" height="600" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/Moonrise.jpg 600w, https://blog.fschoenfeldt.de/content/images/2020/01/Moonrise.jpg 800w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/Ohne-Titel-2.jpg" width="2000" height="1125" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/Ohne-Titel-2.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/Ohne-Titel-2.jpg 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2020/01/Ohne-Titel-2.jpg 1600w, https://blog.fschoenfeldt.de/content/images/size/w2400/2020/01/Ohne-Titel-2.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/Ohne-Titel-2-1.jpg" width="1214" height="1151" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/Ohne-Titel-2-1.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/Ohne-Titel-2-1.jpg 1000w, https://blog.fschoenfeldt.de/content/images/2020/01/Ohne-Titel-2-1.jpg 1214w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/OverRim.jpg" width="2000" height="1562" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/OverRim.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/OverRim.jpg 1000w, https://blog.fschoenfeldt.de/content/images/size/w1600/2020/01/OverRim.jpg 1600w, https://blog.fschoenfeldt.de/content/images/size/w2400/2020/01/OverRim.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/proj_1.3.jpg" width="1555" height="879" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/proj_1.3.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/proj_1.3.jpg 1000w, https://blog.fschoenfeldt.de/content/images/2020/01/proj_1.3.jpg 1555w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/proj_10c4d_bea.jpg" width="750" height="1000" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/proj_10c4d_bea.jpg 600w, https://blog.fschoenfeldt.de/content/images/2020/01/proj_10c4d_bea.jpg 750w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/SherbertStudio2-1.jpg" width="1280" height="720" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/SherbertStudio2-1.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/SherbertStudio2-1.jpg 1000w, https://blog.fschoenfeldt.de/content/images/2020/01/SherbertStudio2-1.jpg 1280w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.fschoenfeldt.de/content/images/2020/01/Unbenannt-1.jpg" width="1564" height="1564" loading="lazy" alt="3D Graphic Experiments from 2015-2017" srcset="https://blog.fschoenfeldt.de/content/images/size/w600/2020/01/Unbenannt-1.jpg 600w, https://blog.fschoenfeldt.de/content/images/size/w1000/2020/01/Unbenannt-1.jpg 1000w, https://blog.fschoenfeldt.de/content/images/2020/01/Unbenannt-1.jpg 1564w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>Most of these were done in Cinema 4D between 2015-2017</figcaption></figure><p>I will post some more experiments made with another software. I did a lot with Photoshop for <a href="https://heroic.family/?ref=blog.fschoenfeldt.de">Heroic Recordings (Heroic)</a> for some years. Stay tuned :)</p>]]></content:encoded></item><item><title><![CDATA[Writing an image compression app (for Ghost) with NodeJS, imagemin & pngquant (Part II)]]></title><description><![CDATA[Part II into writing an image compression app with NodeJS, imagemin & pngquant.]]></description><link>https://blog.fschoenfeldt.de/writing-an-image-compression-app-for-ghost-with-nodejs-imagemin-pngquant-part-ii/</link><guid isPermaLink="false">6432af39251b5639a9276317</guid><category><![CDATA[node]]></category><category><![CDATA[ndoejs]]></category><category><![CDATA[ghost]]></category><category><![CDATA[image]]></category><category><![CDATA[compression]]></category><category><![CDATA[app]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Thu, 22 Aug 2019 14:25:56 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2019/08/neonbrand-60krlMMeWxU-unsplash--2-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2019/08/neonbrand-60krlMMeWxU-unsplash--2-.jpg" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part II)"><p>Hey guys, welcome back to Part II of my mini-image-compression app series :-) - If you haven&apos;t read Part I, <a href="https://blog.fschoenfeldt.de/writing-an-image-compression-app-for-ghost-with-nodejs/">you can do so right now, its only a three minutes read</a>.</p><p>Last time, we created a simple cli-controller to just handle the userinput and print it back into the console:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/first-test-node-1.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part II)" loading="lazy"><figcaption>The result of Part I</figcaption></figure><p>We can now use this functionality to forward it to imagemin/pngquant to compress our images.</p><!--kg-card-begin: markdown--><h2 id="thecommandssyntax">The Commands/Syntax</h2>
<p>Lets think about which commands we could need for our app to do the hard work for us - in this fashion: <code>node run.js %arg%</code>:</p>
<ul>
<li><code>[filename/*] --resize [%/px]</code> just resizes the image and overwrites the original one
<ul>
<li><code>[%/px]</code> is an optional parameter where we can specify the output width relative (%) or absolute (px) to the original photo. <em>Default is 1080p width</em></li>
</ul>
</li>
<li><code>[filename/*] --compress [quality]</code> determines which file type is in the input and then just compresses the images, overwriting the original one
<ul>
<li><code>[quality]</code> just passed the according argument to imgmin/pngquant, we may need to process this argument for the libraries. <em>Default is 70~85% quality, we&apos;ll see what&apos;s suitable</em></li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h2 id="i-didn-t-had-this-in-mind">I didn&apos;t had this in mind</h2><p>To process multiple arguments (especially more than two) we need to update our run.js to handle more than two arguments:</p><!--kg-card-begin: markdown--><pre><code class="language-javascript">// run.js
&quot;use strict&quot;;

// define controller to keep run.js clean
const controller = require(&quot;./src/cli-controller.js&quot;);

// grab user input with destructuring, saving it into args
const [,, ...args] = process.argv;

// function to handle user input, args is the array containing the arguments
const handleCliInput = (args) =&gt; {
  controller.helloWorld(args);
}

// run the handleCliInput function with the entered args
handleCliInput(args);
</code></pre>
<ul>
<li><code>const [,, ...args] = process.argv</code> simply passed all input after run.js into the <code>args</code> array. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment?ref=blog.fschoenfeldt.de">Read more about destructuring on MDN if you&apos;re not sure what it does.</a></li>
<li><code>const handleCliInput = (args) =&gt; { /* ... */ }</code> I didn&apos;t explain this in the last article, but this is a special way to assign a function - called arrow function. You could also do the classic <code>const handleCliInput = function(args) { /* ... */ }</code> expression. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?ref=blog.fschoenfeldt.de">Read more about arrow functions on MDN.</a></li>
</ul>
<h3 id="timetodestruct1">Time to destruct!1</h3>
<p>Destructuring is so cool, now try it out to confirm that it works by running, for example: <code>node run.js my_cat.jpg --resize 50%</code></p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/second-test-node.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part II)" loading="lazy"><figcaption>Destructuring is so cool! :)</figcaption></figure><p>As you can see, args is an array containing all the arguments the user inputs. Cool!</p><!--kg-card-begin: markdown--><h2 id="thegameofcontrollers">The game of controllers</h2>
<p>We want to update our controller to handle the user input properly:</p>
<pre><code class="language-javascript">// cli-controller.js
&quot;use strict&quot;;

// define the function as a module, so we can import it in the run.js
module.exports.helloWorld = (args) =&gt; {
  console.log(`You entered: ${args}`);
}

// function that decides what do do
// remember the syntax: --(node run.js) FILENAME ARGUMENT--
module.exports.pass = (args) =&gt; {
  if(args.length &gt; 0) { // only run this if args where provided
    console.log(`You provided the file ${args[0]}`);
    // !TODO check if file exists
    if(args.length &gt; 1) {
      // if provided, run specific command (like --compress)
      console.log(`You requested to do &gt;&gt; ${args[1]} &lt;&lt;`);
      switch(args[1]) {
        case &quot;--compress&quot;: // fall-through to case &quot;compress&quot;
        case &quot;compress&quot;:
          if(args[2])
            console.log(`** Insert compressing here, using ${args[2]} **`); // !TODO insert compression via model here
          else
            console.log(`** Insert compressing here, using default level **`); // !TODO insert compression via model here
          break;
        case &quot;--resize&quot;: // fall-through to case &quot;resize&quot;
        case &quot;resize&quot;:
          if(args[2])
            console.log(`** Insert resizing here, using ${args[2]} **`); // !TODO insert resizing via model here
          else
            console.log(`** Insert resizing here **`); // !TODO insert resizing via model here
          break;
        default:
          console.log(`${args[1]} is not a valid parameter.`);
      }
    }
  } else {
    // warning the user that he did not provide a file. !TODO we might want to print out our apps syntax here.
    console.log(`You did not provide a file`);
  }
}

</code></pre>
<p>You need to catch any case that your app should do. I didn&apos;t do any verification of the user input yet, and we may want to let the libraries do it themselves, so they just throw an error. I&apos;ve used <code>switch</code> to decide between the arguments, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch?ref=blog.fschoenfeldt.de">if you&apos;ve never heard about switch, you can read about it on the MDN here</a>. But does it work?</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/third-test-node.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part II)" loading="lazy"><figcaption>Yes it does work!</figcaption></figure><h2 id="wrapping-it-up">Wrapping it up</h2><p>Today, we learned how to write a controller that decides between user input and then runs the according command. Later, we will attach those <code>console.log</code>&apos;s with proper functionality. I&apos;m not so happy with the structure yet and it doesn&apos;t really meets the <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself?ref=blog.fschoenfeldt.de">DRY (don&apos;t repeat yourself)</a> standard of programming, but for now it&apos;ll work.</p><p><strong>If you want to clone the project, you can: <a href="https://github.com/fschoenfeldt/ghost-compression?ref=blog.fschoenfeldt.de">https://github.com/fschoenfeldt/ghost-compression</a></strong></p><p>Thanks for checking out the start of my guide series for writing a (ghost) image compression app with NodeJS. If you have any questions, feel free to contact me via <a href="https://fschoenfeldt.de/?ref=blog.fschoenfeldt.de">https://fschoenfeldt.de</a> - Part III is coming, very soon! x</p><p><em>Cover-Image by <a href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NeONBRAND</a> on <a href="https://unsplash.com/search/photos/mechanic?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></p>]]></content:encoded></item><item><title><![CDATA[Writing an image compression app (for Ghost) with NodeJS, imagemin & pngquant (Part I)]]></title><description><![CDATA[Introduction into writing an image compression app with NodeJS, imagemin & pngquant.]]></description><link>https://blog.fschoenfeldt.de/writing-an-image-compression-app-for-ghost-with-nodejs/</link><guid isPermaLink="false">6432af39251b5639a9276316</guid><category><![CDATA[node]]></category><category><![CDATA[nodejs]]></category><category><![CDATA[ghost]]></category><category><![CDATA[image]]></category><category><![CDATA[compression]]></category><category><![CDATA[app]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Sun, 18 Aug 2019 13:45:00 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2019/08/tomas-sobek-plwud_FPvwU-unsplash--1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.fschoenfeldt.de/content/images/2019/08/tomas-sobek-plwud_FPvwU-unsplash--1-.jpg" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part I)"><p>With my new blog, I quickly ran into the issue of very, very big files, I mean look at this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/big_images_ghost.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part I)" loading="lazy"><figcaption>Oh dear, those are some really big images!</figcaption></figure><p>Those images are almost exclusively from one (upcoming) blog post. So I <em>have</em> to reduce the size of the images, otherwise I will squeeze all your data volume out once you read the article. :-D</p><p>I have done image-compression in the past with my project <a href="https://fschoenfeldt.de/fotohaecker?ref=blog.fschoenfeldt.de">fotohaecker (link)</a>, so I know exactly what to do:</p><ul><li>Scan all the photo files (no matter if png or jpeg)</li><li>Resize them to 1080p (1920x1080px) maximum</li><li>Re-compress them with imagemin(-mozjpeg) and pngquant</li><li><em>(optional) Backup the original photos to a backup location</em></li></ul><p>I would then run the script on my server, it would resize and compress my images and your data is saved. Yay!</p><h2 id="prerequisites">Prerequisites</h2><ol><li>I&apos;m using Ubuntu (Linux), but you can use MacOS or Windows aswell :)</li><li>NodeJS, of course. </li><li><em>imagemin</em>, <em>imagemin-mozjpeg</em>, and <em>pngquant </em>(later on)</li></ol><h2 id="preperation-installing-the-dependencies">Preperation &amp; Installing the dependencies</h2><!--kg-card-begin: markdown--><ol>
<li>Create the directory of our project:</li>
</ol>
<pre><code class="language-bash">mkdir ghost-compression &amp;&amp; cd ghost-compression
</code></pre>
<p><em>We create the subdirectory &quot;ghost compression&quot; in our current folder, and then move into it.</em></p>
<ol start="2">
<li>Initialize the package.json</li>
</ol>
<pre><code class="language-bash">npm init
</code></pre>
<p><em>This is used to organize our project, just enter any information you like here. :-)</em></p>
<ol start="3">
<li>Install the first dependencies</li>
</ol>
<pre><code class="language-bash">npm install imagemin imagemin-mozjpeg pngquant
</code></pre>
<p><em>This installs the three mentioned dependencies into our project and requires them as a dependency in our <code>package.json</code></em></p>
<!--kg-card-end: markdown--><h2 id="starting-to-code">Starting to code</h2><!--kg-card-begin: markdown--><p>Alright, now to the fun part: coding! :-D</p>
<h3 id="theappbarebones">The App (barebones)</h3>
<p>We create a <code>run.js</code> just like this:</p>
<pre><code class="language-js">&quot;use strict&quot;;

// define controller to keep run.js clean
const controller = require(&quot;./src/cli-controller.js&quot;);

// function to handle user input, it needs 1-2 parameters to work
const handleCliInput = (arg1, arg2) =&gt; {
  controller.helloWorld(arg1);
}

handleCliInput(process.argv[2], process.argv[3]);

</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><code>handleCliInput</code> redirects the request to the controller (covered below)</li>
<li><code>process.argv</code> is an array that contains the userinput after the run.js</li>
</ul>
<h3 id="thecontrollerbarebones">The Controller (barebones)</h3>
<p>Don&apos;t try to run the app yet, it wont work. We need a <code>cli-controller.js</code> to handle all the hard work and keep our app.js clean, so create a new subfolder <code>src</code> and in there, a <code>cli-controller.js</code>:</p>
<pre><code class="language-js">&quot;use strict&quot;;

// define the function as a module, so we can import it in the run.js
module.exports.helloWorld = (arg) =&gt; {
  console.log(`You entered: ${arg}`);
}
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><code>helloWorld</code> gets exported to the <code>module.exports</code> object, so we can import it in the run.js. It just logs the argument, you give to it.</li>
</ul>
<!--kg-card-end: markdown--><h2 id="verifying-the-folder-structure">Verifying the folder structure</h2><p>Just to make sure we did everything right, verify your folder structure:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/file-structure-1.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part I)" loading="lazy"><figcaption>Your folder structure should look like this</figcaption></figure><h3 id="testing-the-first-result-yay-">Testing the first result <em>yay!</em></h3><p>So, we got a barebones version of our tool running now. Great! Try it out with:<br><code>node run.js %your-message%</code></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.fschoenfeldt.de/content/images/2019/08/first-test-node.png" class="kg-image" alt="Writing an image compression app (for Ghost) with NodeJS, imagemin &amp; pngquant (Part I)" loading="lazy"><figcaption>You should see something like this printed to the console</figcaption></figure><h2 id="so-what-now">So, what now?</h2><p>Today, we&apos;ve learned how to create a tiny NodeJS starter, to get image compression running with ghost. Once it&apos;s finished, you can use it of course to compress anything that&apos;s jpg/png.</p><!--kg-card-begin: markdown--><p><strong>If you want to clone the project, you can: <a href="https://github.com/fschoenfeldt/ghost-compression?ref=blog.fschoenfeldt.de">https://github.com/fschoenfeldt/ghost-compression</a></strong></p>
<!--kg-card-end: markdown--><p>Thanks for checking out the start of my guide series for writing a (ghost) image compression app with NodeJS. If you have any questions, feel free to contact me via <a href="https://fschoenfeldt.de/?ref=blog.fschoenfeldt.de">https://fschoenfeldt.de </a>- Part II is coming, very soon! x</p><p><em>Cover-Image by <a href="https://unsplash.com/@tomas_nz?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Tomas Sobek</a> on <a href="https://unsplash.com/search/photos/zip?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></p>]]></content:encoded></item><item><title><![CDATA[Testing Markdown with Ghost]]></title><description><![CDATA[Ghost, Markdown and Android :-)]]></description><link>https://blog.fschoenfeldt.de/testing-markdown-with-ghost/</link><guid isPermaLink="false">6432af39251b5639a9276314</guid><category><![CDATA[ghost]]></category><category><![CDATA[markdown]]></category><dc:creator><![CDATA[Frederik Schönfeldt]]></dc:creator><pubDate>Sun, 11 Aug 2019 13:50:14 GMT</pubDate><media:content url="https://blog.fschoenfeldt.de/content/images/2019/08/IMG_20190811_133257.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<img src="https://blog.fschoenfeldt.de/content/images/2019/08/IMG_20190811_133257.jpg" alt="Testing Markdown with Ghost"><p>While most people don&apos;t care about proper structuring of their articles (or even documentations) <em>Markdown</em> makes it super easy to do so. Even the <a href="https://ghost.org/blog/android/?ref=blog.fschoenfeldt.de">Ghost Android app</a> (the content-management-system this blog runs on) supports markdown and proper preview of an article.</p>
<h1 id="gettingstarted">Getting Started</h1>
<p>Check out <a href="https://daringfireball.net/projects/markdown/syntax?ref=blog.fschoenfeldt.de">this markdown page</a> for a quick introduction into markdown.</p>
<p>If you don&apos;t want to wait, fire up a <a href="https://www.markdownguide.org/cheat-sheet/?ref=blog.fschoenfeldt.de">markdown cheat sheet</a> and some <a href="https://stackedit.io/app?ref=blog.fschoenfeldt.de">online markdown editor</a> to try markdown out.</p>
<h1 id="thesyntax">The Syntax</h1>
<p>The syntax of markdown is fairly easy, just look above on the official page or the cheat sheet. The goal of markdown is to give you an easy way of writing text, but also make it readable as itself.</p>
<h1 id="sowhatnow">So, what now?</h1>
<p>Use markdown in your personal projects for documentation e.g. <code>README.md</code> with better structure than a plain text file. You can also generate static pages with markdown (I might write another article about that).</p>
<p>Hope you like it x Fred</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>