From 3ae06034b88e7a9887a1c9fb2f5e623cd9469669 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 6 Apr 2015 11:43:16 +0300 Subject: client: property tab cleanup; resolve #396 --- src/client/model/types.js | 4 +- src/client/rz_core.js | 6 +- src/client/view/graph_view.js | 14 +-- src/client/view/internal.js | 14 --- src/client/view/item_info.js | 276 ++++++++++++++++++++++++++++++++++++++++++ src/client/view/link_info.js | 36 ------ src/client/view/node_info.js | 241 ------------------------------------ src/client/view/tab.js | 62 ---------- src/client/view/view.js | 12 -- 9 files changed, 285 insertions(+), 380 deletions(-) delete mode 100644 src/client/view/internal.js create mode 100644 src/client/view/item_info.js delete mode 100644 src/client/view/link_info.js delete mode 100644 src/client/view/node_info.js delete mode 100644 src/client/view/tab.js delete mode 100644 src/client/view/view.js (limited to 'src') diff --git a/src/client/model/types.js b/src/client/model/types.js index a30777e7..76959a83 100644 --- a/src/client/model/types.js +++ b/src/client/model/types.js @@ -73,8 +73,8 @@ return ( { nodetypes: nodetypes, type_attributes: function (type) { - util.assert(type[0] !== '_', 'invalid type name'); - return type_attributes[type_attributes.hasOwnProperty(type) ? type : '_defaults'].attributes; + util.assert(!type || type[0] !== '_', 'invalid type name'); + return type_attributes[!type && type_attributes.hasOwnProperty(type) ? type : '_defaults'].attributes; }, all_attributes: all_attributes, attribute_titles: attribute_titles, diff --git a/src/client/rz_core.js b/src/client/rz_core.js index 4115e4bc..cee60a29 100644 --- a/src/client/rz_core.js +++ b/src/client/rz_core.js @@ -1,7 +1,7 @@ "use strict" -define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/view', 'rz_observer', 'view/selection', 'rz_mesh', 'model/diff', "view/graph_view", 'view/svg_input'], -function($, d3, consts, rz_bus, util, model_graph, model_core, view_helpers, view, rz_observer, selection, rz_mesh, model_diff, graph_view, svg_input) { +define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/item_info', 'rz_observer', 'view/selection', 'rz_mesh', 'model/diff', "view/graph_view", 'view/svg_input'], +function($, d3, consts, rz_bus, util, model_graph, model_core, view_helpers, item_info, rz_observer, selection, rz_mesh, model_diff, graph_view, svg_input) { var addednodes = [], vis, @@ -41,7 +41,7 @@ function svg_click_handler(e) { } svgInput.hide(); selection.clear(); - view.hide(); + item_info.hide(); update_view__graph(false); } diff --git a/src/client/view/graph_view.js b/src/client/view/graph_view.js index 234ed976..3a3bb6a4 100644 --- a/src/client/view/graph_view.js +++ b/src/client/view/graph_view.js @@ -25,8 +25,8 @@ * which resulted in overly complex (read: undefined/buggy) code. */ -define(['d3', 'Bacon', 'consts', 'util', 'view/selection', 'view/helpers', 'model/diff', 'view/view', 'view/bubble', 'model/types', 'view/layouts'], -function(d3 , Bacon, consts, util , selection , view_helpers, model_diff , view, view_bubble, model_types, view_layouts) { +define(['d3', 'Bacon', 'consts', 'util', 'view/selection', 'view/helpers', 'model/diff', 'view/item_info', 'view/bubble', 'model/types', 'view/layouts'], +function(d3 , Bacon, consts, util , selection , view_helpers, model_diff , item_info, view_bubble, model_types, view_layouts) { "use strict" @@ -287,7 +287,7 @@ function GraphView(spec) { function showNodeInfo(node) { util.assert(!temporary, "cannot showNodeInfo on a temporary graph"); - view.node_info.show(graph, node) + item_info.show(graph, node) } function dragstarted(d) { @@ -510,13 +510,7 @@ function GraphView(spec) { // after this events bubbles to the svg element return; } - var that = this; - - view.link_info.on_delete(function () { - view.link_info.hide(); - graph.links__delete([that.link.id]); - }); - view.link_info.show(d); + item_info.show(graph, d, ['name']); (d3.event.shiftKey? selection.invert_link : selection.select_link)(this.link); }); diff --git a/src/client/view/internal.js b/src/client/view/internal.js deleted file mode 100644 index 33e14e7a..00000000 --- a/src/client/view/internal.js +++ /dev/null @@ -1,14 +0,0 @@ -define(['view/tab'], -function(tab) { - -var LINK_INFO_SELECTOR = '#link_info', - NODE_INFO_SELECTOR = '#info', - edit_tab = new tab.Tab({edge: LINK_INFO_SELECTOR, node: NODE_INFO_SELECTOR}); - -return { - LINK_INFO_SELECTOR: LINK_INFO_SELECTOR, - NODE_INFO_SELECTOR: NODE_INFO_SELECTOR, - edit_tab: edit_tab, -}; - -}); diff --git a/src/client/view/item_info.js b/src/client/view/item_info.js new file mode 100644 index 00000000..1f187ce0 --- /dev/null +++ b/src/client/view/item_info.js @@ -0,0 +1,276 @@ +define(['jquery', 'jquery-ui', 'util', 'consts', 'view/helpers', 'model/diff', 'model/types'], +function($, _unused_jquery_ui, util, consts, view_helpers, model_diff, model_types) { + +var DEBOUNCE_TIME = 500; // milliseconds + +var item = null, + msg_node = $('.info-card-message'), + graph, + setup_done = false, + info = $('#info'), + info_container = $('.info-container'), + form_element = $('#editbox'), + delete_button = $('#edit-dialog__delete'), + form = _.object(model_types.all_attributes.map(function (attr) { + var element = edit_element_for_attribute(attr); + + if (element.length == 0) { + element = form_add_element(attr, 'textarea'); + } + return [attr, element]; + })), + change_handlers = [], + status_display = info.find('#displaystatus'), + status = info.find('#editstatus'); + +/** + * Adds the div with the label, return the edit child + */ +function form_add_element(attr, value_element_type) +{ + var div = $('
'), + label = $(''), + value = $('<' + value_element_type + '>'), + delete_button = form_element.find('#info-container__bottom-btn-bar'); + + div.attr('id', attr); + label.addClass('info-card-attr'); + label.text(model_types.attribute_titles[attr] + ':'); + value.addClass('info-card-attr-val'); + value.attr('id', 'edit' + attr); + div.append(label); + div.append(value); + div.insertBefore(delete_button); + return value; +} + +function clean_url(candidate_url) +{ + if (candidate_url.length == 0) { + return ''; + } + if (candidate_url.search('://') != -1) { + return candidate_url; + } + return 'https://' + candidate_url; +} + +function _get_form_data() { + var ret = _.object(_.keys(form),_.values(form).map(function (x) { return x.val(); })); + + ret.url = clean_url(ret.url); + return ret; +} + +function is_node(item) +{ + return item.__src === undefined; +} + +function update_item(item, new_data) +{ + if (is_node(item)) { + graph.update_node(item, new_data); + } else { + graph.update_link(item, new_data); + } +} + +function commit() +{ + if (item === null) { + return; + } + update_item(item, _get_form_data()); +} + +function textarea_resize(text, max) +{ + var height; + text.style.height = 'auto'; + height = text.scrollHeight; + if (max) { + height = Math.min(max, height); + } + text.style.height = height + 'px'; +} + +function setup_change_handlers() +{ + disable_change_handlers(); + // auto save after DEBOUNCE_TIME inactivity + var streams = _.map(_.values(form), function (element) { + return element.asEventStream('change input keyup'); + }); + var single = _.reduce(streams, function (stream_a, stream_b) { return stream_a.merge(stream_b); }); + change_handlers = [single.debounce(DEBOUNCE_TIME).onValue(function () { + commit(); + })]; +} + +function disable_change_handlers() +{ + _.each(change_handlers, function (unsub) { unsub(); }); + change_handlers = []; +} + +function delete_item() +{ + if (is_node(item)) { + graph.nodes__delete([item.id]); + } else { + graph.links__delete([item.id]); + } +} + +/** + * XXX + * need to update fields iff there have been no new input events associated with them since commit. + * do not look at contents because that will exist in the past too. + * + * see Bacon.awaiting. + * + * For now we just show a warning instead telling the user the dialog is not up to date. + */ +function setup_click_handlers() +{ + setup_change_handlers(); + if (setup_done) { + return; + } + setup_done = true; + form_element.on('keydown', function (e) { + if (e.which == consts.VK_ENTER && e.target !== delete_button[0]) { + e.preventDefault(); + } + }); + delete_button.on('click', function (e) { + var msg = is_node(item) ? 'delete node?' : 'delete link?'; + + e.preventDefault(); + hide(); + if (confirm(msg)) { + delete_item(item); + } + }); + $('#edit-dialog__save').on('click', function (e) { + e.preventDefault(); + hide(); + commit(); + }); + + // warn user if the item has been changed while open + diffBusUnsubscribe = graph.diffBus.onValue(function (diff) { + var removed_set = is_node(item) ? diff.node_id_set_rm : diff.link_id_set_rm, + changed_set = is_node(item) ? diff.id_to_node_map : diff.id_to_link_map; + + if (model_diff.is_topo_diff(diff) && _.contains(removed_set, item.id)) { + warning('!! item has been deleted !!'); + return; + } + if (model_diff.is_attr_diff(diff) || _.contains(_.keys(changed_set), item.id)) { + warning('!! item has been changed !!'); + return; + } + }); +} + +function warning(string) +{ + msg_node.text(string); +} + +function base_element_for_attribute(attr) +{ + return info.find('#' + attr); +} + +function edit_element_for_attribute(attr) +{ + return info.find('#edit' + attr); +} + +function update_textarea(textarea, value) +{ + textarea.val(value); + textarea_resize(textarea[0], 150); +} + +function show(_graph, new_item, visible_attributes) +{ + var visible_attributes, + hidden_attributes, + visible_elements, + hidden_elements, + max_height; + + visible_attributes = visible_attributes || model_types.type_attributes(new_item.type).slice(0); + hidden_attributes = _.difference(model_types.all_attributes, visible_attributes), + visible_elements = visible_attributes.map(base_element_for_attribute); + hidden_elements = hidden_attributes.map(base_element_for_attribute); + warning(''); // reset warning + graph = _graph; + item = new_item; + util.assert((is_node(item) ? graph.find_node__by_id : graph.find_link__by_id)(item.id) != null); + + setup_click_handlers(); + + _.each(hidden_elements, function (element) { element.hide(); }); + _.each(visible_elements, function (element) { element.show(); }); + + info.attr('class', 'info'); + info.addClass('type-' + item.type); // Add a class to distinguish types for css + + // hack - should be able to set max-height via css percentage, no? + max_height = $(document.body).innerHeight() - $('.info')[0].getBoundingClientRect().top * 2; + info_container[0].style['max-height'] = String(max_height) + 'px'; + + info_container.show(); + console.log(visible_attributes); + console.log(hidden_attributes); + _.each(visible_attributes, function (attr) { + var element = edit_element_for_attribute(attr); + value = item[attr]; + + switch (attr) { + case 'enddate': + case 'startdate': + element.datepicker({ + inline: true, + showOtherMonths: true, + dayNamesMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + }); + break; + case 'name': + case 'description': + update_textarea(element, value); + break; + case 'status': + if (_.contains(rz_config.role_set, 'admin')) { + status.val(item.status); + status.show(); + status_display.hide(); + } else { + status_display.text(item.status); + status.hide(); + status_display.show(); + } + break; + } + if (element.val !== undefined) { + element.val(value); + } + }); +} + +function hide() { + item = null; + info_container.hide(); +} + +return { + show: show, + hide: hide, +}; + +}); diff --git a/src/client/view/link_info.js b/src/client/view/link_info.js deleted file mode 100644 index d711e200..00000000 --- a/src/client/view/link_info.js +++ /dev/null @@ -1,36 +0,0 @@ -"use strict" - -define(['view/internal'], -function(internal) { - -var delete_button = internal.edit_tab.get('edge', '#deleteedge'), - delete_callback = undefined; - -delete_button.on('click', function() { - if (delete_callback) { - delete_callback(); - } -}); - -function show(link) -{ - internal.edit_tab.show('edge'); - internal.edit_tab.get('edge', '#edgetitle').html(link.name); -} - -function hide() -{ - internal.edit_tab.hide(); -} - -function on_delete(f) -{ - delete_callback = f; -} - -return { - show: show, - hide: hide, - on_delete: on_delete, -}; -}); diff --git a/src/client/view/node_info.js b/src/client/view/node_info.js deleted file mode 100644 index ef853a94..00000000 --- a/src/client/view/node_info.js +++ /dev/null @@ -1,241 +0,0 @@ -define(['jquery', 'jquery-ui', 'util', 'consts', 'view/helpers', 'view/internal', 'model/diff', 'model/types'], -function($, _unused_jquery_ui, util, consts, view_helpers, internal, model_diff, model_types) { - -var d = null, - msg_node = $('.info-card-message'), - graph, - node, - setup_done = false, - info = $('.info'), - info_container = $('.info-container'), - form_element = $('#editbox'), - delete_button = $('#edit-node-dialog__delete'), - form = _.object(model_types.all_attributes.map(function (attr) { - var element = edit_element_for_attribute(attr); - - if (element.length == 0) { - element = form_add_element(attr, 'textarea'); - } - return [attr, element]; - })), - change_handlers = [], - status_display = info.find('#displaystatus'), - status = info.find('#editstatus'); - -/** - * Adds the div with the label, return the edit child - */ -function form_add_element(attr, value_element_type) -{ - var div = $('
'), - label = $(''), - value = $('<' + value_element_type + '>'), - delete_button = form_element.find('#info-container__bottom-btn-bar'); - - div.attr('id', attr); - label.addClass('info-card-attr'); - label.text(model_types.attribute_titles[attr] + ':'); - value.addClass('info-card-attr-val'); - value.attr('id', 'edit' + attr); - div.append(label); - div.append(value); - div.insertBefore(delete_button); - return value; -} - -function clean_url(candidate_url) -{ - if (candidate_url.length == 0) { - return ''; - } - if (candidate_url.search('://') != -1) { - return candidate_url; - } - return 'https://' + candidate_url; -} - -function _get_form_data() { - var ret = _.object(_.keys(form),_.values(form).map(function (x) { return x.val(); })); - - ret.url = clean_url(ret.url); - return ret; -} - -function commit() -{ - graph.update_node(node, _get_form_data()); -} - -function textarea_resize(text, max) -{ - var height; - text.style.height = 'auto'; - height = text.scrollHeight; - if (max) { - height = Math.min(max, height); - } - text.style.height = height + 'px'; -} - -function setup_change_handlers() -{ - disable_change_handlers(); - // auto save style handlers - var streams = _.map(_.values(form), function (element) { - return element.asEventStream('change input keyup'); - }); - var single = _.reduce(streams, function (stream_a, stream_b) { return stream_a.merge(stream_b); }); - change_handlers = [single.debounce(500).onValue(function () { - commit(); - })]; -} - -function disable_change_handlers() -{ - _.each(change_handlers, function (unsub) { unsub(); }); - change_handlers = []; -} - -/** - * XXX - * need to update fields if and only if there have been no new input events associated with them since commit. - * do not look at contents because that will exist in the past too. - * - * see Bacon.awaiting. - * - * For now we just show a warning instead telling the user the dialog is not up to date. - */ -function setup_click_handlers() -{ - setup_change_handlers(); - if (setup_done) { - return; - } - setup_done = true; - form_element.on('keydown', function (e) { - if (e.which == consts.VK_ENTER && e.target !== delete_button[0]) { - e.preventDefault(); - } - }); - delete_button.on('click', function (e) { - e.preventDefault(); - hide(); - if (confirm('delete node?')) { - graph.nodes__delete([node.id]); - } - }); - $('#edit-node-dialog__save').on('click', function (e) { - e.preventDefault(); - hide(); - commit(); - }); - // re-open dialog on node updates while it is open - diffBusUnsubscribe = graph.diffBus.onValue(function (diff) { - if (model_diff.is_topo_diff(diff) && _.contains(diff.node_id_set_rm, node.id)) { - warning('!! node has been deleted !!'); - return; - } - if (model_diff.is_attr_diff(diff) || _.contains(_.keys(diff.id_to_node_map), node.id)) { - warning('!! node has been changed !!'); - return; - } - }); -} - -function warning(string) -{ - msg_node.text(string); -} - -function base_element_for_attribute(attr) -{ - return info.find('#' + attr); -} - -function edit_element_for_attribute(attr) -{ - return info.find('#edit' + attr); -} - -function update_textarea(textarea, value) -{ - textarea.val(value); - textarea_resize(textarea[0], 150); -} - -function show(_graph, d) { - var visible_attributes = model_types.type_attributes(d.type).slice(0), - hidden_attributes = _.difference(model_types.all_attributes, visible_attributes), - visible_elements, - hidden_elements, - max_height; - - visible_elements = visible_attributes.map(base_element_for_attribute); - hidden_elements = hidden_attributes.map(base_element_for_attribute); - warning(''); // reset warning - graph = _graph; - node = d; - util.assert(graph.find_node__by_id(d.id) != null); - - setup_click_handlers(); - - internal.edit_tab.show('node'); - - _.each(hidden_elements, function (element) { element.hide(); }); - _.each(visible_elements, function (element) { element.show(); }); - - info.attr('class', 'info'); - info.addClass('type-' + d.type); // Add a class to distinguish types for css - - // hack - should be able to set max-height via css percentage, no? - max_height = $(document.body).innerHeight() - $('.info')[0].getBoundingClientRect().top * 2; - info_container[0].style['max-height'] = String(max_height) + 'px'; - - _.each(visible_attributes, function (attr) { - var element = edit_element_for_attribute(attr); - value = d[attr]; - - switch (attr) { - case 'enddate': - case 'startdate': - element.datepicker({ - inline: true, - showOtherMonths: true, - dayNamesMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - }); - break; - case 'name': - case 'description': - update_textarea(element, value); - break; - case 'status': - if (_.contains(rz_config.role_set, 'admin')) { - status.val(d.status); - status.show(); - status_display.hide(); - } else { - status_display.text(d.status); - status.hide(); - status_display.show(); - } - break; - } - if (element.val !== undefined) { - element.val(value); - } - }); - -} - -function hide() { - node_id = null; - internal.edit_tab.hide(); -} - -return { - show: show, - hide: hide, - isOpenProperty: internal.edit_tab.isOpenProperty, -}; - -}); diff --git a/src/client/view/tab.js b/src/client/view/tab.js deleted file mode 100644 index 586e308a..00000000 --- a/src/client/view/tab.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict" - -define(['jquery', 'Bacon'], -function($, Bacon) { - -function Tab(dict) { - var k, - selector = {}, - name = [], - openessBus = new Bacon.Bus(); - - for (k in dict) { - if (dict.hasOwnProperty(k) == false) { - continue; - } - selector[k] = dict[k]; - name.push(k); - } - this._selector = selector; - this._name = name; - this._openessBus = openessBus; - this.isOpenProperty = openessBus.toProperty(false) -} - -Tab.prototype.show = function(shown_name) { - var i, - name, - element; - - for (i = 0 ; i < this._name.length ; ++i) { - name = this._name[i]; - element = $(this._selector[name]); - if (name === shown_name) { - element.fadeIn(300); - } else { - element.hide(); - } - } - this._openessBus.push(true); -} - -Tab.prototype.hide = function() { - var i; - - for (i = 0 ; i < this._name.length ; ++i) { - $(this._selector[this._name[i]]).fadeOut(300); - } - this._openessBus.push(false); -} - -Tab.prototype.get = function(name, sel) { - // selector concatenation - var e = $(this._selector[name] + ' ' + sel); - - return e; -} - -return { - Tab: Tab -}; - -}); diff --git a/src/client/view/view.js b/src/client/view/view.js deleted file mode 100644 index 575dc0a5..00000000 --- a/src/client/view/view.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict" - -define(['view/node_info', 'view/link_info', 'view/internal'], -function(view_node_info, view_link_info, view_internal) { -return { - 'node_info': view_node_info, - 'link_info': view_link_info, - 'hide': function() { - view_internal.edit_tab.hide(); - }, -}; -}); -- cgit v1.3.1