This repository has been archived by the owner on Jul 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
331 lines (276 loc) · 10.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/**
* Copyright 2017 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
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.
*/
import objectAssign from 'object-assign'
Object.assign = Object.assign || objectAssign
/**
* Helper to figure out what type of dat.Controller we are modifying
*/
const getControllerType = (controller) => {
if (controller.__li.classList.contains('color')) {
return 'color'
} else {
const type = typeof controller.getValue()
if (type === 'number' || type === 'boolean') {
return type
} else {
return 'option'
}
}
}
const map = (value, inMin, inMax, outMin, outMax) => {
return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
}
/**
* A map of each controller type to its method handler name
*/
const controllerMap = {
'color': 'handleColorControl',
'boolean': 'handleBooleanControl',
'number': 'handleNumberControl',
'option': 'handleOptionControl'
}
/**
* Root Firebase reference that contains all the associated gui params
*/
const DEFAULT_ROOT_DB = "things/"
/**
* dat.fire
*
* Connecting and updating dat.gui interfaces via Firebase Realtime Database
*
* @author Anthony Tripaldi @ Google Creative Lab
*/
export default class DatFire {
/**
* Create an instance of dat.fire.
*
* Once created, initialize it with {@link init(gui, controllers, params)}
*
* @param database initialized and configured instance of Firebase.database()
*/
constructor(database) {
this.database = database
this.controllers = []
this.currentControllerIndex = -1
this.handleValueChange = this.handleValueChange.bind(this)
}
/**
* Initialize dat.fire by connecting our controllers to Firebase database References.
*
* dat.fire has two primary ways of initializing. If no <code>controllers</code> parameter is passed, dat.fire will
* automatically crab all controllers from the main <code>dat.GUI</code> object, checking all sub folders in the process.
*
* If you don't want to use every controller, pass an array of initialized dat.GUI.Controller's:
* <code>
* // create controller somewhere
* var guiSpeed = simulatorGui.add(settings, 'speed', 0, 3);
* ...
*
* // initialize with controllers
* datFire.init(gui, [guiSpeed, ...])
* </code>
* Both methods will then create Firebase reference value listeners for each Controller.property name, so the above example
* will listen for a reference value change on "things/speed" since we also pre-pend a configurable root database ref.
*
* Additional parameters are available for simple controls. If you pass <code>{ "usePrevNext": true }</code> then
* dat.fire won't add reference listeners for every controller, but rather assumes you only have 3 inputs, next, prev, and value.
*
* For example, with Android Things, you can set up two buttons and a dial, attach them to a Firebase Database at things/next,
* things/prev, and things/value, and dat.fire will automatically scroll through all the possible controllers of your
* dat.gui instance, controlling them via one slider or dial.
*
* If you pass <code>{ "simpleGui": true }</code> dat.fire will remove dat.gui from the screen entirely, only showing
* the currently modified controller, ie, as you push a physical slider for speed, that will be the only controller onscreen.
*
* This is best for installations. Check out the example used at Google I/O at http://www.androidexperiments.com
*
* @param {dat.GUI} gui Initialized and completed dat.GUI instance. Only call this after you are finished adding controllers.
* @param {Array} [controllers] Optional array of specific controllers to be added to dat.fire. If not present, we'll automatically add all controllers in our passed GUI instance.
* @param {Object} [params]
* @param {string} [params.dbRef] the top-level database ref in your Firebase schema.
* @param {string} [params.nextRef] firebase ref for next button
* @param {string} [params.prevRef] firebase ref for prev button
* @param {string} [params.valueRef] firebase ref for dial or slider that will update the currently selected controller
* @param {boolean} [params.usePrevNext] dat.fire will only expect to use a single dial with two buttons for iterating through an arbitrarily sized list of controllers
* @param {boolean} [params.simpleGui] "Installation Mode" - whether or not we want to auto-hide dat.gui and only show items that are currently being updated.
*/
init(gui, controllers, params) {
this.gui = gui
this.handleParams(params)
if (controllers && Array.isArray(controllers)) {
this.addControllers(controllers)
} else {
this.addAllControllersFromGui()
}
if (this.params.usePrevNext) {
this.initPrevNext()
} else {
this.controllers.forEach((ctrlObj) => {
this.database.ref(this.params.dbRef + ctrlObj.controller.property)
.on('value', this.handleValueChange)
})
}
}
initPrevNext() {
this.handlePrev = this.handlePrev.bind(this)
this.handleNext = this.handleNext.bind(this)
this.database.ref(this.params.dbRef + this.params.prevRef)
.on('value', this.handlePrev)
this.database.ref(this.params.dbRef + this.params.nextRef)
.on('value', this.handleNext)
this.database.ref(this.params.dbRef + this.params.valueRef)
.on('value', this.handleValueChange)
this.handleNext(true)
}
handleParams(params) {
this.params = params = Object.assign({}, DatFire.getDefaultParams(), params)
if(this.params.simpleGui) {
this.setupSimpleGui();
}
}
handlePrev(prev) {
prev = prev.val ? prev.val() : prev
if (prev) {
this.currentControllerIndex--;
if (this.currentControllerIndex < 0 ) {
this.currentControllerIndex = this.controllers.length - 1;
}
if (this.currentController) {
this.removeBackground(this.currentController.getParent())
}
this.currentController = this.controllers[this.currentControllerIndex]
this.addBackground(this.currentController.getParent())
}
}
handleNext(next) {
next = next.val ? next.val() : next
if (next) {
this.currentControllerIndex++;
if (this.currentControllerIndex >= this.controllers.length) {
this.currentControllerIndex = 0
}
if (this.currentController) {
this.removeBackground(this.currentController)
}
this.currentController = this.controllers[this.currentControllerIndex]
this.addBackground(this.currentController)
}
}
handleValueChange(val) {
if (this.controllers.length && val) {
this.currentController = this.getControllerByKey(val.key)
if(this.params.simpleGui) {
this.currentController.show()
}
const type = getControllerType(this.currentController)
this[controllerMap[type]] && this[controllerMap[type]](val.val())
}
}
handleNumberControl(dialValue) {
this.currentController.setValue(
map(dialValue, 0, 1, this.currentController.__min, this.currentController.__max)
)
}
handleBooleanControl(val) {
if(val > .5)
this.currentController.setValue(true)
else
this.currentController.setValue(false)
}
handleColorControl(val) {
if(val) {
// based on the code at https://github.com/dataarts/dat.gui/blob/master/src/dat/controllers/ColorController.js#L234
// since setting value via setValue on its own doesn't do anything.
// also need to explicitly set .s & .v since apparently, even in normal dat.gui, changing hue first doesn't
// affect the color until you've clicked once on saturation field, so doing that here.
// TODO(atripaldi) find best way to make this configurable per-controller without changing dat.gui
this.currentController.__color.h = val * 360
this.currentController.__color.s = 1
this.currentController.__color.v = .5
this.currentController.setValue(this.currentController.__color.toOriginal())
}
}
handleOptionControl(val) {
this.currentController.__select.selectedIndex = Math.floor(val * this.currentController.__select.childNodes.length)
}
addAllControllersFromGui() {
if (this.gui.__controllers.length) {
this.addControllers(this.gui.__controllers)
}
for (let folder in this.gui.__folders) {
let controllers = this.gui.__folders[folder].__controllers
if (controllers.length)
this.addControllers(controllers)
}
}
addControllers(controllers) {
let mappedControllers = []
controllers.forEach((ctrl) => {
if(this.params.simpleGui) {
this.setFireTo(ctrl)
}
// easy parental access
ctrl.getParent = () => ctrl.domElement.parentElement.parentElement
mappedControllers.push({'key': ctrl.property, 'controller':ctrl})
}, this)
this.controllers = this.controllers.concat(mappedControllers)
}
/**
* Adds additional simpleGui methods and properties to our controllers
*/
setFireTo(controller) {
controller.timeout = -1
controller.hide = () => {
this.fireList.removeChild(controller.getParent())
controller.timeout = -1
}
controller.show = () => {
this.fireList.appendChild(controller.getParent())
if(controller.timeout !== -1) {
clearTimeout(controller.timeout)
controller.timeout = -1
}
controller.timeout = setTimeout(() => {controller.hide()}, 2000)
}
}
getControllerByKey(key) {
for(let i = 0; i < this.controllers.length; i++) {
if(this.controllers[i].key === key)
return this.controllers[i].controller
}
}
removeBackground(parent) {
parent.style.backgroundColor = ""
}
addBackground(parent) {
parent.style.backgroundColor = "#555555"
}
setupSimpleGui() {
this.gui.close()
// create our new list for holding things
this.fireList = document.createElement('ul')
this.gui.domElement.appendChild(this.fireList)
// get rid of close button
this.gui.__closeButton.style.display = 'none'
}
static getDefaultParams() {
return {
dbRef: DEFAULT_ROOT_DB,
nextRef: "next",
prevRef: "prev",
valueRef: "value",
usePrevNext: false,
simpleGui: false
}
}
}