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 :
- VueJs for the app/components architecture,
- bootstrap-vue for the UI,
- solid-auth-client for login/logout,
- query-ldflex for easily query POD resource,
- solid-file-client for dealing with files & folders
Nb: to simplify the code we volontary omit error handling, solid-auth trackSession (check session changes)...
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
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 :
- on left the architecture of your code
- 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
- the App.vue file implementing the router links 'Home' & 'About' that we can see in the above image
- the router links are managed in the /src/router/index.js mapping
to the /src/view/Home.vue viewHome - 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