Portfolio : How to create a Vuejs Portfolio App on Solid

Modified

How to create a Vuejs portfolio app on Solid

Prerequist : Nodejs LTS (latest stable)

We will use :

Nb: to simplify the code we volontary omit error handling, solid-auth trackSession (check session changes)...

First you'll need the vue-cli tool and create your basic vuejs app
npm install -g @vue/cli
vue create portfolio


then 'manually select features' and check those options

  • Choose Vue version
  • Babel
  • Progressive Web App
  • Router
  • Vuex
  • Linter / Formatter

Select vue version 2.x as vue 3.x is preview for the moment

use history mode for router : Yes

Eslint with error prevention only / Lint on save / In dedicated config files

save preset : yes to reuse it later

https://spoggy-test6.solidcommunity.net/public/blog/images/vue-create-portfolio.png

Typing 'Enter' will build your app in the /portfolio folder

As prompted, just cd to the new created folder and run npm run serve to see the base of your app on https://localhost:8080 ( or other port if you already have someting running on port 8080)

Opening a web browser at http://localhost:8080, you should see your app running :

If this is ok, let's take the time to a look at what our app is made with...

Open your favorite code editor

In the picture below you can see all the basic of a vuejs app :

  1. on left the architecture of your code
  2. next the /src/main.js file which is the entrypoint of our app calling the /src/App.vue file and mounting it in the div with id #app located in the /public/index.html
  3. the App.vue file implementing the router links 'Home' & 'About' that we can see in the above image
  4. the router links are managed in the /src/router/index.js mapping Home to the /src/view/Home.vue view
  5. the Home.vue view itself import the /src/components/HelloWorld.vue component ( as said , @ is an alias to /src )

Views & components have the same structure, just consider components as atomic elements and views has a more big structure that group components.

If you take a look closer to /src/components/HelloWorld.vue, you can note the basic architecture of a VueJs component :

  • a template tag where we put our html
  • a script tag where our javascript code reside
  • a style tag where you can add css


-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

Connecting your app to the user's POD

To connect your app to the user's POD, we will use solid-auth-client so open a second command line console to our portfolio folder and install it with npm :

npm install --save solid-auth-client

Then we can build a /src/components/solid/SolidLoginButton.vue component and import it in our /src/views/Home.vue component

We can build it from scratch with this code :

/src/components/solid/SolidLoginButton.vue

<template>
<div>
<button>Login</button>
<button>Logout</button>
</div>
</template>
<script>
export default {
name: 'SolidLoginButton',
}
</script>

Now that we have our component, let insert it into the /src/views/Home.vue view

/src/views/Home.vue

<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<SolidLoginButton />
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
name: 'Home',
components: {
HelloWorld,
'SolidLoginButton': () => import('@/components/solid/SolidLoginButton'),
},
}
</script>

Note the alternative to load a component, HelloWorld is always imported, whereas SolidLoginButton is lazy imported (only if needed). This does not really matters here but i have taken this habit to only load components when needed. Note that you can omit the '.vue' extension too.

If you have stopped your dev server re-run it with npm run serve , if not you should see your app automaticaly updated in the browser at http://localhost:8080 with your new component and its two login/logout buttons.

-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

Now that we have login/logout buttons we can use solid-auth-client library to login/logout

First import the solid-auth-client lib adding import auth from 'solid-auth-client'; just after the script tag of the /src/components/solid/SolidLoginButton.vue file

Then add two Vuejs async methods : login() & logout() that wrap the solid-auth-login ones

Map the two buttons to these new created methods with an @click attribute

/src/components/solid/SoldLoginButton.vue

<template>
<div>
<button @click="login">Login</button>
<button @click="logout">Logout</button>
</div>
</template>
<script>
import auth from 'solid-auth-client';
export default {
name: 'SolidLoginButton',
methods: {
async login() {
let session = await auth.currentSession();
let popupUri = 'https://solidcommunity.net/common/popup.html';
if (!session)
session = await auth.popupLogin({ popupUri });
alert(`Logged in as ${session.webId}`);
},
async logout(){
await auth.logout()
},
}
}
</script>

And test it : Clicking on the login button, the login popup should open and you should be able to login with your Solid WebId


Now we can login/logout, let's optimize & customize our button : the logout button should only be displayed when the user is logged, and the login one only when he is not logged.

We can do this creating webId variable in the data section of the /src/components/solid/SoldLoginButton.vue component and testing for webId value in the template with v-if="webId == null" as attribute of login button and v-else as attribute to logout button. We also need to modify the webId value in the login() function with this.webId = session.webId and in the logout() function with this.webId = null .

the alert(`Logged in as ${session.webId}`); in the login() function is not utile anymore and sometimes blocks the closing of the popup window so we can remove this line.

with this new code only one button should be shown at a time, depending on logged status of the user

/src/components/solid/SoldLoginButton.vue

<template>
<div>
<button v-if="webId == null" @click="login">Login</button>
<button v-else @click="logout">Logout</button>
</div>
</template>
<script>
import auth from 'solid-auth-client';
export default {
name: 'SolidLoginButton',
data: function () {
return {
webId: null
}
},
methods: {
async login() {
let session = await auth.currentSession();
let popupUri = 'https://solidcommunity.net/common/popup.html';
if (!session){
session = await auth.popupLogin({ popupUri });
}
this.webId = session.webId
},
async logout(){
await auth.logout()
this.webId = null
},
}
}
</script>

Let's make it a little bit smarter now using some cool css framework. This could certainely be done with official vue/ui or vuetify

Personnaly I fell easy with bootstrap-vue so let's use npm to install it

npm install vue bootstrap-vue bootstrap --save

Then, register BootstrapVue in your app entry point (main.js)

import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)

And import Bootstrap and BootstrapVue css files:

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Our new /src/main.js should now look like this

/src/main.js

import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'

Vue.use(BootstrapVue)
Vue.use(IconsPlugin)

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')


This allow us to use bootstrap-vue in every component and we can customize our login logout buttons, modifying button tag to b-button and adding a variant attribute

<template>
<div>
<b-button variant="success" v-if="webId == null" @click="login">Login</b-button>
<b-button variant="danger" v-else @click="logout">Logout</b-button>
</div>
</template>
<script>
import auth from 'solid-auth-client';
export default {
name: 'SolidLoginButton',
data: function () {
return {
webId: null
}
},
methods: {
async login() {
let session = await auth.currentSession();
let popupUri = 'https://solidcommunity.net/common/popup.html';
if (!session){
session = await auth.popupLogin({ popupUri });
}
this.webId = session.webId
},
async logout(){
await auth.logout()
this.webId = null
},
}
}
</script>

If all is good you should see a green login button when not logged and a red logout button when not logged.

That's the way I start lot of my Solid Apps like poPock and you can build lot of different Solid apps using this pattern.

-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

Uploading a pic to user's storage

As our goal is to build a portfolio app, let's build a component that allow the user to upload a picture to his storage (POD).

Starting with bootstrap-vue form-file our /src/components/portfolio/Upload.vue component could be something like this, allowing multiple and accepting only images :

/src/components/portfolio/Upload.vue

<template>
<div class="container">
<b-form-file multiple
accept="image/*"
v-model="files"
:state="Boolean(files)"
placeholder="Choose a file or drop it here..."
drop-placeholder="Drop file here..."
@input="onInput"
></b-form-file>
</div>
</template>
<script>
export default {
name: 'Upload',
data() {
return {
files: null
}
},
methods: {
onInput(files) {
console.log(files)
}
}
}
</script>

and we can now import it into our /src/views/Home.vue

/src/views/Home.vue

<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<SolidLoginButton />
<Upload />
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld,
'SolidLoginButton': () => import('@/components/solid/SolidLoginButton'),
'Upload': () => import('@/components/portfolio/Upload'),
},
}
</script>


Now we can upload multiple images at the same time and see the files in the console


Now that we can upload files, we need a place to store them. As we build a portfolio a good place good be on user's storage, in the /public folder with a /portfolio subfolder.

First we need to retrieve user's storage. The simple way to do that is using query-ldflex

The best way i know to use query-ldflex is to add it in /public/index.html with this two script tags at the end the head tag. One for rdflib and one for ldflex-query

/public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="https://cdn.jsdelivr.net/npm/solid-auth-client@2.3.0/dist-lib/solid-auth-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rdflib/dist/rdflib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@solid/query-ldflex/dist/solid-query-ldflex.rdflib.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

PLEASE REFRESH NOW THE WEBPAGE TO BE SURE THAT THESE LIBS ARE LOADED.

At this stage there is a message console saying that multiple versions of solid-auth-client are loaded, but this is not blockant.

Now we can acces ldflex everywhere with let ldflex = window.solid and ldflex automaticaly retrieve solid-auth-client logged user, providing us a simple way to get user's storage with let storage = await ldflex.data.user.storage. So our /src/components/portfolio/Upload.vue can look like this.

/src/components/portfolio/Upload.vue

<template>
<div class="container">
<b-form-file multiple
accept="image/*"
v-model="files"
:state="Boolean(files)"
placeholder="Choose a file or drop it here..."
drop-placeholder="Drop file here..."
@input="onInput"
></b-form-file>
</div>
</template>

<script>
let ldflex = window.solid

export default {
name: 'Upload',
data() {
return {
files: null
}
},
methods: {
async onInput(files) {
console.log(files)
let storage = await ldflex.data.user.storage
let path = `${storage}`+'public/portfolio/'
console.log(path)
}
}
}
</script>


Now we've got our files and a path to store them (see in the console)

-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

Storing the files on the POD

With this code, we know where to store our image files... Quite pretty good...

So let's now use the amazing @jeffz solid-file-client to store them of the user's POD at the desired path...

we can install the lib with

npm install --save solid-file-client

and use it with

import FC from 'solid-file-client'
const fc = new FC( window.solid.auth )

So, our /src/componenets/portfolio/Upload.vue now becomes something like that :

/src/componenets/portfolio/Upload.vue

<template>
<div class="container">
<b-form-file multiple
accept="image/*"
v-model="files"
:state="Boolean(files)"
placeholder="Choose a file or drop it here..."
drop-placeholder="Drop file here..."
@input="onInput"
></b-form-file>
</div>
</template>

<script>
let ldflex = window.solid
import FC from 'solid-file-client'
const fc = new FC( window.solid.auth )
export default {
name: 'Upload', data() {
return {
files: null
}
},
methods: {
async onInput(files) {
console.log(files)
let storage = await ldflex.data.user.storage
let path = `${storage}`+'public/portfolio/'
console.log(path)
await files.forEach(async function(f) {
console.log(f)
let uri = f.webkitRelativePath.length > 0 ? path+f.webkitRelativePath : path+f.name
console.log(encodeURI(uri))
await fc.createFile(encodeURI(uri), f, f.type)
})
}
}
}
</script>

AT THIS POINT, TRYING THIS CODE COULD THROW YOU AN ERROR, as your app located at http://localhost:8080 has not been declared as a "trusted app" on your POD.

to fix this issue, you should add http://localhost:8080 as a 'trusted app' in the "Preferences" tab of your POD. and give it at least 'Read', 'Write' and 'Append' access modes.

Once this is done you can try your app to upload images :

And you should find your picture in the /public/portfolio/ folder of your POD

END OF PART ONE

Identifier
https://spoggy-test6.solidcommunity.net/public/blog/portfolio.html
Modified
Derived From
https://scenaristeur.github.io/solid-vue-panes/dokieli/dokieli.html
Derived On
Language
English
Notifications Inbox
https://spoggy-test6.solidcommunity.net/inbox/
Annotation Service
https://spoggy-test6.solidcommunity.net/public/blog/annotations