/* This file is part of rhizi, a collaborative knowledge graph editor. Copyright (C) 2014-2015 Rhizi This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ define(['jquery', 'util', 'consts', 'model/diff', 'model/types', 'model/graph', 'messages'], function($, util, consts, model_diff, model_types, graph, messages) { "strict" // constants var DEBOUNCE_TIME = 500; // milliseconds // aliases var is_node = graph.is_node; // variables var msg_node = $('.info-card-message'), setup_done = false, info_container = $('.info-container'), form_element = $('#editbox'), 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_container.find('#displaystatus'), status = info_container.find('#editstatus'), diffBusUnsubscribe, outside_change = false, visible_attributes, item, graph, edited_attributes; /** * 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 + '>'); div.attr('id', attr); div.addClass('info-container__row'); 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.insertAfter(msg_node); 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); ret = _.pick(ret, _.keys(edited_attributes)); return ret; } function update_item(item, new_data) { if (is_node(item)) { graph.update_node(item, new_data); } else { graph.update_link(item, new_data); } } function get_item_from_graph() { return is_node(item) ? graph.find_node__by_id(item.id) : graph.find_link__by_id(item.id); } function no_change() { return _.isEqual(_.pick(get_item_from_graph(), _.keys(edited_attributes)), _get_form_data()); } function commit() { if (item === null || outside_change || no_change()) { return; } update_item(item, _get_form_data()); } function textarea_resize(text, min, max) { var height; text.style.height = 'auto'; height = text.scrollHeight; height = Math.max(min, 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'); }); streams.forEach(function (stream) { stream.onValue(function (e) { edited_attributes[e.target.id.slice(4)] = 1; }); }); 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; $('#edit-dialog__save').on('click', function (e) { e.preventDefault(); commit(); hide(); }); // 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)) { var changed = changed_set[item.id]['__attr_write'], changed_keys = _.keys(changed), form_subset = _.pick(_get_form_data(), changed_keys); if (!_.isEqual(form_subset, changed)) { warning('!! item has been changed !!'); outside_change = true; } return; } }); } function warning(string) { msg_node.text(string); } function base_element_for_attribute(attr) { return info_container.find('#' + attr); } function edit_element_for_attribute(attr) { return info_container.find('#edit' + attr); } function update_textarea(textarea, value) { textarea.val(value); textarea_resize(textarea[0], 25, 150); } function show(_graph, new_item, new_visible_attributes) { var hidden_attributes, visible_elements, hidden_elements; visible_attributes = (new_visible_attributes || model_types.type_attributes(new_item.type)).slice(0).reverse(); 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(get_item_from_graph() != null); setup_click_handlers(); _.each(hidden_elements, function (element) { element.hide(); }); _.each(visible_elements, function (element) { element.show(); }); info_container.attr('class', 'info-container'); // clear previous type-x css classes info_container.addClass('type-' + item.type); // add css type class _.each(visible_attributes, function (attr) { var element = edit_element_for_attribute(attr); value = item[attr]; element.parent().insertAfter(msg_node); 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); } }); info_container.show(); } function init() { item = null; outside_change = false; graph = undefined; visible_attributes = []; edited_attributes = {}; } function hide(do_commit) { do_commit = do_commit === undefined || true; if (do_commit) { commit(); } if (diffBusUnsubscribe) { diffBusUnsubscribe(); } init(); info_container.hide(); } function first_time_init() { // setup the special type select box based on the domain. var root = info_container.find('#edittype'); model_types.nodetypes.forEach(function (nodetype) { root.append($('')); }); // set directionality of the information box, per domain_types if (model_types.misc && model_types.misc.direction) { info_container.css('direction', model_types.misc.direction); } } first_time_init(); init(); return { show: show, hide: hide, }; });