UNPKG

10.4 kBJavaScriptView Raw
1//
2// BlockV AG. Copyright (c) 2018, all rights reserved.
3//
4// Licensed under the BlockV SDK License (the "License"); you may not use this file or
5// the BlockV SDK except in compliance with the License accompanying it. Unless
6// required by applicable law or agreed to in writing, the BlockV SDK distributed under
7// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
8// ANY KIND, either express or implied. See the License for the specific language
9// governing permissions and limitations under the License.
10//
11/* eslint-disable no-underscore-dangle */
12/* eslint-disable prefer-const */
13/* eslint-disable no-console */
14import FaceSelection from './FaceSelection'
15import ProgressImage from './faces/ProgressImage'
16import ImageFace from './faces/ImageFace'
17import ImagePolicy from './faces/ImagePolicy'
18import LayeredImage from './faces/LayeredImage'
19import BaseWebFace from './faces/WebFace/BaseWebFace'
20
21// list registered faces
22let registeredFace = {
23 'native://image': ImageFace,
24 'native://progress-image-overlay': ProgressImage,
25 'native://image-policy': ImagePolicy,
26 'native://layered-image': LayeredImage
27}
28
29export default class VatomView {
30 constructor (bv, vAtom, FSP, config) {
31 this.blockv = bv
32 this.vatomObj = vAtom
33 this.fsp = FSP || FaceSelection.Icon
34 this.config = config || {}
35 // eslint-disable-next-line
36 this._currentFace = null
37 this.onVatomUpdated = this.onVatomUpdated.bind(this)
38 this.region = this.blockv.dataPool.region('inventory')
39 this.region.addEventListener('object.updated', this.onVatomUpdated)
40 // create a default view with a div container
41 // eslint-disable-next-line
42 this.element = document.createElement('div')
43 this.element.style.position = 'relative'
44 this.element.style.width = this.config.width || '64px'
45 this.element.style.height = this.config.height || '64px'
46
47 // create loader
48 this.createLoader = this.config.loader || function () {
49 let css = '.spinner {margin: 0px auto;width: 70px;text-align: center; margin-top: -50%;}'
50 css += '.spinner > div {width: 12px;height: 12px;margin: 0px 3px;border-radius: 100%;display: inline-block;-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;animation: sk-bouncedelay 1.4s infinite ease-in-out both;}'
51 css += '.spinner .bounce1 {-webkit-animation-delay: -0.32s;animation-delay: -0.32s;}'
52 css += '.spinner .bounce2 {-webkit-animation-delay: -0.16s;animation-delay: -0.16s;}'
53 css += '@-webkit-keyframes sk-bouncedelay {0%, 80%, 100% { -webkit-transform: scale(0) }40% { -webkit-transform: scale(1.0) }}'
54 css += '@keyframes sk-bouncedelay {0%, 80%, 100% {-webkit-transform: scale(0);transform: scale(0);} 40% {-webkit-transform: scale(1.0);transform: scale(1.0);}}'
55
56 let head = document.head || document.getElementsByTagName('head')[0]
57 let style = document.createElement('style')
58 head.appendChild(style)
59
60 style.type = 'text/css'
61 if (style.styleSheet) {
62 // This is required for IE8 and below.
63 style.styleSheet.cssText = css
64 } else {
65 style.appendChild(document.createTextNode(css))
66 }
67
68 let loader = document.createElement('div')
69
70 loader.innerHTML = '<div class="spinner"><div class="bounce1" style="background-color: #333;"></div><div class="bounce2" style="background-color: #333;"></div><div class="bounce3" style="background-color: #333;"></div></div>'
71 return loader
72 }
73
74 this.createErrorView = this.config.errorView || function (bvi, v, err) {
75 let con = document.createElement('div')
76 const rs = v.properties.resources.find(r => r.name === 'ActivatedImage')
77 const du = rs && bvi.UserManager.encodeAssetProvider(rs.value.value)
78 con.style.backgroundSize = 'contain'
79 con.style.backgroundPosition = 'center'
80 con.style.backgroundRepeat = 'no-repeat'
81 con.style.backgroundImage = `url('${du}')`
82 con.style.width = '100%'
83 con.style.height = '100%'
84
85 let errorView = document.createElement('div')
86 errorView.style.cssText = 'position: absolute; top: 0px; right: 0px; padding-right: 5px; padding-top: 5px;'
87 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==" />'
88 errorView.addEventListener('click', e => alert(err.message), false)
89
90 con.appendChild(errorView)
91 return con
92 }
93
94 this.update()
95 }
96
97 update () {
98
99 // Check if ready to be displayed
100 if (!this.vatomObj)
101 return console.warn('No vAtom supplied')
102
103 // Notify current face it's being unloaded
104 if (this._currentFace && this._currentFace.onUnload) this._currentFace.onUnload()
105 this._currentFace = null
106
107 // Remove all views from our element
108 const view = this.element
109 while (view.firstChild) {
110 view.removeChild(view.firstChild)
111 }
112
113 // Load again
114 this.load()
115
116 }
117
118 load () {
119 // reset errorview and loader
120 if (this.loader && this.loader.parentNode) {
121 this.loader.parentNode.removeChild(this.loader)
122 }
123 if (this.errorView && this.errorView.parentNode) {
124 this.errorView.parentNode.removeChild(this.errorView)
125 }
126 this.loader = null
127 this.errorView = null
128
129 let rFace = null
130 Promise.resolve(() => null).then(() => {
131 // start the face selection procedure
132 const st = this.fsp(this.vatomObj)
133 if (!st)
134 throw new Error('No face found for this view mode.')
135 let FaceClass = null
136 // check if face is registered
137 const du = st.properties.display_url.toLowerCase()
138 let excludedFaces = this.config.excludedFaces
139
140 if (excludedFaces.includes(du)) {
141 throw new Error('This face is not allowed to run in this view mode. [excluded : ' + du + ']')
142 } else {
143 FaceClass = registeredFace[du]
144 }
145 // if there is no face registered in the array but we have a http link, show the web face
146 if (FaceClass === undefined && du.indexOf('http') !== -1) {
147 FaceClass = BaseWebFace
148 } else if (FaceClass === undefined) {
149 throw new Error('No Face Registered')
150 }
151 // create a new instance of the chosen face class and pass through the information
152 rFace = new FaceClass(this, this.vatomObj, st)
153 this._currentFace = rFace
154 // make rface opaque
155 rFace.element.style.opacity = 0
156
157 // add face to element
158 this.element.appendChild(rFace.element)
159
160 // add the loader
161 this.element.appendChild(this.loader = this.createLoader())
162
163 // check for error
164
165 // call rface.onload , wait for promise
166 return rFace.onLoad()
167 }).then(() => {
168 if (this.loader) {
169 this.element.removeChild(this.loader)
170 rFace.element.style.opacity = 1
171 }
172 }).catch((err) => {
173 console.warn('Error from catch', err)
174 // remove current face
175 this.element.appendChild(this.errorView = this.createErrorView(this.blockv, this.vatom, err))
176 if (rFace && rFace.element && rFace.element.parentNode) {
177 this.element.removeChild(rFace.element)
178 }
179 if (this.loader && this.loader.parentNode) {
180 this.element.removeChild(this.loader)
181 }
182 })
183 }
184
185 set vatom (vAtom) {
186 if (vAtom && vAtom.id === this.vatomObj.id) {
187 this.vatomObj.payload = vAtom.payload
188 if (this._currentFace) {
189 this._currentFace.onVatomUpdated()
190 }
191 } else if (vAtom) {
192 this.vatomObj = vAtom
193 this.update()
194 }
195 }
196
197 get vatom () {
198 return this.vatomObj
199 }
200
201 free () {
202
203 // Remove event listener
204 this.region.removeEventListener('object.updated', this.onVatomUpdated)
205
206 // Notify current face it's being unloaded
207 if (this._currentFace && this._currentFace.onUnload) this._currentFace.onUnload()
208 this._currentFace = null
209
210 // Remove all views from our element
211 const view = this.element
212 while (view.firstChild) {
213 view.removeChild(view.firstChild)
214 }
215
216 }
217
218 onVatomUpdated (id) {
219 // Stop if not our vatom
220 if (id !== this.vatomObj.id) {
221 return
222 }
223
224 // Fetch latest vatom from data pool
225 var vatom = this.region.getItem(id, false)
226 if (!vatom)
227 return console.warn('DataPool indicated that an updated vatom was available, but we were unable to fetch it.')
228
229 // Store new vatom and notify face
230 this.vatom = vatom
231 }
232
233 // register our own face
234 static registerFace (faceClass) {
235 registeredFace[faceClass.url.toLowerCase()] = faceClass
236 }
237}