Newer
Older
piotr.kupczyk@id.ethz.ch
committed
import _ from 'lodash'
import autoBind from 'auto-bind'
piotr.kupczyk@id.ethz.ch
committed
import actions from '@src/js/store/actions/actions.js'
piotr.kupczyk@id.ethz.ch
committed
export default class BrowserController {
doGetPage() {
throw 'Method not implemented'
piotr.kupczyk@id.ethz.ch
committed
}
doLoadNodes() {
throw 'Method not implemented'
piotr.kupczyk@id.ethz.ch
committed
}
doNodeAdd() {
piotr.kupczyk@id.ethz.ch
committed
throw 'Method not implemented'
}
piotr.kupczyk@id.ethz.ch
committed
doNodeRemove() {
piotr.kupczyk@id.ethz.ch
committed
throw 'Method not implemented'
}
piotr.kupczyk@id.ethz.ch
committed
doGetObservedModifications() {
throw 'Method not implemented'
}
constructor() {
autoBind(this)
this.loadedNodes = []
this.lastObjectModifications = {}
init(context) {
context.initState({
loaded: false,
filter: '',
piotr.kupczyk@id.ethz.ch
committed
usedFilter: '',
nodes: [],
selectedId: null,
selectedObject: null,
removeNodeDialogOpen: false
})
this.context = context
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
load() {
return this.doLoadNodes().then(loadedNodes => {
piotr.kupczyk@id.ethz.ch
committed
const { filter, nodes, selectedId, selectedObject } =
this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
let newNodes = this._createNodes(loadedNodes)
newNodes = this._filterNodes(newNodes, filter)
newNodes = this._setNodesExpanded(
newNodes,
this._getExpandedNodes(nodes),
true
piotr.kupczyk@id.ethz.ch
committed
)
piotr.kupczyk@id.ethz.ch
committed
newNodes = this._setNodesSelected(newNodes, selectedId, selectedObject)
this._sortNodes(newNodes)
this.loadedNodes = loadedNodes
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.context.setState({
piotr.kupczyk@id.ethz.ch
committed
loaded: true,
piotr.kupczyk@id.ethz.ch
committed
usedFilter: filter,
piotr.kupczyk@id.ethz.ch
committed
nodes: newNodes
piotr.kupczyk@id.ethz.ch
committed
})
})
}
piotr.kupczyk@id.ethz.ch
committed
refresh(fullObjectModifications) {
const observedModifications = this.doGetObservedModifications()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const getTimestamp = (modifications, type, operation) => {
return (
(modifications &&
modifications[type] &&
modifications[type][operation]) ||
0
)
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newObjectModifications = {}
let refresh = false
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
Object.keys(observedModifications).forEach(observedType => {
const observedOperations = observedModifications[observedType]
newObjectModifications[observedType] = {}
observedOperations.forEach(observedOperation => {
const timestamp = getTimestamp(
this.lastObjectModifications,
observedType,
observedOperation
)
const newTimestamp = getTimestamp(
fullObjectModifications,
observedType,
observedOperation
)
newObjectModifications[observedType][observedOperation] = Math.max(
timestamp,
newTimestamp
)
if (newTimestamp > timestamp) {
refresh = true
}
})
piotr.kupczyk@id.ethz.ch
committed
})
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.lastObjectModifications = newObjectModifications
if (refresh) {
this.load()
}
}
filterChange(newFilter) {
piotr.kupczyk@id.ethz.ch
committed
this.context.setState({
filter: newFilter
})
if (this.filterTimerId) {
clearTimeout(this.filterTimerId)
this.filterTimerId = null
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
this.filterTimerId = setTimeout(async () => {
const { filter, usedFilter, nodes, selectedId, selectedObject } =
this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
let initialNodes = null
if (filter.startsWith(usedFilter)) {
initialNodes = nodes
} else {
initialNodes = this.loadedNodes
}
let newNodes = this._createNodes(initialNodes)
newNodes = this._filterNodes(newNodes, newFilter)
newNodes = this._setNodesExpanded(
newNodes,
this._getParentNodes(newNodes),
true
)
newNodes = this._setNodesSelected(newNodes, selectedId, selectedObject)
this._sortNodes(newNodes)
this.context.setState({
usedFilter: filter,
nodes: newNodes
})
}, 200)
piotr.kupczyk@id.ethz.ch
committed
}
nodeExpand(nodeId) {
piotr.kupczyk@id.ethz.ch
committed
const { nodes } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newNodes = this._setNodesExpanded(nodes, { [nodeId]: nodeId }, true)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.context.setState({
piotr.kupczyk@id.ethz.ch
committed
nodes: newNodes
piotr.kupczyk@id.ethz.ch
committed
})
}
nodeCollapse(nodeId) {
piotr.kupczyk@id.ethz.ch
committed
const { nodes } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newNodes = this._setNodesExpanded(nodes, { [nodeId]: nodeId }, false)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.context.setState({
piotr.kupczyk@id.ethz.ch
committed
nodes: newNodes
piotr.kupczyk@id.ethz.ch
committed
})
}
nodeSelect(nodeId) {
piotr.kupczyk@id.ethz.ch
committed
const { nodes } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
let nodeObject = null
this._visitNodes(nodes, node => {
piotr.kupczyk@id.ethz.ch
committed
if (node.id === nodeId) {
piotr.kupczyk@id.ethz.ch
committed
nodeObject = node.object
piotr.kupczyk@id.ethz.ch
committed
}
})
piotr.kupczyk@id.ethz.ch
committed
if (nodeObject) {
this.context.dispatch(
actions.objectOpen(this.getPage(), nodeObject.type, nodeObject.id)
)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newNodes = this._setNodesSelected(nodes, nodeId, nodeObject)
this.context.setState({
nodes: newNodes,
selectedId: nodeId,
selectedObject: nodeObject
})
piotr.kupczyk@id.ethz.ch
committed
}
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
nodeAdd() {
if (!this.isAddNodeEnabled()) {
return
}
const selectedNode = this.getSelectedNode()
this.doNodeAdd(selectedNode)
}
nodeRemove() {
if (!this.isRemoveNodeEnabled()) {
return
}
this.context.setState({
removeNodeDialogOpen: true
})
}
nodeRemoveConfirm() {
const { removeNodeDialogOpen } = this.context.getState()
if (!removeNodeDialogOpen) {
return Promise.resolve()
}
const selectedNode = this.getSelectedNode()
return this.doNodeRemove(selectedNode).then(() => {
return this.context.setState({
removeNodeDialogOpen: false
})
})
}
nodeRemoveCancel() {
this.context.setState({
removeNodeDialogOpen: false
})
}
piotr.kupczyk@id.ethz.ch
committed
objectSelect(object) {
piotr.kupczyk@id.ethz.ch
committed
const { nodes } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
const newNodes = this._setNodesSelected(nodes, null, object)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this.context.setState({
nodes: newNodes,
selectedId: null,
selectedObject: object
piotr.kupczyk@id.ethz.ch
committed
})
}
getPage() {
return this.doGetPage()
}
piotr.kupczyk@id.ethz.ch
committed
getLoaded() {
piotr.kupczyk@id.ethz.ch
committed
const { loaded } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
return loaded
}
piotr.kupczyk@id.ethz.ch
committed
getFilter() {
piotr.kupczyk@id.ethz.ch
committed
const { filter } = this.context.getState()
piotr.kupczyk@id.ethz.ch
committed
return filter
}
getNodes() {
piotr.kupczyk@id.ethz.ch
committed
const { nodes } = this.context.getState()
return nodes
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
getSelectedNode() {
piotr.kupczyk@id.ethz.ch
committed
const { nodes, selectedId, selectedObject } = this.context.getState()
let selectedNode = null
piotr.kupczyk@id.ethz.ch
committed
this._visitNodes(nodes, node => {
if (
(selectedId && selectedId === node.id) ||
(selectedObject && _.isEqual(selectedObject, node.object))
) {
selectedNode = node
}
})
piotr.kupczyk@id.ethz.ch
committed
return selectedNode
}
isAddNodeEnabled() {
const selectedNode = this.getSelectedNode()
piotr.kupczyk@id.ethz.ch
committed
return selectedNode && selectedNode.canAdd
isRemoveNodeEnabled() {
const selectedNode = this.getSelectedNode()
piotr.kupczyk@id.ethz.ch
committed
return selectedNode && selectedNode.canRemove
}
isRemoveNodeDialogOpen() {
const { removeNodeDialogOpen } = this.context.getState()
return removeNodeDialogOpen
piotr.kupczyk@id.ethz.ch
committed
_createNodes = nodes => {
if (!nodes) {
return []
}
piotr.kupczyk@id.ethz.ch
committed
const newNodes = []
nodes.forEach(node => {
const newNode = {
...node,
selected: false,
expanded: false,
children: this._createNodes(node.children)
piotr.kupczyk@id.ethz.ch
committed
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
}
newNodes.push(newNode)
})
return newNodes
}
_getExpandedNodes(nodes) {
return this._visitNodes(
nodes,
(node, result) => {
if (node.expanded) {
result[node.id] = node.id
}
},
{}
)
}
_getParentNodes(nodes) {
return this._visitNodes(
nodes,
(node, result) => {
if (node.children && node.children.length > 0) {
result[node.id] = node.id
}
},
{}
)
}
_setNodesExpanded(nodes, nodeIds, expanded) {
return this._modifyNodes(nodes, node => {
if (nodeIds[node.id]) {
return {
...node,
expanded
}
} else {
return node
}
})
}
_setNodesSelected(nodes, selectedId, selectedObject) {
return this._modifyNodes(nodes, node => {
if (
(selectedId && selectedId === node.id) ||
(selectedObject && _.isEqual(selectedObject, node.object))
) {
return {
...node,
selected: true
}
} else if (node.selected) {
return {
...node,
selected: false
}
} else {
return node
}
})
}
piotr.kupczyk@id.ethz.ch
committed
_sortNodes = (nodes, level = 0) => {
if (!nodes) {
return
}
if (level > 0) {
nodes.sort((n1, n2) => {
return n1.text.localeCompare(n2.text)
})
}
nodes.forEach(node => {
this._sortNodes(node.children, level + 1)
})
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
_visitNodes = (nodes, visitFn, results = []) => {
if (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
visitFn(node, results)
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
this._visitNodes(node.children, visitFn, results)
}
}
return results
}
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
_filterNodes = (nodes, filter, ascendantMatches = false) => {
if (!nodes || filter.trim().length === 0) {
return nodes
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
let newNodes = []
nodes.forEach(node => {
const nodeMatches =
piotr.kupczyk@id.ethz.ch
committed
node.canMatchFilter &&
piotr.kupczyk@id.ethz.ch
committed
node.text &&
node.text.toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1
const newNode = {
...node
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
newNode.children = this._filterNodes(
node.children,
filter,
ascendantMatches || nodeMatches
)
if (
ascendantMatches ||
nodeMatches ||
(newNode.children && newNode.children.length > 0)
) {
newNodes.push(newNode)
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
})
piotr.kupczyk@id.ethz.ch
committed
piotr.kupczyk@id.ethz.ch
committed
return newNodes
piotr.kupczyk@id.ethz.ch
committed
}
piotr.kupczyk@id.ethz.ch
committed
_modifyNodes = (nodes, modifyFn) => {
if (!nodes) {
return nodes
}
let newNodes = []
let modified = false
nodes.forEach(node => {
let newNode = modifyFn(node)
const newChildren = this._modifyNodes(newNode.children, modifyFn)
piotr.kupczyk@id.ethz.ch
committed
if (newNode === node) {
if (newChildren !== node.children) {
newNode = {
...node,
children: newChildren
piotr.kupczyk@id.ethz.ch
committed
} else {
newNode.children = newChildren
}
newNodes.push(newNode)
piotr.kupczyk@id.ethz.ch
committed
if (newNode !== node) {
modified = true
piotr.kupczyk@id.ethz.ch
committed
})
piotr.kupczyk@id.ethz.ch
committed
return modified ? newNodes : nodes
}