Code Monkey home page Code Monkey logo

blog's People

Contributors

mgrubinger avatar

Watchers

 avatar  avatar

blog's Issues

Import and Export a JS Module in one line


date: '2021-01-14'
short: export { namedImport } from './path/to/module';

When you need to immediately export a javascript module again (useful for creating index files):

export { namedImport } from './path/to/module';

Kennedy Space Center Visitor Guide


short: "A mobile visitor guide and education app"
year: "2016"
date: "2016"
type: "mobile app"
tasks: "Development"
tools: "JS, Cordova, PHP, Node, Android, iBeacons"
cover: "https://user-images.githubusercontent.com/465547/203112733-c946d941-a583-45d1-bc3c-82fbf55f7966.jpg"

This project was realized at Fluxguide

We developed a brand new media guiding system to further enhance the visitor experience at the Kennedy Space Center in Orlando, Florida.

Visitors to the complex can rent a media guide to explore attractions and shows during their visit. As they walk around,
the app will automagically show closeby exhibits and attractions. A map prevents getting lost.

The guide is available in 14 languages and can also be used inside the theatres
to listen to synchronized audio in the visitor's own language. At specific locations
the exhibits come to live using Augmented Reality.

Key Features

  • Audio and multimedia content in 14 languages
  • Fully accessible
  • Map with GPS
  • iBeacon triggering throughout the visitor complex
  • Dynamic GPS-based triggering while on bus-rides
  • lipsync audio content while in theatres
  • Anti-theft alarm system
  • Deployed on site, over 400 devices including charging infrastructure
  • custom CMS to edit contents

Space Chase! App

In addition to the onsite visitor guide, we developed a downloadable educational/quiz app: Space Chase! (on iOS and Android )

Partners

Fluxguide – App: Design, Development, Integrations

Q Media Productions – Project Lead, Production and Storytelling

Imagineer – Hardware (Android Players, Charging Racks, Headphones)

Psiori – AR development

ksc_01

ksc_02

ksc_03

ksc_04

ksc_05

Playing Valcamonica


short: "Explore ancient rock art on a large multi-touch screen."
date: "2012"
year: "2012"
tasks: "Concept, Photography, Development, Hardware"
tools: "AS3, OpenExhibits SDK, mySQL"
type: "multitouch exhibit"
video: "https://www.youtube.com/watch?v=6AjHpB7zY7k"
cover: "https://user-images.githubusercontent.com/465547/203114635-07e6c446-122a-40da-950c-d41fdc6fed73.jpg"

During the master's course Digital Media Technologies we developed a multi-touch game for an exhibition on prehistoric rock art in Valcamonica, a valley in northern Italy.

After several weeks of brainstorming and discussions within the team and with archaeologists we came up with a concept involving two main parts:

The central module of the application displays one of the largest rocks in the valley with several hundreds of engravings ("Pitotis") as a high resolution image (3.5 gigapixels). Visitors of the exhibition can explore the rock using multi-touch gestures. By zooming in on the figures it is possible to see the figures almost in real-life size. In order to achieve this, we systematically photographed the rock and manually stitched 214 photos together to create the first complete image of this rock.

Scattered across this huge rock, there are several small games which visitors can play. These mini-games tell short stories about the figures and encourage visitors of the exhibition to play together and discuss about the way, these engravings were drawn.

From October 1st, 2012 to November 4th, Playing Valcamonica is installed at the exhibition PITOTI at the Triennale Design Museum in Milano, Italy. In March 2013, the exhibition was displayed at the Museum of Archaeology and Anthropology in Cambridge, UK.

Awards

APA IT Challenge 2011

Playing Valcamonica was awarded the APA IT Challenge in the category Academia. We feel very honored about this!

Golden Wire 2011 (x2)

The project was also awarded the Golden Wire, the media award of St. Pölten University of Applied Sciences two categories: Interactive and Audience-Favorite.

Team

Ursula Egger, Martin Grubinger, Nadine Jax, Georg Seidl, Christoph Weis

The project was developed under supervision, support and advice by Markus Seidl and Peter Judmaier.

Playing Valcamonica was realized in cooperation of:

Museum of Archaeology and Anthropology and St. Pölten University of Applied Sciences

featured on:

derStandard

Open Exhibits


playing_valcamonica_01

playing_valcamonica_02

playing_valcamonica_03

playing_valcamonica_04

playing_valcamonica_05

playing_valcamonica_06

playing_valcamonica_07

How to deploy a Next.js application to a custom VPS using Bitbucket Pipelines


date: '2020-01-24T08:00:00Z'
short: How to automatically deploy your Next.js app to your server using git, Bitbucket pipelines and rsync
tags:
- Next.js
- Bitbucket
- CI/CD
- pm2

Assuming, that you:

  • are developing a Next.js application
  • host your Git repository on Bitbucket
  • want to run your Next.js app on a virtual private server, like DigitalOcean, Linode or any other machine you can ssh into
  • want your commits to be automatically published to your server
  • appreciate different environments for testing/staging/production based on git branches

Setting up Bitbucket Pipelines

Most of the steps required to setup Bitbucket Pipelines are documented well enough, so I'm just going to list the steps together with the relevant links here:

Getting Started:
If you have never used Bitbucket Pipelines, I recommend reading the official guide for getting started with Bitbucket Pipelines first.

Exchange SSH keys:
In order to enable your pipeline to talk to your server via ssh, you will need to set up SSH keys. Please follow along this article to do so.

Create the Pipelines file

Bitbucket Pipelines allow you to run commands in a fresh docker image that already contains your code every time you push a commit to a certain branch. We'll use it to build our Next.js application and then push it to the server via rsync.

Create a file called bitbucket-pipelines.yml in the root of your git repository.

I use the following configuration:

# Using the node image provided by bitbucket
image: node:10.15.3

pipelines:
  branches:
    # run this on every commit to the branch "staging"
    staging:
      - step:
          name: buildAndDeploy
          # define which caches to use
          caches:
            - node # provided by bitbucket to cache node_modules
            - nextcache # see definitions section below
          script:
            # install rsync
            - apt-get update && apt-get -qq install rsync
            # install node modules
            - npm install
            # build Next.js app
            - npx next build
            # create deploy directory (to contain .next folder, package.json, node_modules, public)
            - mkdir deploy
            - cp -a .next ./deploy
            - cp package.json ./deploy
            - cp -a node_modules ./deploy
            - cp -a public ./deploy
            # rsync to a temp directory on remote server
            - rsync -arz --delete $BITBUCKET_CLONE_DIR/deploy/ $USER@$REMOTE_IP:/var/www/staging-temp
            # clear current serving directory, sync from temp directory to serving directory, restart next server
            - ssh $USER@$REMOTE_IP "rsync -ar --delete /var/www/staging-temp/ /var/www/staging && pm2 restart next-staging && rm -r /var/www/staging-temp"
definitions:
  caches:
    nextcache: .next/cache

A few things to note here:

  • By defining branches > staging we can specify that this script should only run on commits to branch staging
  • You likely want to do something similar for other branches like master, but deploy to a different server or folder depending on your server setup.
  • By creating a custom cache definition called nextcache we can cache the directory .next/cache between builds as recommended by Next.js.
  • In order to deploy only specific directories (.next, node_modules, ...) I copy them to a deploy directory first (another approach would be to use a include-list for rsync)
  • Target folder on the server is hardcoded here (/var/www/staging)
  • to minimize the time it takes for the files to land in the staging directory I am transferring them to a staging-temp folder first, then rsync'ing them locally to staging.
  • The pm2 part of the script assumes you already have your Next.js app running using pm2
  • This is a rather minimal setup, a more real world bitbucket-pipelines.yaml would probably include tests etc.

Add environment variables

If you don't want certain pieces of information about your target server in your repository (like the IP address or the user), I recommend using environment variables for the build process.

In the Bitbucket Repository page, go to Settings → Pipelines → Repository Variables. In my example I used these two variables:

  • Name: REMOTE_IP, Value: The IP address of your target server
  • Name: USER, Value: the username to use for ssh

These Repository Variables can be used in the bitbucket-pipelines.yaml file using $-notation, like $REMOTE_IP�. There are also a bunch of default variables provided by Bitbucket.

Try it out!

If everything is setup correctly (especially ssh keys), your Next.js-app should be deployed to the target server after every commit to staging branch. Visit the Pipelines section of your repository on Bitbucket (<your-bitbucket-repo-url/addon/pipelines/home) to watch what's happening (or see what's going wrong ⚠️).

Feedback

Let me know what you think of this approach: Send me a message on Twitter or an email

28771km


title: "28771 Kilometers"
year: "2017"
date: "2017"
tasks: "Concept, Design, Development"
type: "web app"
tools: "Leaflet, Mapbox, Webpack"
link: "https://28771km.grooovinger.com/"
short: "Travel along as you scroll. 1 pixel = 1 kilometer."
featured: true
cover: "https://user-images.githubusercontent.com/465547/202228535-b623696f-f124-4dd9-820a-4687aa321dd8.jpg"

For my slide show Transasia – Mit dem Motorrad von Meidling nach Malaysia I needed an interactive way to visualize the route I traveled through asia. I built this little web app, where I can control the drawing of my route simply by scrolling.

The plot: for one scrolled pixel, you travel for one kilometer. Thus the name "28771 kilometers" as this is the total length of the trip.

Additionally I needed a way to zoom in to specific areas/countries as I speak, so a quick, reliable and most of all invisible (for the audience) UI was needed. Inspired by one of Marcin Wicharys talks, I implemented an "invisible UI":

Start typing the first letters of a country along the route to zoom in and fit the country into the viewport (also jumps the head of the route to the border). For example: "IRA" for Iran, "MAL" for Malaysia.

This is in no way a finished product but it served me well during my presentations and was fun to build.

Visit "28771 kilometers"

28771km_01

28771km_02

28771km_03

Transasia Book


title: "Transasia Book"
date: "2017"
year: "2017"
short: "A book about my motorcycle trip across Asia."
type: "book"
link: "http://transasia.mgrubinger.at/2017/04/transasia-das-buch/"
cover: "https://user-images.githubusercontent.com/465547/202225633-38bcc84b-67e8-4d1f-b6e4-a169f2f7f163.jpg"
featured: true

In spring 2016 I arrived back home from a 6-month+ motorcycle trip from Vienna, Austria to Kuala Lumpur, Malaysia (see my blog and project 28771 Kilometers). I had a stack of SD-cards full of photos and a bag full of stories to tell. I decided to recompile the stories I wrote for the blog and a selection of the best photos into a beautiful book.

After months of photo editing, writing, layouting and designing I finally printed the book: Transasia – Mit dem Motorrad von Meidling nach Malaysia. I'm pretty happy with the results.

Currently I only sell the book directly, so if you're interested please contact me: [email protected]

transasia_book_0001

transasia_book_0002

transasia_book_0003

transasia_book_0004

transasia_book_0005

Test-Article


slug: testarticle-yay

Let's see how this works

Editing Database in VS Code via SQLTools Plugin


short: Stay in the editor while working on your database.

Lately I've been using the plugin SQLTools for VS Code for viewing and editing SQL databases.

It's easy to set up (don't forget to install the required drivers for your database engine, see the description of SQLTools) and easy to use.

I have not yet figured out, why it sometimes would open the result of a query in a new editor pane. Besides that, I find it comfortable to be able to quickly view and edit the database right from my editor.

By the way, this extension also works if you're working on a remote environment via VS Codes Remote-SSH tools.

WUP at Ars Electronica


year: "2009"
date: "2009-09-01"
type: "interactive experience"
tools: "Java, JavaScript"
short: "Facade installation at the Ars Electronica Center"
cover: https://user-images.githubusercontent.com/465547/210011273-6efed3b1-bb67-419e-9b19-c1a0450925ab.jpg

As a team of five students, we were invited to create a piece of interactive art using the facade of the Ars Electronica Center in Linz. For a couple of hours during the Ars Electronica 2009 visitors were able to play games and control visuals on the 38.500 LEDs of the 5.100 square meters large facade of the Center using smartphones.

To get a better impression, please watch the video.

Team:

Blumenstein Kerstin, Eitler Thomas, Grubinger Martin, Kuba Roman, Maurice Wohlkönig

Awards

This project was awarded the Golden Wire 2009.

wup_01

wup_02

Kunstenaar


year: "2012"
date: "2012"
type: "interactive installation"
tools: ""
short: "Digital multi-touch guestbook for events"
cover: "https://user-images.githubusercontent.com/465547/211394537-14be4e49-1aec-4a1e-8692-a271a7fccc64.jpg"

For the 15th anniversary of my university, St. Pölten University of Applied Sciences, I was commissioned to create a digital guest-book on a 46-inch multi-touch screen.

I developed a multi-touch drawing application using emerging and standard web-technologies. This was a great opportunity to examine the current state of multi-touch enabled web-applications.

Additionally, a large projection showed the eight most recent drawings on a large projection wall.

As a result of this project I wrote my master thesis on the possibilities of multi-touch-enabled web applications.

kunstenaar_01

kunstenaar_02

kunstenaar_03

kunstenaar_04

kunstenaar_05

kunstenaar_06

kunstenaar_07

kunstenaar_08

Pitch black darkness and the degradation of end user experience


date: '2023-10-09'
short:

Yesterday I rode my bike home in pitch black darkness, because the bike lamps ran out of battery. Luckily I was able to go almost all the way on backcountry gravel roads and/or cycle paths. No way I would have survived had I ridden on the main road without lights. I felt helpless without those lamps.

I thought: wouldn't it be great if we had bike lamps powered by the circular motion of the wheels?

Wait, we had that!

When I was a kid*, every bike would be equipped with a dynamo which powered the head- and taillamps of the bike. No worrying about low batteries! No taking the lamps off the bike when parking it in public because otherwise they would be stolen 100%.

Another example: switching on a TV was a matter of pressing one button on a clunky remote. Now? I have to switch on the TV, the receiver, the sound system and set them all to the correct inputs. Luckily I still own a Logitech Harmony, so at least I can do it all through one remote, and some things the Harmony sets up automatically. But this is not the norm, I know many people with two or more remote controls for their TV setup.

This thought lead to the broader question: How come we are accepting such obvious degradations in end-user experience? Sure, the lamps are brighter and the TV sound is from a different league, but still.

Speaking about degradation of end user experience: a big chunk of the "modern" web is barely usable. Ads cluttering the UI, forms failing to submit without proper display of the problem, layout shifts all over the place, text contrast nowhere near conforming WCAG AA levels, janky scrolling experience — I could go on, but you get my point. (Except: have you tried using the facebook mobile website?)

Chris Coyer recently wrote about this topic as well in A lot of stuff is just fine. While I generally agree with his article, I do think a lot of things in the "real" world are not fine and could be significantly better.

What’s extra fricked about all this is that you really gotta try to screw up a website as much as we do. — Chris Coyer in A lot of stuff is just fine

Where do we go from here? In my opinion, there are a few things we can all do:

  • buy less shitty things
  • buy less (in general)
  • as craftspeople (like web developers): aim to build the best possible product for the end user
  • as product teams: don't let capitalism ruin your product
  • as web developers: use the platform; stop using react ffs

* I know these things are still around, but most bikes are sold without.

The case for a global base-10 time system

npm: only install production dependencies


short: Faster npm ci installs

In a production environment, you should not need node_dependencies from listed in the devDependencies section of your package.json.

NODE_ENV=production npm ci will only install dependencies listed in dependencies of your package.json. devDependencies will not be installed.

Alternative: use npm ci --omit=dev

Quickly serve a html document via https

I just needed to quickly serve a simple html document on a remote machine.

Usually I would go for npx serve in the directory where the .html file resides. But serve does not support https, so I found this fork of vercels serve package: https://github.com/warren-bank/node-serve

Just hit npx @warren-bank/serve --ssl

If no certificate is provided, you'll need to do the "this page is insecure but I still want to see it" dance in your browser, but that was enough for my use case (just needed a "secure context" to use secure browser APIs)

Optionally, you can provide a real certificate/key/passphrase as described in the docs.

How to add a Leaflet map to a Gatsby site


date: '2019-07-24T18:00:00Z'
short: Let's fix window is not defined
tags:
- Gatsby
- Leaflet
- SSR

👉 Heads-up: This post is probably outdated, but the main concept remains the same: don't use window variables if you do server side rendering or static site generation.

I have been a long time user and fan of Leaflet, a popular mapping library by @mourner. Recently I needed to integrate a rather simple map (with about 20 markers) to a Gatsby site*.

As it turns out, there's a wrapper for Leaflet in React out there: react-leaflet (Some crazy naming right there, I know!). So I installed react-leaflet as a dependency and imported it, created a <Map>, <TileLayer> as well as a few <Markers> in my page component. Et voilà, we have a leaflet map up and showing up in my gatsby site in no time (running in development mode)!

I simply commited and pushed my changes, as always expecting Netlify to do the rest (i.e. building the site and deploying it). Only later that day I realized the builds were throwing an error: window is not defined 😧

The problem

Leaflet uses the window object internally. When trying to do SSR (server side rendering) as Gatsby does, there is no window (because the app is not acually loading in a brower environment). As a newcomer to React and SSR, this was new to me (although it makes sense).

So wherever I need use window. in my code, I have to add checks if window actually exists. This can be done easily in my application code. But, we're using an external library (Leaflet) here and the last thing I want to do is to mess with the library itself.

The solution

After trying a few different approaches without success, I came across this page in the Gatsby docs: Debugging HTML Builds

We can configure webpack to basically ignore certain modules for the build-stage. To do this, add this code to your gatsby-node.js file:

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /leaflet/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

That's it. Our Gatsby build works again! 🙃


* Right now I can't share the site we are working on as it is not published yet. But here's a screenshot:

Screenshot of a map showing several pins

Export JSDoc Type from Svelte Components


short: How to export/import JSDoc @typedef definitions in Svelte

The component which defines the type needs to do so in a <script context="module"> block (see Svelte docs on context="module")

// ReusableComponent.svelte
<script context="module">
/**
 * An option object for <Select>
 * @typedef {Object} SelectOption
 * @property {string} value the value of this option
 * @property {string} label the visible label to render
 */
</script>

// ... rest of the component

then, in the importing component you can refer to the type by importing it from the module:

// MyComponent.svelte

<script>
import ReusableComponent from './ReusableComponent.svelte`

/** @type {import('./ReusableComponent.svelte').SelectOption[]} */
let optionsList = [
  {label: 'Option 1', value: 1},
  {label: 'Option 2', value: 2},
]
</script>

CSS media query to check for JavaScript support


short: Use CSS to conditional styles depending if JavaScript is enabled

You can now use CSS to check if JavaScript is supported using the new Scripting media query.

@media (scripting: none) {
  .show-for-noscript-users {
    display: block;
  }
}

This example media query would show elements with the class .show-for-noscript-users if JavaScript is disabled in the users browser.

@media (scripting: enabled) {
  .show-for-script-users {
    display: block;
  }
}

This example media query would show elements with the class .show-for-script-users if JavaScript is enabled in the users browser.

Possible Values

Possible values are:

  • enabled – JavaScript is enabled
  • none – JavaScript is not enabled
  • initial-only – JavaScript is only enabled during the initial page load, not afterwards.

Initial Only?

This one is weird. It means that JavaScript is active during page load, but not afterwards. When would that happen? I can't think of a case, but Chrome Platform Status mentions that this can never happen in a browser environment. This issue mentions printing environments. My conclusion is: good to know it's there, probably never going to use it.

Browser support

Support is pretty decent as of January 2023, with Samsung Internet being the only major browser to not support it.

What happens if the browser does not recognize the media query? It will skip the whole block (query), so keep that in mind when using it.

Use cases

Get rid of noscript elements in a flex/grid layout

Traditionally we would use a combination of <noscript> and <style> elements to achieve styling that only applies to noscript environments (JavaScript disabled). However, sometimes an additional <noscript> element can break flex or grid layouts. Using this media query, we can set the display property of the <noscript> element to contents to skip it as a direct flex/grid child.

Demo on CodePen: Grid with a second row of items only for noscript environments (Visit this link with JavaScript disabled to see the second row of the grid)

Progressive Enhancement

According to Progressive Enhancement (PE) approach to building resilient websites, we can use this media query to style things in a certain way if JavaScript is turned off, but enhance the styles with additional rules for when JavaScript it turned on (or vice versa, depending on the use case).

Keep in mind that there are plenty of situations where JavaScript is on, but does not execute, or has not finished executing yet!

Futher reading

Scripting Media Query on MDN

Scripting at Can I Use

Trigger download of remote images with Next.js


date: '2019-11-07T08:00:00Z'
short: 'When the download attribute on an HTML anchor is not enough. Also: how to zip on the fly with Next.js API routes.'
tags:
- Next.js
- React
- Node.js

👉 Heads-up: This post is probably outdated! Proceed with care.

Recently I needed to create a component in a Next.js app that triggers a fiel download prompt. Typically, using the download attribute on the link to the file would be enough to trigger a download instead of opening the file directly in the browser.

This would be an example to ask the browser to open the download dialog for the file (instead of opening it):

<a href="/path/to/my/file.jpg" download>Download file.jpg</a>

(additionally you could provide a custom filename, like this: download="othername.jpg")

"download" attribute does not work for images on a different origin

As it turns out, the download attribute only works for same-origin resources. In my application the images are living on a different server, therefore this approach fails (i.e. the browser ignores download attribute and simply opens the file directly).

The solution

I decided to use a fairly new feature of Next.js: API routes

The aim is to create an API endpoint that takes the URL to the file as a parameter and acts as a simple proxy to the target server. Before sending it back to the user the proxy sets the Content-Disposition header, in order to trigger a download.

Here's what the final function looks like:

const request = require("request");

export default (req, res) => {
  // path to file
  const filePath = req.query.filename;     

  // filename only
  const fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

  // set header
  res.setHeader("content-disposition", "attachment; filename=" + fileName);

  // send request to the original file
  request
    .get(process.env.REMOTE_URL + filePath) // download original image
    .on("error", function(err) {
      res.writeHead(404, { "Content-Type": "text/html" });
      res.write("<h1>404 not found</h1>");
      res.end();
      return;
    })
    .pipe(res); // pipe converted image to HTTP response
};

Now, I can link to /api/proxy?filename=public/mybeautifulpicture.jpg to trigger a download prompt in the browser, even if the file is on a different domain.

Be aware of potential Server Side Request Forgery (SSRF) vulnerability when using the url to the file directly. Thanks to Thomas Konrad for pointing this out! 🙏

Bonus: zip multiple files on the fly before downloading

As an addition to above solution, I implemented a way to request multiple files from the remote server, which are zipped up on the fly before delivered to the user. Using this approach, there's no need to do a cleanup job to remove generated zips from the server after the user has downloaded them.

Using the following script, I can request a zip of multiple files by sending a POST request e.g. to /api/zip with a body of {files: ["file1.jpg", "file2.jpg"]}.

This is a proof of concept implementation. You might want to add some checks like an allow-list and limits to prevent potential malicious usage.

// file: api/zip.js
var async = require("async");
var request = require("request");
var archiver = require("archiver");

export default (req, res) => {
  
  // name of final zip file
  const zipFileName = "downloads.zip";

  // check for "files" in request body
  if (req.body.files == undefined || req.body.files == "") {
    outputError(res);
    return;
  }

  // split up files
  let filesArray = req.body.files.split(",");

  // check if files is an array
  if (!Array.isArray(filesArray)) {
    outputError(res);
    return;
  }

  // prepend every file with the base url of the remote server
  // this assumes REMOTE_URL is set as an environment variable
  filesArray = filesArray.map(f => process.env.REMOTE_URL + f);

  // set content-disposition header
  res.setHeader("content-disposition", "attachment; filename=" + zipFileName);

  // zip them files
  zipURLs(filesArray, res);
};

/**
 * Zip files and send it as response
 * @param urls {array} files to zip
 * @param outStream the response object
 */
function zipURLs(urls, outStream) {
  var zipArchive = archiver.create("zip");

  async.eachLimit(urls, 3,
    function(url, done) {
      try {
        var stream = request.get(url);
      } catch (error) {
        outputError(outStream);
        return;
      }

      stream
        .on("error", function(err) {
          return done(err);
        })
        .on("end", function() {
          return done();
        });

      // Use the last part of the URL as a filename within the ZIP archive.
      zipArchive.append(stream, { name: url.replace(/^.*\//, "") });
    },
    function(err) {
      if (err) throw err;
      zipArchive.pipe(outStream);
      zipArchive.finalize();
    }
  );
}

/**
 * Output 404 Error
 * @param res 
 */
function outputError(res) {
  res.writeHead(404, { "Content-Type": "text/html" });
  res.write("<h1>Whoops, something went wrong</h1>");
  res.end();
}

Let me know what you think about this approach by leaving a comment below.

Svelte at Geizhals


short: Things we've built with Svelte at Geizhals

For the last year or so, we started implementing client side widgets on the public facing Geizhals Preisvergleich websites using Svelte as our go-to library for more complex UIs.

Pricealerts

Both the page for managing your pricealerts as well as the dialog to create a new pricealert for a product are built using Svelte.

https://geizhals.at/?showpa

Wishlists

Similar to Pricealerts, all the UI around wishlists is rendered using Svelte.

Ratgeber

Certain product categories now feature a longform text for people who want to get a better understanding of things to take into consideration when choosing a product. These texts are authored by a dedicated team at Geizhals (no AI!).

Reviews for merchants

Login

(soon) new widgets on the home page

Submit forms in dialogs


short: using method=dialog

If you're using a <form> element inside a <dialog> element, you might want to consider setting its method attribute to dialog.

This will cause the dialog to close automatically on form submission and set the value of the submit button to dialog.returnValue. It will not actually submit the form though.

More details:

I learned about this from Dominik Reitz ner at the Svelte Vienna Meetup.

Was the page navigated to using back or forward button?


date: '2021-01-12'
short: 'using PerformanceNavigationTiming.type'

window.performance.getEntriesByType("navigation")[0].type === "back_forward";

This is how to detect if a page was loaded because the user navigated via back or forward buttons.

Possible values:

  • back_forward if the navigation happened because of a back or forward navigation
  • navigate if the navigation happened because of a regular navigation event (like clicking a link).
  • reload after reloading the page
  • prerender if the navigation has happened during a prerender hint

See PerformanceNavigationTiming.type on MDN

Cinema Paradiso Guestbook


short: "Interactive Drawing Application"
year: "2012"
date: "2012"
type: "multitouch installation"
tasks: "concept, design, development"
tools: "JavaScript, HTML5, CSS3, CodeIgniter"
cover: "https://user-images.githubusercontent.com/465547/203109931-2c9160a6-fcd9-4416-91c9-b9618d0fd0a5.jpg"

For Cinema Paradiso's 10-year anniversary, we created an interactive guestbook. Choosing one of five brand colors, visitors could leave a painting or written message on a multitouch screen. After publishing, their painting was shown to other visitors on a tv screen.

Additionally, visitors were asked to insert their email address after saving the image, if they want to take part in a competition.

The Guestbook is based on my previous project Kunstenaar. We added a mobile admin UI to have more control over what's visible on the tv and improved the performance of the drawing application as well as the tv application significantly.

Realized in coorporation with Christoph Weis.

cinema_01

cinema_02

cinema_03

cinema_04

cinema_05

cinema_06

cinema_07

cinema_08

cinema_09

Sonnenwelt Village


date: "2013"
year: "2013"
short: "Populate a village with your energy-efficient house on this multitouch table."
type: "multitouch exhibit"
cover: "https://user-images.githubusercontent.com/465547/203115771-ed301b4a-17ac-441a-b3b5-5b8a974f2c84.jpg"
featured: true

For the new science center for renewable energy Sonnenwelt in Großschönau, Austria I developed a multitouch application for the final stage of the interactive journey through the center.

During their visit, visitors can collect items and play games to learn more about energy usage in our daily lifes on a mobile guide.

At the end of the visit the customized house gets transfered to a 55 inch multitouch table showing a growing village. By choosing a good spot and correct alignment for the house visitors earn additional points for saving energy.

Every day a different village gets created depending on how and where visitors decide to place their houses.

Context

This application was developed as a subcontractor to St. Pölten University of Applied Sciences in collaboration with Christoph Weis.

sonnenwelt_01
(Klaus Pichler)

Muddle Motion Theatre


short: "A mysterious puppet theatre, performed at Donaufestival Krems"
date: "2009"
tasks: "Concept, Design, Performance"
type: "performance"
tools: "Projector, old instruments, analogue fx"
video: "https://vimeo.com/20463916"
cover: "https://user-images.githubusercontent.com/465547/207103692-6eff4ff0-6202-4403-b808-4ac07c01a550.jpg"

A mysterious puppet theatre supported by analogue/digital projected visuals and sound effects, synthesizers, a beat machine and various hacked toys/things from the scrapeyard.

Premiered on 28th of April 2009 at the Donaufestival Krems, Austria.

Performer:

Progressive Factory: Christian Munk, Martin Grubinger

Surrealistic Theatre: Robert Binder, Barbara Binder

muddlemotion_01

muddlemotion_02

Thomsons Projection


year: "2014"
date: "2014"
short: "web app for hand-drawn maps"
type: "web app"
link: "http://www.christopherthomson.net/index"
cover: "https://user-images.githubusercontent.com/465547/210010527-c898c6f8-42c7-41a3-9962-07b88fd35a29.jpg"

Christopher Thomson, a talented writer, filmmaker and photographer, asked me to transform two of his hand-drawn maps into an interactive experience.

The two intriguing maps show the area around the village of Dordolla, Italy in an interesting perspective. One at daytime looking west, one at nighttime looking east. The maps show hiking paths, almost forgotten places and the peaks around the village.

The application enables users to explore the maps in all their beautiful detail. Every place on the map also has a photograph attached to it. This way you can get a sense of the beauty of the area even if you're far away.

This project was realized as part of the exhibition EWIGE BAUSTELLE in May 2014.

thomsonsprojection_01

thomsonsprojection_02

thomsonsprojection_03

thomsonsprojection_04

Autowuzzler


date: "2021"
year: "2021"
tools: SvelteKit, Colyseus, Matter.js, Supabase.io
short: A real-time multiplayer game
type: game
cover: https://user-images.githubusercontent.com/465547/207396769-02232676-3753-4320-bf9f-c538373ce698.png

Autowuzzler is a virtual foosball (table soccer) multiplayer game. Invite your friends and shoot those goals. Play for free over at: autowuzzler.com

Note: This game is in public beta right now and far from finished (I have plenty of ideas how to take this further). If you experience any issues, please do not hesitate to contact me (@grooovinger or [email protected])

autowuzzler-ingame-6players

Idea

During the first Covid-19 lockdown, I missed playing foosball with my colleagues at the Geizhals-office, so I decided to do something about it. While playing toy cars with my kid, I had the idea of mixing virtual toy cars with a foosball setup and combining it with a simple Join-via-PIN à la kahoot.it to enable frictionless sessions.

Gameplay

The game resembles a 2D topdown view of a foosball table. Each player is assigned to one of two teams and tries to kick the ball into the correct goal. The first team reaching 10 goals wins.

Joining a Game

To start a new game, a player creates a game PIN for a session. This game PIN can then be shared with friends as a unique URL to their game session. When a player first loads the game, they enter a nickname which will appear next to the players car.

Tech-Stack

This application is built on the following stack:

  • SvelteKit for the public app and frontend (autowuzzler.com)
  • Colyseus.js as an authoritative real-time game server
  • Matter.js for physics calculations
  • Supabase.io to store game PINs
  • Hosting
    • Linode for the Node-server
    • Netlify for the public-facing (frontend) application

Read more about the idea, process and development of Autowuzzler on Smashing Magazine.

Automatic resizing of textareas and inputs using CSS


short: "field-sizing: content lands in Chrome 123"

There's a new CSS property landing in Chrome 123 that will automatically resize textareas and inputs based on its content. Since I could not remember its name and only found it by digging through my Mastodon conversations and finding this post by Jen Simmons again, I thought I'd note it down here.

The property is called field-sizing and has two possible values: strict (default, just as before) and content which will automatically grow the element if needed.

The team at Polypane did a great job explaining it in more detail in this blog post: Field-sizing just works! – go read it if you have not heard about it.

Superscroll


year: "2011"
date: "2011"
tasks: "concept, development"
tools: "Processing, Arduino, Thermoprinter, Lego, Webcam"
type: "interactive installation"
short: "A semi-digital interactive multi-player game."
video: "https://vimeo.com/43594944"
cover: "https://user-images.githubusercontent.com/465547/207104892-985f51eb-7283-47e3-8ed8-367270ff4040.jpg"

For an assignment for the course Interaction Design at St. Pölten University of Applied Sciences we created a litte game that combines physical level design and a digital, projected avatar.

Player 1 uses a physical controller to print the level that Player 2 has to play in real time.

The printing is done using a thermoprinter. As this represented a quick and dirty prototype we used a camera and OpenCV to detect the created level.

As you can see in the video, it was great fun to play, while at the same time it's not exactly what you would call eco-friendly...

Team

Christian Munk, Daniel Werndl, Guilia Raberger, Martin Grubinger. A project of Progressive Factory.

superscroll_01

superscroll_02

superscroll_03

superscroll_04

superscroll_05

Form with multiple submit buttons


short: How to handle HTML Forms with multiple submits

tldr;

A form can have multiple input[type=submit] (or <button type=submit>) elements. You can specify different values, targets, methods or even different actions for these.

In JavaScript, the information which submit-button was used to submit the form can be accessed in the SubmitEvents submitter property.

Example:

<iframe src="https://stackblitz.com/edit/web-platform-cbuerh?embed=1&file=index.html&hideDevTools=1&hideExplorer=1&view=preview" width="100%" height="400px"></iframe>

Recently, I've been exploring the html <form> element more, mainly because it's such a central building block for SvelteKit apps. Or, more precisely: forms have been a major building block for web applications forever, but I feel that it's importance and role has been neglected in the last decade or so of JavaScript-heavy SPAs. Personally, I feel that I have a knowledge gap around such an important building block, and maybe I'm not alone.

The example above has two sections:

  1. Form with two submit buttons with the same name attribute, but different values. By default, hitting one of the two submits will submit the form and change the URL of the example website, depending on the value of the clicked element. Kinda like a radio group, but for distinct actions a user can take.
  2. The same form, but this time intercepting the forms submit event and updating text on the website via JavaScript – again, depending on which button was used.

There are more things you can specify on a submit element, like the action to submit to or the forms method. See this MDN page on the input[type=submit] element.

Svelte Tricks Collection


short: A collection of tricks and niceties you might find useful when working with Svelte.

A collection of tricks and niceties you might find useful when working with Svelte. This list is intended to be updated from time to time.

  1. Style components using css variables
  2. Forward events to parent component
  3. Locally global styles (with Svelte-Preprocess)
  4. Loop over part of an Array
  5. Dynamic import of components within template

Style components using css variables

I can think of multiple options to apply external styling for a reusable component:

  • Expose a style prop
  • Expose a prop for different classes, which you can then apply to the individual elements inside the component
  • Use the :global() selector in the parent component
  • Use CSS Custom Properties (CSS vars).
<iframe width="100%" height="500px" src="https://stackblitz.com/edit/vitejs-vite-nkl1ru?ctl=1&embed=1&file=src/App.svelte&hideExplorer=1&view=preview"></iframe>

In the example above I used the CSS vars approach. The idea is, that you allow overriding of certain styles based on CSS vars that get applied to the component. Svelte supports setting CSS vars directly when using a component, like this:

<Component --bg-color=”blue” />

Note how you don't need the style attribute, just pass in the props via --propname attribute!

Then, in the <style> block of your reusable component, use the CSS variable – ideally with a fallback value:

<style>
.mycomponent {
    --bg: var(--bg, red);
}
</style>

I think that’s a clean way to open up styling adjustments for a component. I think of this approach like a separate set of props, just for styling. You might also want to consider using special prefixes for these kind of variables to prevent naming clashes.


Forward events to parent component

If you want a component to pass an event to its parent, all you need to do is to set the attribute on: on the element triggering the event. Like this:

// within InnerComponent.svelte
<div class="card">
  <button on:click type="button">Open</button> // <<-- note the on:click here
</div>

// within ParentComponent.svelte
<MyComponent on:click={openCard} />

This is not a secret trick, it's even in the new official tutorial. I find it very useful and it's a svelte feature that just makes me authoring components such a joy.


Locally global styles (with Svelte-Preprocess)

(a weird name that I just made up)

In Svelte, you can set individual CSS rules to be global using the :global() keyword in the style section. Sometimes you might want to have styles, that are global, but only as long as the element is a child of a certain element to prevent clashes.

Utilizing svelte-preprocess with Less or Sass and postcss you can write rules like this:

<style lang="less">
.mycomponent {
  /* local styles of this component */

  :global {
    .dynamically-added-element {
        color: deeppink;
    /* global styles that are only available for elements with class dynamically-added-element which are children of .mycomponent */
    ...
    }
  }
}
</style>

Note that the :global {} block is a feature of svelte-preprocess


Loop over part of an Array

To run an each loop over just a part of an array, use a dynamically constructed array straight in the template:

{#each {length: 5} as _, index (index)}
    {@const item = items[index] }
    <!-- do stuff with {item} -->
{/each}

You might argue that extracting the slice of the array in the <script> area of a component is more readable. I agree.


Dynamic import of components within template

To dynamically load a component, you can import it straight from the template using #await:

{#await import("$lib/path/to/ComponentName.svelte") }
  <p>loading...</p>
{:then { default: ComponentName }}
  <ComponentName />
{/await}

Line number 5 destructures the imported module to get the default export (our svelte component) and renames it to ComponentName. Be careful not to accidently create request waterfalls by only loading components lazily your application does not need for the initial render.

CSS Background Position Offsets


short: Improved positioning of CSS background images

You can set an offset for CSS background-position to specify the position of a background image from right and/or bottom boundaries, like this:

.my-element {
  background-image: url('path/to/image.png');
  background-position: right 10px bottom 5px; 

This will place the image in the bottom-right corner, but move it 10px to the left and 5px towards the top. Kinda like setting a transform: translateX(-10px, -5px) (which you can't for background images).

So far I've only ever used right bottom or a percentage/pixel based position for background images, but background offset comes in handy when making sure a flush right or bottom background-icon is positioned exactly where you need it.

Also see css-background-offsets on caniuse

neo:lights:out


short: "A simple puzzle game about switching all the lights off. A remake of the 1990s game Lights Out."
year: "2020"
date: "2020"
type: "PWA game"
tasks: "everything"
tools: "svelte, sveltekit, netlify, PWA"
link: "https://neolightsout.grooovinger.com/"
cover: "https://user-images.githubusercontent.com/465547/203114081-4b95cae1-d0e5-4619-8222-8935319ec75e.png"
video: "https://youtu.be/fUvCqCCmVsg"

When I was a kid, I loved to play a game called Lights Out by Tiger Electronics.

It was my first portable "game console", and this is what it looked like:

<iframe width="560" height="315" src="https://www.youtube.com/embed/pj0lVmhkx7M" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

OMG, this commercial is gold!

Recently a similar grid pattern reminded me of this game, so I decided to recreate it – but as a progressive web app.

While I'm not a big fan of the Neomorphism design trend, I wanted to play around with it to get an idea how the gradients and shadows are combined for that special look.

Stuff I used for this project:

  • Svelte with Sapper, which I highly enjoyed using (upgraded to SvelteKit in the meantime)
  • Default Sapper Service Worker to enable offline mode
  • CSS Grid, because – well – it's a grid!
  • This handy Neumorphism Generator
  • This ancient Lights Out implementation where I could copy the levels from
  • Figma for the favicon and other images
  • Netlify for hosting the statically exported app, thanks so much!

neolightsout_001

neolightsout_002

<template> element


date: '2022-10-25'
short: ... for dynamic content

The <template> element is useful if you want to dynamically create markup based on a predefined “template” (great naming right there).

Example markup:

<template>
	<div>
		<h3>any markup, really</h3>
		<!-- watch out for FOUT if you also load relevant styles -->
		<link rel="stylesheet" href="url-to-stylesheet.css">
	</div>
</template>

<div class="my-target"></div>

Example script

// grab the template
const template = document.getElementById('my-template');
// clone the templates content
const clonedTemplate = template.content.firstElementChild.cloneNode(true);
// optionally modify the cloned tree here
// append to `.my-target`
document.targetSelector('.my-target').appendChild(clonedTemplate);

👉 The first child of (the

Group all DOM elements by font-size


date: '2021-04-09'
short: Debug the distribution of font-size of DOM elements on the page

Group all elements on the current page by their font size:

let elementsBySize = [];
[...$$('body *')].forEach(el => {
  let fontSize = window.getComputedStyle(el).fontSize;
  if(!elementsBySize[fontSize]) elementsBySize[fontSize] = [];
  elementsBySize[fontSize].push(el)
});

elementsBySize now contains an array with key: font size and value of an array containing all the elements with that font size.

Note: this only works in the chrome(ium) devtools, since it uses the $$ syntax. You can replace it with document.querySelectorAll('*')

Make an element stick to top and bottom!


date: '2020-08-12T08:00:00Z'
published: true
short: Useful CSS to make an element stick to the top and bottom of a scroll container!
tags:
- TIL
- CSS

It is possible to make an element stick to the top and bottom of a scroll container! You could say: "Of course!", but I guess I was just surprised it actually works.

Here's the CSS:

.i-am-sticky {
	position: sticky;
	top: 0; /* or any other value if you want some offset */
	bottom: 0; /* or any other value if you want some offset */
}

I find this especially useful for a List/Detail two-column layout where items in the right column shows details when item in the left column is selected (think Apple Mail etc).

This way the selected item always stays visible to the user.

(Imagine trying to solve this using JS, urgh.)

Demo:

Here's a quick demo:

https://codesandbox.io/embed/sticky-top-and-bottom-5s9ti

What are we going to eat today?


short: "A tiny app that helps you decide what you should get for lunch"
year: "2019"
date: "2019-06-13"
type: "web app"
tasks: "idea, development"
tools: "svelte, firebase"
link: "https://whatarewegoingtoeattoday.grooovinger.com/"
cover: "https://user-images.githubusercontent.com/465547/210010706-3e4b3dc4-a989-4df5-ad17-7d41ab8c9842.png"

Every day at around noon we face the same problem at the Fluxguide office: What are we going to eat today?

Because of that, and, more importantly, because I really wanted to build something using Svelte, I decided to build an app for that.

Using What are we going to eat today? you can add all the possible things you can get for lunch (along with price and distance information) and then hit that bigass yellow button. Using Artificial Intelligence on a Blockchain and AR, the app decides to read your mind and suggest what you should get for lunch today.

(Ok, actually it is simply a simple dumb randomizing function.)

This app has been retired in the meantime.

<iframe width="177" height="315" src="https://www.youtube.com/embed/ONdeBfgvK8E?rel=0&controls=0&showinfo=0&autoplay=1&fs=0&modestbranding=1&showinfo=0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

Tech: I used svelte to build the app, which was really easy to get started with. I especially like how many things are already baked in (transition, stores, settings tags in the <head> of your document, to name just a few) and still it's very simple to use.

For storing data as well as authentication, I used Google Firebase. It was quite easy to set up and use in a svelte app. Unfortunately I only discovered late in the process that it adds around 500kb to my Javascript-bundle. Uarg! Since this is just a fun side-project, I did not bother to implement an other data storing and auth solution.

The app was hosted on Netlify, of course.

whatarewegoingtoeattoday01

whatarewegoingtoeattoday02

whatarewegoingtoeattoday03

whatarewegoingtoeattoday04

Cypress Testing Library Custom Error Message


date: '2021-06-23T22:00:00Z'
short: How to reduce logging noise when using Cypress Testing Library
tags:
- Cypress
- Testing
- TIL

Cypress Testing Library outputs a rather verbose log message when it can't find an element using e.g. findByRole. The intention is to help you, the developer to fix the test and figure out which accessible elements are available.

However, I find this not very useful and too verbose, especially in a CI environment.

Luckily, Cypress Testing Library allows for customization of the message.

I use this configuration to limit the log messag length, while still keeping the relevant information about which element was not found:

// file: cypress/support/commands.js
import '@testing-library/cypress/add-commands';
import { configure } from '@testing-library/cypress'

configure({
  getElementError: (message, container) => {

    // truncate everything after 'Here are the accessible roles:'
    let indexOfRoleListMessage = message.indexOf('Here are the accessible roles:');
    let shortMessage = message.substring(0, indexOfRoleListMessage);

    // return a new error with the shorter message
    let error = new Error(shortMessage);
    error.name = 'AccessibleElementNotFoundError';
    return error;
  }
})

Henri Cartier-Bresson at MoMA NY


short: "Website for the exhibition at MoMA NY"
year: "2010"
date: "2010"
tasks: "development (front- and backend)"
type: "website"
link: "https://moma.org/cartierbresson"
cover: "https://user-images.githubusercontent.com/465547/203113467-47d9d07e-fe5e-470d-ac15-87be610cd6a0.jpg"
tools: "Symfony, JavascriptMVC"

During my internship at Second Story Interactive Studios in Portland, Oregon I was honored to be part of the team to develop the website accompaning the exhibition Henri Cartier-Bresson: The Modern Century at the Museum of Modern Art NY.

Even though in spring of 2010 the term "responsive webdesign" was not yet a coined term, the site is built in a responsive manner. This was especially important as the website was also installed at a public kiosk at the museum.

Looking back I find it highly interesting that the result of the project was a static website. There was no Gatsby or Hugo back then, so we exported the php views from Symfony using a custom-built exporter.

Awards

The project was awarded a Best of the Web Award by Museums and the Web in 2011.

MoMA_01

MoMA_02

MoMA_03

MoMA_04

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.