<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Allan Dorr</title>
    <description>The latest articles on DEV Community by Allan Dorr (@aldorr).</description>
    <link>https://dev.to/aldorr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F136227%2F27d6c1ad-413c-42aa-bb8a-78d059ce3637.jpeg</url>
      <title>DEV Community: Allan Dorr</title>
      <link>https://dev.to/aldorr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aldorr"/>
    <language>en</language>
    <item>
      <title>Chad Vocab: Vocabulary practice with local Gemma!</title>
      <dc:creator>Allan Dorr</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:51:49 +0000</pubDate>
      <link>https://dev.to/aldorr/chad-vocab-vocabulary-practice-with-local-gemma-2c6e</link>
      <guid>https://dev.to/aldorr/chad-vocab-vocabulary-practice-with-local-gemma-2c6e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chad Vocab&lt;/strong&gt; is a self-hosted vocabulary trainer I built for &lt;strong&gt;Chad&lt;/strong&gt;, a friend who is learning &lt;strong&gt;Polish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;He is able to enter his own language pairs, or photograph a text book page, which gets converted into a vocabulary deck. He can make other language pairs and other decks, if needed. In this demo, he gets English cues and answers in Polish — by typing or speaking. Near-miss answers are graded by an open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model running locally through &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt;. Spoken answers go through optional ElevenLabs Scribe when he pastes his own API key, otherwise open Whisper via &lt;a href="https://github.com/rishikanthc/Scriberr" rel="noopener noreferrer"&gt;Scriberr&lt;/a&gt;. He can also photograph a textbook page; Gemma vision proposes pairs, and he reviews them before anything hits the deck.&lt;/p&gt;

&lt;p&gt;It does not have to be big. It has to matter to him: a private practice loop that keeps his speech and photos on a household laptop, not a cloud AI subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://vocab.slotify.work/" rel="noopener noreferrer"&gt;https://vocab.slotify.work/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo login (registration is closed on the public tunnel):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Username:&lt;/strong&gt; &lt;code&gt;chad&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password:&lt;/strong&gt; &lt;code&gt;mXz1cvwHZZgg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app runs on my machine behind a Cloudflare Tunnel to &lt;code&gt;vocab.slotify.work&lt;/code&gt;, so it is only reachable while the host is awake for judging. LM Studio and Scriberr stay on localhost and are not exposed. LM Studio and any Open Whisper endpoint could potentially be used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick path for judges:&lt;/strong&gt; log in as &lt;code&gt;chad&lt;/code&gt; → practice an English cue in Polish → try a close-but-wrong spelling and watch Gemma accept or correct → open Deck to see the starter list, add/remove words, scan a vocab page or generate audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/67PvrX2uG7U" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aldorr" rel="noopener noreferrer"&gt;
        aldorr
      &lt;/a&gt; / &lt;a href="https://github.com/aldorr/chad-vocab" rel="noopener noreferrer"&gt;
        chad-vocab
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open-source Polish vocab trainer for Chad — local Gemma + Whisper
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Chad Vocab&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Open-source, self-hosted vocabulary trainer built for &lt;strong&gt;Chad&lt;/strong&gt; while he learns &lt;strong&gt;Polish&lt;/strong&gt; from English cues.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;English → Polish&lt;/strong&gt; (or any language pair you set)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type or speak&lt;/strong&gt; — spoken answers go through local &lt;a href="https://github.com/rishikanthc/Scriberr" rel="noopener noreferrer"&gt;Scriberr&lt;/a&gt; (Whisper)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy grading&lt;/strong&gt; — &lt;a href="https://lmstudio.ai/" rel="nofollow noopener noreferrer"&gt;LM Studio&lt;/a&gt; + an open model such as &lt;strong&gt;Gemma&lt;/strong&gt; accepts near-misses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photo → deck&lt;/strong&gt; — Gemma vision reads a textbook page; you review before import&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional accents&lt;/strong&gt; — &lt;a href="https://elevenlabs.io/" rel="nofollow noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; TTS / Scribe per account (bring your own API key)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastery queue&lt;/strong&gt; — needs-practice first; learned cards rare; last 5 mixed with review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Live demo (while the host machine is awake): &lt;a href="https://vocab.slotify.work/" rel="nofollow noopener noreferrer"&gt;https://vocab.slotify.work/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MIT licensed. Self-host forever — a later hosted free/paid offering (if any) does not close the source.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Requirements&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rishikanthc/Scriberr" rel="noopener noreferrer"&gt;Scriberr&lt;/a&gt; running locally (Homebrew: &lt;code&gt;brew tap rishikanthc/scriberr &amp;amp;&amp;amp; brew install scriberr&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lmstudio.ai/" rel="nofollow noopener noreferrer"&gt;LM Studio&lt;/a&gt; with an open model loaded (Gemma recommended) and the local server started&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photo →&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aldorr/chad-vocab" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Repo: &lt;a href="https://github.com/aldorr/chad-vocab" rel="noopener noreferrer"&gt;https://github.com/aldorr/chad-vocab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stack: Vite + React UI, Hono API, SQLite. MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Open-source AI is not a bolt-on — it is the grading and photo path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gemma via LM Studio&lt;/strong&gt; — fuzzy answer grading and textbook photo OCR (&lt;code&gt;server/src/lib/lmstudio.ts&lt;/code&gt;). Without a vision-capable Gemma load, photo import does not work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scriberr / Whisper&lt;/strong&gt; — local speech-to-text fallback when no ElevenLabs key is on the account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional ElevenLabs&lt;/strong&gt; — per-account encrypted API key on Progress for accented word audio and faster Scribe STT. Chad can add his own key; the open path still works without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo hardening for the challenge&lt;/strong&gt; — &lt;code&gt;REGISTRATION_ENABLED=false&lt;/code&gt; on the public tunnel, a seeded &lt;code&gt;chad&lt;/code&gt; login with an English→Polish starter deck, branding as Chad Vocab.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Chad’s practice data should not need to leave the house. Local Gemma means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spoken answers and textbook photos stay on the laptop&lt;/li&gt;
&lt;li&gt;no paid cloud model required to practice&lt;/li&gt;
&lt;li&gt;we can swap models in LM Studio without rewriting the app&lt;/li&gt;
&lt;li&gt;anyone can clone the MIT repo and run the same stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed API would be easier for a weekend demo URL. It would also send a friend’s language practice to someone else’s server. Open-weight inference is what made “build it for Chad, keep it private” possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building Chad Vocab for Hacktoberfest Weekend&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;We need to submit this for the Hacktoberfest Weekend Challenge: Build for a Friend. The app is for Chad — he's learning Polish. Demo at &lt;a href="https://vocab.aldorr.net/" rel="noopener noreferrer"&gt;https://vocab.aldorr.net/&lt;/a&gt; with one login, registration closed.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Checking the challenge rules and auditing the workspace against the prompt: open-source AI at the core, built for one real person during the challenge window.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-getchallengedetails"&gt;get_challenge_details&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;id=78 Hacktoberfest Weekend&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;id=78 Hacktoberfest Weekend&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Prompt: build with open-source AI for a friend. Theme: Build for a Friend. Required tag #hf26challenge. Deadline Oct 5 2026 06:59 UTC. Writing quality weighted most heavily. Prize categories include Best Use of Gemma, ElevenLabs, Entire.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Stick with open source at the core and optional ElevenLabs per account. This is the Chad Vocab App. Login should be for him. Close signup so the public tunnel doesn't get flooded.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implementing Chad Vocab branding, REGISTRATION_ENABLED=false for the demo, and seeding username chad with an English→Polish starter deck. Public repo at github.com/aldorr/chad-vocab. Live demo credentials go in the DEV post only, not in git.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-seedandlock"&gt;seed_and_lock&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;npm run seed:chad; REGISTRATION_ENABLED=false&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;npm run seed:chad; REGISTRATION_ENABLED=false&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Seeded chad with Polish←English deck (12 cards). Registration returns 403 on the public tunnel.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-githubpush"&gt;github_push&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;gh repo create aldorr/chad-vocab --public --push&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;gh repo create aldorr/chad-vocab --public --push&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;https://github.com/aldorr/chad-vocab&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Rebuilt the production client so &lt;a href="https://vocab.aldorr.net/" rel="noopener noreferrer"&gt;https://vocab.aldorr.net/&lt;/a&gt; shows Chad Vocab with demo-login-only. Verified Chad can log in and practice Polish from English cues. Staging the DEV challenge draft next for the user to edit before publish.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        4 of 4 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — local Gemma for fuzzy grading and textbook photo extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — optional per-account TTS / Scribe for accented practice (bring-your-own key)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Entire&lt;/strong&gt; — agent session embedded above&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More...
&lt;/h2&gt;

&lt;p&gt;Please let me know what you think, how you would improve it, and if you found this at all useful. Thanks!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Upload files to the blockchain (Part 1)</title>
      <dc:creator>Allan Dorr</dc:creator>
      <pubDate>Mon, 30 Aug 2021 12:29:08 +0000</pubDate>
      <link>https://dev.to/aldorr/upload-files-to-the-blockchain-part-1-3m9h</link>
      <guid>https://dev.to/aldorr/upload-files-to-the-blockchain-part-1-3m9h</guid>
      <description>&lt;h2&gt;
  
  
  Now let's get our hands dirty
&lt;/h2&gt;

&lt;p&gt;So hopefully I've &lt;em&gt;piqued&lt;/em&gt; your curiosity and you want to jump in now and join in on the fun.&lt;br&gt;
If you want to read my wee rambling on the build-up, see previous post: &lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c" class="crayons-story__hidden-navigation-link"&gt;Filesharing with web3.storage and Vue.js (Part 0)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/aldorr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F136227%2F27d6c1ad-413c-42aa-bb8a-78d059ce3637.jpeg" alt="aldorr profile" class="crayons-avatar__image" width="640" height="596"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/aldorr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Allan Dorr
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Allan Dorr
                
              
              &lt;div id="story-author-preview-content-803077" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/aldorr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F136227%2F27d6c1ad-413c-42aa-bb8a-78d059ce3637.jpeg" class="crayons-avatar__image" alt="" width="640" height="596"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Allan Dorr&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 25 '21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c" id="article-link-803077"&gt;
          Filesharing with web3.storage and Vue.js (Part 0)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vue"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vue&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;h3&gt;
  
  
  Reconsidering
&lt;/h3&gt;

&lt;p&gt;After fooling around and installing, then following some tutorials and attempting to build the first steps in the project, I figured I don't actually want to make a WeTransfer clone, but something any developer can take, add their own Token and fire up their own WT-Like-Thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identifying more issues
&lt;/h3&gt;

&lt;p&gt;I've followed the tutorials and have had to update few things to get it working with Vue 3. These, I will cover when I stumble across them again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Installing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a web3.storage &lt;a href="https://web3.storage/login" rel="noopener noreferrer"&gt;account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get an &lt;a href="https://web3.storage/account" rel="noopener noreferrer"&gt;API Token&lt;/a&gt; (Including 1TB of storage!)&lt;/li&gt;
&lt;li&gt;If you haven't done so already, install:
Node version 14 or higher and NPM version 7 just to be sure we're on the same page. I use nvm, so it's pretty easy to switch between versions.&lt;/li&gt;
&lt;li&gt;Create your Application (I used the vue-cli to generate one: i.e. &lt;code&gt;vue create web3-storage-tutorial&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Install web3storage &lt;code&gt;npm install web3.storage&lt;/code&gt; in the app's folder&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Javascript
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;web3storage.js&lt;/code&gt;&lt;br&gt;
I put this in a &lt;code&gt;/src/utils&lt;/code&gt; folder. Maybe I'll have more utils and put them in there. I don't know. It seems like a good enough place for now.&lt;/p&gt;

&lt;p&gt;Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web3storage.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Web3Storage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;web3.storage/dist/bundle.esm.min.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is to import web3storage for Vue 3. We have to use the pre-compiled bundle, otherwise Webpack will go all wonky and we'll get stupid errors. We don't want stupid errors. They lead us nowhere.&lt;/p&gt;

&lt;p&gt;Now we want to export our code for use in our Vue modules, so we add this under the import:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web3storage.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Code for accessing and uploading files goes here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now inside of this &lt;code&gt;export&lt;/code&gt; we're going to create a couple of functions that will make sense in a minute:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VUE_APP_WEB3STORAGE_TOKEN&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nf"&gt;makeStorageClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Web3Storage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getAccessToken&lt;/code&gt; will read our web3storage access token from the .env.local file. For Vue 3 we have to prepend it with &lt;code&gt;VUE_APP_&lt;/code&gt; in order for it to load. &lt;em&gt;We'll need this token in a minute.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;makeStorageClient&lt;/code&gt; we will need whenever we want to upload something. Again, we'll be needing this soon.&lt;/p&gt;
&lt;h3&gt;
  
  
  Soon is now, so we need these functions
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm going to already let you know that I basically copied these functions from the web3.storage docs. I'm hoping we can use them as a basis, though, and make something great!&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;storeWithProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFileList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;myFileList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;// show the root cid as soon as it's ready&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onRootCidReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploading files with cid:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// when each chunk is stored,&lt;/span&gt;
        &lt;span class="c1"&gt;//update the percentage complete and display&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;uploaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onStoredChunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;uploaded&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;totalSize&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;uploaded&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Uploading... &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;% complete`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// TODO: can we get this to show in browser &amp;amp; update?&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// makeStorageClient returns&lt;/span&gt;
        &lt;span class="c1"&gt;// an authorized Web3.Storage client instance&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeStorageClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// client.put will invoke our callbacks during the upload&lt;/span&gt;
        &lt;span class="c1"&gt;// and return the root cid when the upload completes&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;onRootCidReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;onStoredChunk&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the method that uploads our files and then responds to our app with the CID. we need this to display, and make a link. Right? Yes, we want to be able to send the link to cool people...&lt;/p&gt;

&lt;p&gt;And, here's our JavaScript so far:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Feed it something
&lt;/h2&gt;

&lt;p&gt;Now this beast we are creating wants to be fed. It likes files, so we want to give it some files.&lt;br&gt;
And then finally we want to tell it what to do with said files (Upload them and make them available to the world… to the solar sisters, to the universal compadre-hood of file-sharing-ness. Hello aliens. See our files!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1543722530-d2c3201371e7%3Fixlib%3Drb-1.2.1%26ixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26auto%3Dformat%26fit%3Dcrop%26w%3D1200%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1543722530-d2c3201371e7%3Fixlib%3Drb-1.2.1%26ixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26auto%3Dformat%26fit%3Dcrop%26w%3D1200%26q%3D80" alt="Andromeda Galaxy - Photo by Guillermo Ferla" width="1200" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right.&lt;/p&gt;

&lt;p&gt;We scaffolded out our project with vue-cli, so it has almost everything I need. Oh shoot. Except some CSS. I'm going to go ahead and some &lt;a href="https://duckduckgo.com/?t=ffab&amp;amp;q=css+reset" rel="noopener noreferrer"&gt;CSS reset&lt;/a&gt; and my CSS Framework I'm using.&lt;/p&gt;
&lt;h3&gt;
  
  
  Some CSS first
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install &lt;/span&gt;equal-vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And we update our main.js file to look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Regular app stuff&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// Equal stuff here&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Equal&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal-vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal-vue/dist/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Notice **.use(Equal)**&lt;/span&gt;
&lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Now some Vue
&lt;/h3&gt;

&lt;p&gt;We want to render a button that triggers a file input. Probably we should be able to select multiple files and upload them all to the same cab.&lt;/p&gt;

&lt;p&gt;In the pre-scaffolded Vue project there is the &lt;code&gt;App.vue&lt;/code&gt; which loads &lt;code&gt;HelloWorld.vue.&lt;/code&gt;&lt;br&gt;
Let's rename this to &lt;code&gt;UploadFiles.vue&lt;/code&gt;&lt;br&gt;
Remember: in App.vue we should change &lt;code&gt;HelloWorld&lt;/code&gt; to &lt;code&gt;UploadFiles&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In UploadFiles, we basically want a button that opens a file dialog when we click it, then we want to return the file and then upload it with another button.&lt;/p&gt;
&lt;h3&gt;
  
  
  Basic html structure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;preview&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"file_select"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"upload"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The button opens the file dialog. Then when we have some files selected, the upload button appears, and some image previews show up.&lt;/p&gt;

&lt;p&gt;Let's try to get it to look something like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9epwvrum0jmmsc0hrloc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9epwvrum0jmmsc0hrloc.png" alt="Image of Today's Final Result" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following is what I turned UploadFiles.vue into:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--UploadFiles.vue--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hero"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-divider&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-alert&lt;/span&gt;
      &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"my_thumbs!==null"&lt;/span&gt;
      &lt;span class="na"&gt;iconbox&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
      &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Chosen Files"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;it-avatar-group&lt;/span&gt; &lt;span class="na"&gt;square&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;it-badge&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"top-left"&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
          &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(thumb, i) in my_thumbs"&lt;/span&gt;
          &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"i"&lt;/span&gt;
          &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"i+1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;it-avatar&lt;/span&gt;
            &lt;span class="na"&gt;square&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"upload_preview"&lt;/span&gt;
            &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"thumb"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/it-badge&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/it-avatar-group&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/it-alert&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-divider&lt;/span&gt;  &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"my_thumbs!==null"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-button-group&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;it-button&lt;/span&gt;
        &lt;span class="na"&gt;:type=&lt;/span&gt;&lt;span class="s"&gt;"my_thumbs!==null?'warning':'black'"&lt;/span&gt;
        &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"note_add"&lt;/span&gt;
        &lt;span class="na"&gt;outlined&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"onPickFile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          Select {{my_thumbs!==null?'New ':''}}Files
      &lt;span class="nt"&gt;&amp;lt;/it-button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;it-button&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"danger"&lt;/span&gt;
        &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"upload"&lt;/span&gt;
        &lt;span class="na"&gt;outlined&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"onUploadFile"&lt;/span&gt;
        &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"my_thumbs!==null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          Upload Files
      &lt;span class="nt"&gt;&amp;lt;/it-button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/it-button-group&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;
      &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display: none"&lt;/span&gt;
      &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"fileInput"&lt;/span&gt;
      &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt;
      &lt;span class="na"&gt;multiple&lt;/span&gt;
      &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;change=&lt;/span&gt;&lt;span class="s"&gt;"onFilePicked"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-divider&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"cid!==null"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;it-badge&lt;/span&gt;
      &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Copy link"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;
      &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"cid!==null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;it-button&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"onCopyLink(cidLink(cid))"&lt;/span&gt;
        &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"cid_link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          {{cidLink(cid)}}
      &lt;span class="nt"&gt;&amp;lt;/it-button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/it-badge&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;web3storage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/web3storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UploadFile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;RevealCID&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;filelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;my_thumbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="na"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;onPickFile &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;onFilePicked &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_thumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;onUploadFile &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;web3storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storeWithProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_thumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;cidLink &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.ipfs.dweb.link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onCopyLink &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copied!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1593996705986-bf25089a82c5%3Fixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26ixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D240%26h%3D240%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1593996705986-bf25089a82c5%3Fixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26ixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D240%26h%3D240%26q%3D80" alt="Explosion - Photo by Kayla Farmer" width="240" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of code, but I think it's pretty straight forward, but I'll go through some of it, just for clarification.&lt;/p&gt;
&lt;h4&gt;
  
  
  HTML &amp;amp; Equal CSS
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;it-&lt;/code&gt; tags are from the Equal CSS framework. We make an alert box with the images in it, which only shows up if we have images. The &lt;code&gt;v-if="my_thumbs!==null"&lt;/code&gt; ensures this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;my_thumbs&lt;/code&gt; is set to &lt;code&gt;null&lt;/code&gt; at first, until you've triggered the &lt;code&gt;onFilePicked&lt;/code&gt; method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We loop through the thumbnails with &lt;code&gt;v-for&lt;/code&gt;, and beneath that we have our &lt;em&gt;file select&lt;/em&gt; &lt;code&gt;button&lt;/code&gt;, and our &lt;em&gt;upload&lt;/em&gt; &lt;code&gt;button&lt;/code&gt;   (which also &lt;em&gt;only&lt;/em&gt; shows when &lt;code&gt;my_thumbs!==null&lt;/code&gt;).&lt;br&gt;
Then, our markup has an invisible input file selector, we need that to be able to display our system's file select dialog. (If there's another way to do this, I'd love to hear from you.)&lt;br&gt;
Finally, we have a place to display our &lt;em&gt;link&lt;/em&gt; to the files when we get the CID back. And it is &lt;em&gt;clickable&lt;/em&gt; to copy. Yay!&lt;/p&gt;
&lt;h4&gt;
  
  
  Javascript Data &amp;amp; Methods
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; is pretty clear... &lt;code&gt;cid&lt;/code&gt; should hold a returned cid value once our files have been queued.&lt;/p&gt;

&lt;p&gt;Then, I will run through our methods:&lt;/p&gt;
&lt;h5&gt;
  
  
  onPickFile
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;onPickFile &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This just issues a click command on our file input, to trigger a file dialog.&lt;/p&gt;


&lt;h5&gt;
  
  
  onFilePicked
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;onFilePicked &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_thumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This happens when the file input changes. This does two main things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our &lt;code&gt;filelist&lt;/code&gt; is updated to contain the files we selected&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;my_thumbs&lt;/code&gt; contains a bunch of file blobs that we can then display in the upload preview.&lt;/li&gt;
&lt;/ol&gt;


&lt;h5&gt;
  
  
  onUploadFile
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;onUploadFile &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;web3storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storeWithProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_thumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is our simple upload method. It sets the cid to what we get returned when we call our &lt;code&gt;storeWithProgress&lt;/code&gt; method from before. That method, we send our filelist.&lt;/p&gt;

&lt;p&gt;Finally we set our &lt;code&gt;my_thumbs&lt;/code&gt; and &lt;code&gt;filelist&lt;/code&gt; to null, so our state resets.&lt;/p&gt;


&lt;h5&gt;
  
  
  Our returned data and copying links
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;cidLink &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.ipfs.dweb.link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onCopyLink &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textToCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copied!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;cidLink&lt;/code&gt; just returns the URL that a specific CID would have.&lt;br&gt;
&lt;code&gt;onCopyLink&lt;/code&gt; takes a some text (we are sending it one of these &lt;code&gt;cidLink&lt;/code&gt;s), and copies it to the clipboard... &lt;em&gt;assuming our super-modern browser supports such shenanigans&lt;/em&gt;.&lt;/p&gt;



&lt;p&gt;I didn't do much css here today, besides the components I took from Equal. Of course, we'd want to do some more awesome stuff with that. For example, I added my own &lt;code&gt;main.css&lt;/code&gt; file to add custom styles to center and pretty things up a bit.&lt;/p&gt;

&lt;p&gt;If you'd like for me to cover more CSS, please comment.&lt;/p&gt;

&lt;p&gt;For now, though, (in case you were following along and want to know how I made it look like that) here's a gist of my &lt;code&gt;main.css&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;






&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;Tune in next time for these exciting topics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go back and return upload status and CID to interface.

&lt;ul&gt;
&lt;li&gt;Realized we didn't get this to work yet!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;List already uploaded files.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbpcaxxvtqkxwg12h4ef.png" alt="File List Drawer" width="800" height="537"&gt;
&lt;/li&gt;
&lt;li&gt;Create/save/retrieve shortened links.
___&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;File uploaded with App to web3.storage:&lt;br&gt;
&lt;a href="https://bafybeida4yahentkyih6v6k5o2hwm2agxrhm25ysjslxrm5xoqwem4lfi4.ipfs.dweb.link/Sunset@Achterwasser.jpg" rel="noopener noreferrer"&gt;Sunset@Achterwasser - File uploaded with App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2e1zdgiu0tin4jkuv4j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2e1zdgiu0tin4jkuv4j.jpg" alt="Sunset@Achterwasser - File uploaded with App" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;aldorr&lt;/p&gt;

</description>
      <category>vue</category>
      <category>database</category>
      <category>tutorial</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Filesharing with web3.storage and Vue.js (Part 0)</title>
      <dc:creator>Allan Dorr</dc:creator>
      <pubDate>Wed, 25 Aug 2021 12:24:44 +0000</pubDate>
      <link>https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c</link>
      <guid>https://dev.to/aldorr/filesharing-with-web3-storage-and-vue-js-1m2c</guid>
      <description>&lt;h2&gt;
  
  
  The "Research"
&lt;/h2&gt;

&lt;p&gt;Stumbling through devposts and fintech, I came across this site that claims you can "Build apps backed by Filecoin, no infrastructure required." Decentralized file storage based on  digital coin technology. Automatic replication across network. Freeee*. Bam, I'm already sold and brainstorming project...&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Filestore for some database&lt;/li&gt;
&lt;li&gt;Nextcloud clone&lt;/li&gt;
&lt;li&gt;Wetransfer clone&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;Okay, for 1. I could consider implementing that sometime in the future for some project. But we want to do something a little fun...&lt;br&gt;
For 2... too many variables. Too much to cover. Would eventually have to implement Desktop Client.&lt;br&gt;
For 3... seems like a good fit. A simple file-sharing app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other considerations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A crazy-long URL like &lt;a href="https://baf05eifw7aroo4aue6nmfn4llm4cxfbuumxxhz2qqfx6bo4yeax2pjpbde.ipfs.dweb.link/" rel="noopener noreferrer"&gt;https://baf05eifw7aroo4aue6nmfn4llm4cxfbuumxxhz2qqfx6bo4yeax2pjpbde.ipfs.dweb.link/&lt;/a&gt; is unwieldy, to say the least. Therefore we will need a link shortener involved somehow. I will have to read this post: &lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bauripalash/building-a-simple-url-shortener-with-just-html-and-javascript-16o4" class="crayons-story__hidden-navigation-link"&gt;Building a Simple URL Shortener With Just HTML and Javascript&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bauripalash" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F93311%2F6a5a7b9c-a043-43a6-8333-e9959908a367.png" alt="bauripalash profile" class="crayons-avatar__image" width="480" height="474"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bauripalash" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Palash Bauri 👻
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Palash Bauri 👻
                
              
              &lt;div id="story-author-preview-content-44467" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bauripalash" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F93311%2F6a5a7b9c-a043-43a6-8333-e9959908a367.png" class="crayons-avatar__image" alt="" width="480" height="474"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Palash Bauri 👻&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bauripalash/building-a-simple-url-shortener-with-just-html-and-javascript-16o4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 16 '18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bauripalash/building-a-simple-url-shortener-with-just-html-and-javascript-16o4" id="article-link-44467"&gt;
          Building a Simple URL Shortener With Just HTML and Javascript
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/urlshortener"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;urlshortener&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/html"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;html&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bauripalash/building-a-simple-url-shortener-with-just-html-and-javascript-16o4" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;110&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bauripalash/building-a-simple-url-shortener-with-just-html-and-javascript-16o4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              19&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Oh my, there's no encryption, and anyone who has my file's CID can access it. Plus, since I own the key from for all the CIDs, I can log in to web3.storage and see everything everyone has uploaded. Do I want to involve encryption? (Note: Warn users that they should (strong) encrypt their files before uploading.)&lt;/li&gt;
&lt;li&gt;I'm going to have to choose some framework stuff, so I'm choosing &lt;a href="https://v3.vuejs.org/" rel="noopener noreferrer"&gt;Vue 3&lt;/a&gt;, plus &lt;a href="https://quatrochan.github.io/Equal/" rel="noopener noreferrer"&gt;Equal UI&lt;/a&gt; to try out and hope that it will make a few things easier. (Note: Probably you should use the UI framework you're familiar with. I chose Equal, because it seems to have the best support for Vue 3, whereas Buefy... which I would have loved to have used, I couldn't get set up with Vue 3.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before I get much farther in this, I'm going to take a breath and see if there's anything else I need to consider... Anonymous... Would that pose a risk of bad stuff happening on my account. Hmmm. I am not sure how to avoid this.&lt;/p&gt;

&lt;p&gt;Stay tuned, for my little tutorial on how to make something along these lines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0srk9hbxvh226g8l4fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0srk9hbxvh226g8l4fn.png" alt="Preview of upload page" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
