From 8d946b847475a4a470f3975ebba8d42440856edc Mon Sep 17 00:00:00 2001 From: Shawn Murphy Date: Tue, 18 Sep 2018 13:30:39 +0200 Subject: [PATCH] make class picker respect language path changes --- lib/huviz.js | 135 ++++++++++++++++++++++++++++++++++++------ src/gclui.coffee | 9 ++- src/huviz.coffee | 2 + src/treepicker.coffee | 86 ++++++++++++++++++++++----- 4 files changed, 199 insertions(+), 33 deletions(-) diff --git a/lib/huviz.js b/lib/huviz.js index e5450f15b..9dadf8cdc 100644 --- a/lib/huviz.js +++ b/lib/huviz.js @@ -2265,14 +2265,21 @@ OnceRunner.prototype.makeWrapper = function(callback) { return this.recolor_edges(); }; + CommandController.prototype.resort_pickers = function() { + var _ref; + if ((_ref = this.taxon_picker) != null) { + _ref.resort_recursively(); + } + }; + CommandController.prototype.build_predicate_picker = function(label) { - var id, title, where; + var extra_classes, id, needs_expander, sort_by_label, squash_case, title, where; id = 'predicates'; title = "Medium color: all edges shown -- click to show none\n" + "Faint color: no edges are shown -- click to show all\n" + "Stripey color: some edges shown -- click to show all\n" + "Hidden: no edges among the selected nodes"; where = (label != null) && this.control_label(label, this.comdiv, title) || this.comdiv; this.predicatebox = where.append('div').classed('container', true).attr('id', id); this.predicates_ignored = []; - this.predicate_picker = new ColoredTreePicker(this.predicatebox, 'anything', [], true); + this.predicate_picker = new ColoredTreePicker(this.predicatebox, 'anything', (extra_classes = []), (needs_expander = true), (sort_by_label = true), (squash_case = true)); this.predicate_hierarchy = { 'anything': ['anything'] }; @@ -2337,13 +2344,13 @@ OnceRunner.prototype.makeWrapper = function(callback) { }; CommandController.prototype.build_taxon_picker = function(label, where) { - var id, title; + var extra_classes, id, needs_expander, sort_by_label, squash_case, title; id = 'classes'; title = "Medium color: all nodes are selected -- click to select none\n" + "Faint color: no nodes are selected -- click to select all\n" + "Stripey color: some nodes are selected -- click to select all\n"; where = (label != null) && this.control_label(label, where, title) || this.comdiv; this.taxon_box = where.append('div').classed('container', true).attr('id', id); this.taxon_box.attr('style', 'vertical-align:top'); - this.taxon_picker = new ColoredTreePicker(this.taxon_box, 'Thing', [], true); + this.taxon_picker = new ColoredTreePicker(this.taxon_box, 'Thing', (extra_classes = []), (needs_expander = true), (sort_by_label = true), (squash_case = true)); this.taxon_picker.click_listener = this.on_taxon_clicked; this.taxon_picker.hover_listener = this.on_taxon_hovered; this.taxon_picker.show_tree(this.hierarchy, this.taxon_box); @@ -8835,7 +8842,7 @@ OnceRunner.prototype.makeWrapper = function(callback) { }; Huviz.prototype.on_change_language_path = function(new_val, old_val) { - var e; + var e, _ref; try { MultiString.set_langpath(new_val); } catch (_error) { @@ -8846,7 +8853,10 @@ OnceRunner.prototype.makeWrapper = function(callback) { } if (this.shelved_set) { this.shelved_set.resort(); - return this.discarded_set.resort(); + this.discarded_set.resort(); + } + if ((_ref = this.gclui) != null) { + _ref.resort_pickers(); } }; @@ -10816,6 +10826,15 @@ Build and control a hierarchic menu of arbitrarily nested divs looking like: * On the other hand, branches in the tree which are empty are hidden. * Clicking uncollapsed branches cycles just their selectedness. * Clicking collapsed branches cycles the selectedness of them and their children. + +*
a container holds one or more contents +*
a content (ie a node such as THING) may have a container for it kids +* so the CONTENT with id=Thing is within the root CONTAINER + and the Thing CONTENT itself holds a CONTAINER with the child CONTENTS of its subclasses + +Possible Bug: it appears that
has a redundant child + which looks like
. + It is unclear why this is needed. Containers should not directly hold containers. */ (function() { @@ -10825,19 +10844,24 @@ Build and control a hierarchic menu of arbitrarily nested divs looking like: uniquer = require("uniquer").uniquer; TreePicker = (function() { - function TreePicker(elem, root, extra_classes, needs_expander) { + function TreePicker(elem, root, extra_classes, needs_expander, sort_by_label) { this.elem = elem; this.needs_expander = needs_expander; + this.sort_by_label = sort_by_label; this.onChangeState = __bind(this.onChangeState, this); this.handle_click = __bind(this.handle_click, this); this.click_handler = __bind(this.click_handler, this); if (extra_classes != null) { this.extra_classes = extra_classes; } - this.id_to_elem = { - root: elem - }; - this.id_to_elem[root] = elem; + if (this.sort_by_label == null) { + this.sort_by_label = true; + } + if (this.squash_case_during_sort == null) { + this.squash_case_during_sort = true; + } + this.id_to_elem = {}; + this.id_to_elem['/'] = elem; this.ids_in_arrival_order = [root]; this.id_is_abstract = {}; this.id_is_collapsed = {}; @@ -10845,14 +10869,18 @@ Build and control a hierarchic menu of arbitrarily nested divs looking like: "true": {}, "false": {} }; - this.id_to_parent = {}; - this.id_to_children = {}; + this.id_to_parent = { + root: '/' + }; + this.id_to_children = { + '/': [root] + }; this.id_to_payload_collapsed = {}; this.id_to_payload_expanded = {}; this.id_to_name = {}; this.set_abstract(root); + this.set_abstract('/'); this.set_abstract('root'); - console.log('shield', this.shield); } TreePicker.prototype.get_my_id = function() { @@ -10901,15 +10929,88 @@ Build and control a hierarchic menu of arbitrarily nested divs looking like: return uniquer(uri); }; + TreePicker.prototype.get_childrens_ids = function(parent_id) { + if (parent_id == null) { + parent_id = '/'; + } + return this.id_to_children[parent_id] || []; + }; + + TreePicker.prototype.get_container_elem_within_id = function(an_id) { + var content_elem; + content_elem = this.id_to_elem[an_id][0][0]; + return content_elem.querySelector('.container'); + }; + + TreePicker.prototype.resort_recursively = function(an_id) { + var child_elem, child_id, container_elem, elem, kids_ids, sort_by_first_item, val, val_elem, val_elem_pairs, _i, _j, _len, _len1, _results; + if (an_id == null) { + an_id = '/'; + } + kids_ids = this.get_childrens_ids(an_id); + if (!kids_ids || !kids_ids.length) { + return; + } + val_elem_pairs = []; + sort_by_first_item = function(a, b) { + return a[0].localeCompare(b[0]); + }; + for (_i = 0, _len = kids_ids.length; _i < _len; _i++) { + child_id = kids_ids[_i]; + this.resort_recursively(child_id); + val = this.get_comparison_value(child_id, this.id_to_name[child_id]); + child_elem = this.id_to_elem[child_id][0][0]; + this.update_label_for_node(child_id, child_elem); + val_elem_pairs.push([val, child_elem]); + } + val_elem_pairs.sort(sort_by_first_item); + container_elem = this.get_container_elem_within_id(an_id); + if (!container_elem) { + throw "no container_elem"; + } + _results = []; + for (_j = 0, _len1 = val_elem_pairs.length; _j < _len1; _j++) { + val_elem = val_elem_pairs[_j]; + elem = val_elem[1]; + _results.push(container_elem.appendChild(elem)); + } + return _results; + }; + + TreePicker.prototype.update_label_for_node = function(node_id, node_elem) { + var label_elem; + if (node_elem == null) { + node_elem = this.id_to_elem[node_id]; + } + label_elem = node_elem.querySelector('p.treepicker-label'); + if (label_elem != null) { + return label_elem.textContent = this.id_to_name[node_id]; + } + }; + + TreePicker.prototype.get_comparison_value = function(node_id, label) { + var this_term; + if (this.sort_by_name) { + this_term = label || node_id; + } else { + this_term = node_id; + } + if (this.squash_case_during_sort === true) { + this_term = this_term.toLowerCase(); + } + return this_term; + }; + TreePicker.prototype.add_alphabetically = function(i_am_in, node_id, label) { - var container, elem, elem_lower, label_lower, _i, _len, _ref; + var container, elem, label_lower, other_term, this_term, _i, _len, _ref; label_lower = label.toLowerCase(); container = i_am_in[0][0]; + this_term = this.get_comparison_value(node_id, label); _ref = container.children; for (_i = 0, _len = _ref.length; _i < _len; _i++) { elem = _ref[_i]; - elem_lower = (this.id_to_name[elem.id] || elem.id).toLowerCase(); - if (elem_lower > label_lower) { + other_term = this.get_comparison_value(elem.id, this.id_to_name[elem.id]); + if (other_term > this_term) { return this.add_to_elem_before(i_am_in, node_id, "#" + elem.id, label); } } diff --git a/src/gclui.coffee b/src/gclui.coffee index a08ce72a5..0587a3758 100644 --- a/src/gclui.coffee +++ b/src/gclui.coffee @@ -156,6 +156,11 @@ class CommandController recolor_edges_and_predicates: (evt) => @predicate_picker.recolor_now() @recolor_edges() # FIXME should only really be run after the predicate set has settled for some amount of time + resort_pickers: -> + @taxon_picker?.resort_recursively() + #@predicate_picker?.resort_recursively() + #@set_picker?.resort_recursively() + return build_predicate_picker: (label) -> id = 'predicates' title = @@ -167,7 +172,7 @@ class CommandController @predicatebox = where.append('div').classed('container',true).attr('id',id) #@predicatebox.attr('class','scrolling') @predicates_ignored = [] - @predicate_picker = new ColoredTreePicker(@predicatebox,'anything',[],true) + @predicate_picker = new ColoredTreePicker(@predicatebox,'anything',(extra_classes=[]), (needs_expander=true), (sort_by_label=true), (squash_case=true)) @predicate_hierarchy = {'anything':['anything']} # FIXME Why is show_tree being called four times per node? @predicate_picker.click_listener = @on_predicate_clicked @@ -213,7 +218,7 @@ class CommandController .attr('id',id) @taxon_box.attr('style','vertical-align:top') # http://en.wikipedia.org/wiki/Taxon - @taxon_picker = new ColoredTreePicker(@taxon_box,'Thing',[],true) + @taxon_picker = new ColoredTreePicker(@taxon_box,'Thing', (extra_classes=[]), (needs_expander=true), (sort_by_label=true), (squash_case=true)) @taxon_picker.click_listener = @on_taxon_clicked @taxon_picker.hover_listener = @on_taxon_hovered @taxon_picker.show_tree(@hierarchy,@taxon_box) diff --git a/src/huviz.coffee b/src/huviz.coffee index d19c4b165..8c03d22fe 100644 --- a/src/huviz.coffee +++ b/src/huviz.coffee @@ -4090,6 +4090,8 @@ class Huviz if @shelved_set @shelved_set.resort() @discarded_set.resort() + @gclui?.resort_pickers() + return on_change_color_nodes_as_pies: (new_val, old_val) -> # TODO why this == window ?? @color_nodes_as_pies = new_val diff --git a/src/treepicker.coffee b/src/treepicker.coffee index 2cba894e0..ab72105bb 100644 --- a/src/treepicker.coffee +++ b/src/treepicker.coffee @@ -16,33 +16,43 @@ Build and control a hierarchic menu of arbitrarily nested divs looking like: * On the other hand, branches in the tree which are empty are hidden. * Clicking uncollapsed branches cycles just their selectedness. * Clicking collapsed branches cycles the selectedness of them and their children. + +*
a container holds one or more contents +*
a content (ie a node such as THING) may have a container for it kids +* so the CONTENT with id=Thing is within the root CONTAINER + and the Thing CONTENT itself holds a CONTAINER with the child CONTENTS of its subclasses + +Possible Bug: it appears that
has a redundant child + which looks like
. + It is unclear why this is needed. Containers should not directly hold containers. ### uniquer = require("uniquer").uniquer class TreePicker - constructor: (@elem, root, extra_classes, @needs_expander) -> + constructor: (@elem, root, extra_classes, @needs_expander, @sort_by_label) -> if extra_classes? @extra_classes = extra_classes - @id_to_elem = {root:elem} # FIXME remove root - @id_to_elem[root] = elem + if not @sort_by_label? + @sort_by_label = true + if not @squash_case_during_sort? + @squash_case_during_sort = true + @id_to_elem = {} + @id_to_elem['/'] = elem @ids_in_arrival_order = [root] @id_is_abstract = {} @id_is_collapsed = {} @id_to_state = true: {} false: {} - @id_to_parent = {} - @id_to_children = {} + @id_to_parent = {root: '/'} + @id_to_children = {'/': [root]} @id_to_payload_collapsed = {} @id_to_payload_expanded = {} @id_to_name = {} - @set_abstract(root) + @set_abstract(root) # FIXME is this needed? + @set_abstract('/') @set_abstract('root') # FIXME duplication?!? - # - #@shield.classed('shield') - console.log('shield', @shield) - #debugger get_my_id: () -> @elem.attr("id") shield: -> @@ -65,17 +75,61 @@ class TreePicker @id_is_abstract[id] = true get_abstract_count: -> return Object.keys(@id_is_abstract).length - is_abstract: (id) -> + is_abstract: (id) -> # ie class has no direct instances but maybe subclasses tmp = @id_is_abstract[id] tmp? and tmp uri_to_js_id: (uri) -> uniquer(uri) + get_childrens_ids: (parent_id) -> + parent_id ?= '/' # if no parent indicated, return root's kids + return @id_to_children[parent_id] or [] + get_container_elem_within_id: (an_id) -> + # the div with class='container' holding class='contents' divs + content_elem = @id_to_elem[an_id][0][0] + return content_elem.querySelector('.container') + resort_recursively: (an_id) -> + an_id ?= '/' # if an_id not provided, then sort the root + kids_ids = @get_childrens_ids(an_id) + if not kids_ids or not kids_ids.length + return + val_elem_pairs = [] + sort_by_first_item = (a, b) -> + return a[0].localeCompare(b[0]) + for child_id in kids_ids + @resort_recursively(child_id) + val = @get_comparison_value(child_id, @id_to_name[child_id]) + child_elem = @id_to_elem[child_id][0][0] + @update_label_for_node(child_id, child_elem) + val_elem_pairs.push([val, child_elem]) + val_elem_pairs.sort(sort_by_first_item) + container_elem = @get_container_elem_within_id(an_id) + if not container_elem + throw "no container_elem" + for val_elem in val_elem_pairs + elem = val_elem[1] + container_elem.appendChild(elem) + update_label_for_node: (node_id, node_elem) -> # passing node_elem is optional + # This takes the current value of @id_to_name[node_id] and displays it in the HTML. + # Why? Because the label might be a MultiString whose language might have changed. + node_elem ?= @id_to_elem[node_id] # look up node_elem if it is not passed in + label_elem = node_elem.querySelector('p.treepicker-label') + if label_elem? + label_elem.textContent = @id_to_name[node_id] + get_comparison_value: (node_id, label) -> + if @sort_by_name + this_term = (label or node_id) + else + this_term = node_id + if @squash_case_during_sort is true # expose this as a setting + this_term = this_term.toLowerCase() + return this_term add_alphabetically: (i_am_in, node_id, label) -> label_lower = label.toLowerCase() container = i_am_in[0][0] + this_term = @get_comparison_value(node_id, label) for elem in container.children - elem_lower = (@id_to_name[elem.id] or elem.id).toLowerCase() - if (elem_lower > label_lower) + other_term = @get_comparison_value(elem.id, @id_to_name[elem.id]) + if (other_term > this_term) return @add_to_elem_before(i_am_in, node_id, "#"+elem.id, label) # fall through and append if it comes before nothing @add_to_elem_before(i_am_in, node_id, undefined, label) @@ -83,12 +137,16 @@ class TreePicker i_am_in.insert('div', before). # insert just appends if before is undef attr('class','contents'). attr('id',node_id) - show_tree: (tree,i_am_in,listener,top) -> + show_tree: (tree, i_am_in, listener, top) -> # http://stackoverflow.com/questions/14511872 top = not top? or top for node_id,rest of tree label = rest[0] #label = "┗ " + rest[0] + + # FIXME the creation of a node in the tree should be extracted into a method + # rather than being spread across this one and add_alphabetically. + # Setting @id_to_elem[node_id] should be in the new method contents_of_me = @add_alphabetically(i_am_in, node_id, label) @id_to_elem[node_id] = contents_of_me msg = "show_tree() just did @id_to_elem[#{node_id}] = contents_of_me"