@blockv/sdk
Version:
Allows web apps to display and interact with vatoms.
193 lines (171 loc) • 9.49 kB
JavaScript
//
// BlockV AG. Copyright (c) 2018, all rights reserved.
//
// Licensed under the BlockV SDK License (the "License"); you may not use this file or
// the BlockV SDK except in compliance with the License accompanying it. Unless
// required by applicable law or agreed to in writing, the BlockV SDK distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
// ANY KIND, either express or implied. See the License for the specific language
// governing permissions and limitations under the License.
//
/* eslint-disable no-underscore-dangle */
/* eslint-disable prefer-const */
/* eslint-disable no-console */
import FaceSelection from './FaceSelection'
import ProgressImage from './faces/ProgressImage'
import ImageFace from './faces/ImageFace'
import ImagePolicy from './faces/ImagePolicy'
import LayeredImage from './faces/LayeredImage'
import BaseWebFace from './faces/WebFace/BaseWebFace'
// list registered faces
let registeredFace = {
'native://image': ImageFace,
'native://progress-image-overlay': ProgressImage,
'native://image-policy': ImagePolicy,
'native://layered-image': LayeredImage
}
export default class VatomView {
constructor (bv, vAtom, FSP, config) {
this.blockv = bv
this.vatomObj = vAtom
this.fsp = FSP || FaceSelection.Icon
this.config = config || {}
// eslint-disable-next-line
this._currentFace = null
this.blockv.dataPool.region('inventory').addEventListener('object.updated', this.onVatomUpdated.bind(this))
// create a default view with a div container
// eslint-disable-next-line
this.element = document.createElement('div')
this.element.style.position = 'relative'
this.element.style.width = this.config.width || '64px'
this.element.style.height = this.config.height || '64px'
// create loader
this.createLoader = this.config.loader || function () {
let loader = document.createElement('div')
loader.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%;'
loader.innerHTML = '<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-double-ring"><circle cx="50" cy="50" ng-attr-r="{{config.radius}}" ng-attr-stroke-width="{{config.width}}" ng-attr-stroke="{{config.c1}}" ng-attr-stroke-dasharray="{{config.dasharray}}" fill="none" stroke-linecap="round" r="40" stroke-width="4" stroke="#456caa" stroke-dasharray="62.83185307179586 62.83185307179586" transform="rotate(305.714 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform></circle><circle cx="50" cy="50" ng-attr-r="{{config.radius2}}" ng-attr-stroke-width="{{config.width}}" ng-attr-stroke="{{config.c2}}" ng-attr-stroke-dasharray="{{config.dasharray2}}" ng-attr-stroke-dashoffset="{{config.dashoffset2}}" fill="none" stroke-linecap="round" r="35" stroke-width="4" stroke="#88a2ce" stroke-dasharray="54.97787143782138 54.97787143782138" stroke-dashoffset="54.97787143782138" transform="rotate(-305.714 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;-360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform></circle></svg>'
return loader
}
this.createErrorView = this.config.errorView || function (bvi, v, err) {
let con = document.createElement('div')
const rs = v.properties.resources.find(r => r.name === 'ActivatedImage')
const du = rs && bvi.UserManager.encodeAssetProvider(rs.value.value)
con.style.backgroundSize = 'contain'
con.style.backgroundPosition = 'center'
con.style.backgroundRepeat = 'no-repeat'
con.style.backgroundImage = `url('${du}')`
con.style.width = '100%'
con.style.height = '100%'
let errorView = document.createElement('div')
errorView.style.cssText = 'position: absolute; top: 0px; right: 0px; padding-right: 5px; padding-top: 5px;'
errorView.innerHTML = '<img width="20" height="20" src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQ5Ny40NzIgNDk3LjQ3MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDk3LjQ3MiA0OTcuNDcyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4Ij4KPGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4yNSAwIDAgLTEuMjUgMCA0NSkiPgoJPGc+CgkJPGc+CgkJCTxwYXRoIHN0eWxlPSJmaWxsOiNGRkNDNEQ7IiBkPSJNMjQuMzc0LTM1Ny44NTdjLTIwLjk1OCwwLTMwLjE5NywxNS4yMjMtMjAuNTQ4LDMzLjgyNkwxODEuNDIxLDE3LjkyOCAgICAgYzkuNjQ4LDE4LjYwMywyNS40NjMsMTguNjAzLDM1LjEyMywwTDM5NC4xNC0zMjQuMDMxYzkuNjcxLTE4LjYwMywwLjQyMS0zMy44MjYtMjAuNTQ4LTMzLjgyNkgyNC4zNzR6Ii8+CgkJCTxwYXRoIHN0eWxlPSJmaWxsOiMyMzFGMjA7IiBkPSJNMTczLjYwNS04MC45MjJjMCwxNC44MTQsMTAuOTM0LDIzLjk4NCwyNS4zOTUsMjMuOTg0YzE0LjEyLDAsMjUuNDA3LTkuNTEyLDI1LjQwNy0yMy45ODQgICAgIFYtMjE2Ljc1YzAtMTQuNDYxLTExLjI4Ny0yMy45ODQtMjUuNDA3LTIzLjk4NGMtMTQuNDYxLDAtMjUuMzk1LDkuMTgyLTI1LjM5NSwyMy45ODRWLTgwLjkyMnogTTE3MS40ODktMjg5LjA1NiAgICAgYzAsMTUuMTY3LDEyLjM0NSwyNy41MTEsMjcuNTExLDI3LjUxMWMxNS4xNjcsMCwyNy41MjMtMTIuMzQ1LDI3LjUyMy0yNy41MTFjMC0xNS4xNzgtMTIuMzU2LTI3LjUyMy0yNy41MjMtMjcuNTIzICAgICBDMTgzLjgzNC0zMTYuNTc5LDE3MS40ODktMzA0LjIzNCwxNzEuNDg5LTI4OS4wNTYiLz4KCQk8L2c+Cgk8L2c+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==" />'
errorView.addEventListener('click', e => alert(err.message), false)
con.appendChild(errorView)
return con
}
this.update()
}
update () {
// Check if ready to be displayed
if (!this.vatomObj) {
console.warn('No vAtom supplied')
} else {
this.free()
this.load()
}
}
load () {
// reset errorview and loader
if (this.loader && this.loader.parentNode) {
this.loader.parentNode.removeChild(this.loader)
}
if (this.errorView && this.errorView.parentNode) {
this.errorView.parentNode.removeChild(this.errorView)
}
this.loader = null
this.errorView = null
let rFace = null
Promise.resolve(() => null).then(() => {
// start the face selection procedure
const st = this.fsp(this.vatomObj)
let FaceClass = null
// check if face is registered
const du = st.properties.display_url.toLowerCase()
let excludedFaces = this.config.excludedFaces
if (excludedFaces.includes(du)) {
FaceClass = ImageFace
} else {
FaceClass = registeredFace[du]
}
// if there is no face registered in the array but we have a http link, show the web face
if (FaceClass === undefined && du.indexOf('http') !== -1) {
FaceClass = BaseWebFace
} else if (FaceClass === undefined) {
throw new Error('No Face Registered')
}
// create a new instance of the chosen face class and pass through the information
rFace = new FaceClass(this, this.vatomObj, st)
this._currentFace = rFace
// make rface opaque
rFace.element.style.opacity = 0
// add face to element
this.element.appendChild(rFace.element)
// add the loader
this.element.appendChild(this.loader = this.createLoader())
// check for error
// call rface.onload , wait for promise
return rFace.onLoad()
}).then(() => {
if (this.loader) {
this.element.removeChild(this.loader)
rFace.element.style.opacity = 1
}
}).catch((err) => {
console.warn('Error from catch', err)
// remove current face
this.element.appendChild(this.errorView = this.createErrorView(this.blockv, this.vatom, err))
if (rFace && rFace.element && rFace.element.parentNode) {
this.element.removeChild(rFace.element)
}
if (this.loader && this.loader.parentNode) {
this.element.removeChild(this.loader)
}
})
}
set vatom (vAtom) {
if (vAtom && vAtom.id === this.vatomObj.id) {
this.vatomObj.payload = vAtom.payload
if (this._currentFace) {
this._currentFace.onVatomUpdated()
}
} else if (vAtom) {
this.vatomObj = vAtom
this.update()
}
}
get vatom () {
return this.vatomObj
}
free () {
if (this._currentFace && this._currentFace.onUnload) {
this._currentFace.onUnload()
this.blockv.dataPool.region('inventory').removeEventListener('object.updated')
}
const view = this.element
while (view.firstChild) {
view.removeChild(view.firstChild)
}
}
onVatomUpdated (id) {
if (id !== this.vatomObj.id) {
return
}
if (this._currentFace && this._currentFace.onVatomUpdated) {
this._currentFace.onVatomUpdated()
}
}
// register our own face
static registerFace (faceClass) {
registeredFace[faceClass.url.toLowerCase()] = faceClass
}
}