How I Feel Today, una app para el fediverso

Hace poco he publicado una aplicación web que permite publicar mensajes en el fediverso sin salir de tu navegador.

La aplicación se llama How I Feel Today y es una aplicación VueJS que se conecta a tu servidor de Mastodon (en realidad tu servidor del Fediverso pero como todo el mundo lo conoce como Mastodon…​.) para publicar un diagrama "radar" en formato PNG con tus estados de salud, financiero, social y emocional.

Sin texto, sin explicaciones, solamente una imagen que refleja tu estado de ánimo en ese momento.

UI

El interface de la aplicación es muy sencillo, solamente tienes que seleccionar el "nivel" de cada uno de los estados y la imagen se va ajustando en tiempo real.

hift web

Cuando has ajustado los niveles a cómo te sientes en cada uno puedes publicarlos pulsando simplemente en "publish" y la aplicación publicará un toot con la imagen

Como puedes ver, la aplicación es muy sencilla de usar.

OAuth

Para poder publicar tu HIFT lo primero que debes hacer es identificarte en tu instancia y permitir a la aplicación obtener un access token para poder publicar en tu cuenta

La aplicación detecta cuando no está logeada (más detalles a continuación) y te muestra una pantalla para que indiques la instancia que usas (i.e. mastodon.social, jvm.social, etc)

hift login

Cuando has puesto tu instancia y pulsado Submit, la aplicación te redirige a tu instancia para que la apruebes y ella se encarga de completar el proceso de autorización guardando el access token en tu navegador

En mi opinión, esta es (una de) la "gracia" de esta aplicación: no requiere de un servidor donde guardar secretos ni completar de forma oscura el proceso de autorización. Todo se realiza en tu navegador a base de "redirects"

Vue

El código de la aplicación se encuentra publicado en https://github.com/jagedn/hift/

La aplicación está desarrollada con Vue 3 y se ejecuta completamente en el navegador por lo que no se guarda nada en servidores externos (El servidor donde está alojada es de Github pero por tener el código publicado junto con la aplicación web. En realidad podrías descargarte el código y ejecutarla en tu máquina)

Como puedes ver en el fichero package.json tiene pocas dependencias:

"bootstrap": "^5.3.3",
"chart.js": "^3.9.1",
"masto": "^6.7.0",
"pinia": "^2.1.7",
"vue": "^3.5.10",
"vue-chart-3": "^3.1.8",

Bootstrap para poder "enmaquetar" de una forma responsive la página

Chart.js para generar el diagrama en tiempo real y Vue-Chart-3 para integrarlo en Vue3

Pinia para mantener el estado de la aplicación y compartir los cambios en el modelo de datos

Básicamente la aplicación se compone de un parent HomeView.vue y dos componentes FeelingForm.vue y FeelingChart.vue , así como 2 stores, uno para mantener la configuración de la instancia a la que te has logeado y otro para mantener tus "feelings"

Propagación de eventos y compartir estado

Debido a mis escasos conocimientos de Vue una de las cosas donde me he peleado más ha sido en conseguir sincronizar los cambios entre el formulario donde seleccionas el nivel de cada sentimiento y la generación del Chart, pero una vez puesta en marcha la verdad es que muy simple con Vue3

En el parent simplemente usamos "@evento" (@save en este caso) y proporcionamos una funcion a ejecutar ante este evento

<div class="col-lg-4 col-md-12 col-sm-12 px-0">
      <FeelingForm @save="publishFeeling" v-model="feeling" @logoff="logoff" :topics="topics"/>
    </div>

y en el child emitimos el evento

const submitForm = () => {
  emits('save');
};

  ...

   <form @submit.prevent="submitForm">
    ....
   </form>

En este ejemplo, cuando el usuario pulsa Submit se captura el evento y se emite a su vez uno propio "save"

Para compartir el estado entre el padre y los hijos Vue lo hace realmente fácil

En el padre usamos ref para crear objetos "referenciados"

const feeling = {
  a: ref(0),
  b: ref(0),
  c: ref(0),
  d: ref(0),
};

y los pasamos a los hijos

 v-model="feeling"

En los hijos simplemente definimos el modelo

defineModel('modelValue')

y los usamos donde sea necesario

 <input type="range" class="form-range" id="a"  @change="updated" v-model="modelValue.a.value" min="1" max="5"/>

Fediverse

Una vez que tengo la imagen (en memoria) que quieres publicar es muy sencillo subirla a tu instancia. En realidad el protocolo es muy simple y se podría hacer con unos simples fetch pero ya que existe una librería masto que lo hace por mí, todo se vuelve más fácil

  const masto = createRestAPIClient({
    url: url,
    accessToken: accessToken,
  });

  const response = await masto.v1.media.create({
    file: currentImage,
    description:`A radar chart shows four categories:
        ${topics.a} (light red),
        ${topics.b} (light teal),
        ${topics.c} (light gray),
        and ${topics.d} (gold).
    The ${topics.a} category has a value of ${feeling.a.value}
    The ${topics.b} category has a value of ${feeling.b.value}
    The ${topics.c} category has a value of ${feeling.c.value}
    The ${topics.d} category has a value of ${feeling.d.value}
    `
  })
  const mediaId = response.id;

  await masto.v1.statuses.create({
    status: `#HIFT`,
    visibility: "public",
    mediaIds:[mediaId]
  });

Básicamente creamos un objeto masto usando la url+accessToken y subimos la imagen con media.create añadiendo un texto alternativo (que no falte)

El servidor nos devuelve el id de la imagen que ha guardado y simplemente creamos un toot añadiendo el mediaId

y eso es todo para publicar un toot con una imágen!!!

Por último simplemente guardamos en el navegador el HIFT publicado para usarlos como punto de partida la próxima vez que abrás la aplicación

Github

La aplicación está publicada en Github (podría estar alojada en cualquier servidor de contenido estático) y esto plantea un pequeño reto para aplicaciones Vue y similares que se basan en la ruta del navegador para mantener el estado además de los redirects necesarios de esta aplicación

En el caso concreto de Github hay dos pequeños "hacks" a tener en cuenta

El index.html generado por Vue hay que añadirle un pequeño script:

index.html
   <script type="text/javascript">
      // Single Page Apps for GitHub Pages
      // MIT License
      // https://github.com/rafgraph/spa-github-pages
      // This script checks to see if a redirect is present in the query string,
      // converts it back into the correct url and adds it to the
      // browser's history using window.history.replaceState(...),
      // which won't cause the browser to attempt to load the new url.
      // When the single page app is loaded further down in this file,
      // the correct url will be waiting in the browser's history for
      // the single page app to route accordingly.
      (function(l) {
        if (l.search[1] === '/' ) {
          var decoded = l.search.slice(1).split('&').map(function(s) {
            return s.replace(/~and~/g, '&')
          }).join('?');
          window.history.replaceState(null, null,
                  l.pathname.slice(0, -1) + decoded + l.hash
          );
        }
      }(window.location))
    </script>

además de añadir un .htaccess

.htaccess
Options All -Indexes

<Files .htaccess>
order allow,deny
deny from all
</Files>

<IfModule mod_rewrite.c>
  # Redirect to the public folder
  RewriteEngine On
  # RewriteBase /
  RewriteRule login index.html [L]

  # Redirect to HTTPS
  # RewriteEngine On
  # RewriteCond %{HTTPS} off
  # RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

Así conseguimos que cuando la instancia en la que hacemos login nos haga el redirect a login , página que no existe en nuestra aplicación, sea redirigida a index.html y que se ejecute nuestra aplicacion Vue

Este texto ha sido escrito por un humano

This post was written by a human

2019 - 2025 | Mixed with Bootstrap | Baked with JBake v2.6.7 | Terminos Terminos y Privacidad