<commit_before>function add_datepickers(that) {
  $('input#id_start_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_end_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_date_selected', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
}

function state_dialog(url, state_title, state_type) {
    $('#change-record').load(url,
            function () {
                var patient_id = $(this).data('id');
                var service_id = $(this).data('service-id');
                function onsuccess(response, status, xhr, form) {
                    var parse = $(response);
                    if ($('.errorlist', parse).length != 0) {
                        $('#change-record').html(response);
                        $('#change-record form').ajaxForm({
                            success: onsuccess,
                            data: { patient_id: patient_id,  state_type: state_type, service_id: service_id }
                        });
                        console.log('error');
                    } else {
                        console.log('success');
                        window.location.reload(true);
                    }
                }
                $('input.date', this).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
                $('form', this).ajaxForm({
                    success: onsuccess,
                    data: { patient_id: patient_id,  state_type: state_type, service_id: service_id }
                });
                $(this).dialog({title: "Changement - " + state_title,
                    width: '500px',
                    buttons: [ { text: "Annuler",
                        click: function() { $(this).dialog("close"); } },
                    { text: "Valider",
                        click: function() { $("#change-record form").submit(); } }]});
            });
}

(function($) {
  $(function() {
    var $tabs = $('#tabs').tabs();

    $('.atabs').click(function() {
        location.hash = 'tab=' + $(this).data('id');
    });

    $('#btn_all_state').click(function() {
      $('.checkbox_state').attr('checked', true);
    });
    $('#btn_none_state').click(function() {
      $('.checkbox_state').attr('checked', false);
    });
    $('.checkbox_state').click(function() {
        $("#search").click();
    });
    $('#id_general-pause').click(function() {
        $('#btn-maj').click();
    });
    $('#id_general-confidential').click(function() {
        $('#btn-maj').click();
    });
    $('.policyholder-radio').click(function() {
        $("#policyholder-form").submit();
    });
    $('.pr-line').click(function() {
        window.location.href = $(this).data('link');
    });
    $('button#reset').click(function() {
        window.location.href = window.location.pathname;
        return false;
    });
    $('#print-button').click(function() { window.print(); });

    $('#close-patientrecord').click(function() {
        state_dialog('update-state', 'Clore', 'CLOS');
    });
    $('#reopen-patientrecord').click(function() {
        state_dialog('update-state', 'Réaccueil', 'ACCUEIL');
    });
    $('#diagnostic-patientrecord').click(function() {
        state_dialog('update-state', 'Diagnostic', 'DIAGNOSTIC');
    });
    $('#traitement-patientrecord').click(function() {
        state_dialog('update-state', 'Traitement', 'TRAITEMENT');
    });
    $('#finaccueil-patientrecord').click(function() {
        state_dialog('update-state', "Fin d'accueil", 'FIN_ACCUEIL');
    });
    $('#bilan-patientrecord').click(function() {
        state_dialog('update-state', 'Bilan', 'BILAN');
    });
    $('#surveillance-patientrecord').click(function() {
        state_dialog('update-state', 'Surveillance', 'SURVEILLANCE');
    });
    $('#suivi-patientrecord').click(function() {
        state_dialog('update-state', 'Suivi', 'SUIVI');
    });
    $('#patientrecord-history').click(function() {
      $('#dossier-histo-dlg').dialog({title: 'Historique dossier',
        width: '500px',
        buttons: [ { text: "Fermer",
          click: function() { $(this).dialog("close"); } }]}
        );
    });
    if (location.hash.indexOf('histo') != -1) {
      $('#patientrecord-history').click();
      location.hash = '';
    }

    $('#new-patientrecord').click(function() {
        generic_ajaxform_dialog('new', 'Nouveau dossier',
            '#dossier-dlg', '500px', 'Ajouter', false, function(that) {
                    $('input#id_date_selected', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
                    $(that).find('#id_last_name').keyup(function() {
                            var val = $(this).val();
                            if (val.length < 3) {
                               $(that).find('#last_name_matches').empty();
                               return;
                            }
                            $.ajax({
                               url: "/lookups/ajax_lookup/patientrecord?term=" + val,
                               success: function(json) {
                                  var list = $(that).find('#last_name_matches');
                                  list.empty();
                                  $(eval(json)).each(function(a, b) {
                                          list.append($('
' + b.value + '
'));
                                  });
                               }
                            });
                    });
            });
    });
    $('#patientrecord-delete').click(function() {
        generic_ajaxform_dialog('delete', 'Supprimer le dossier',
            '#ajax-dlg', '500px', 'Oui', '..');
    });

    $('#new-address-btn').click(function() {
        generic_ajaxform_dialog('address/new', 'Ajouter une adresse',
            '#ajax-dlg', '600px', 'Ajouter');
    });
    $('.update-address-btn').click(function() {
        generic_ajaxform_dialog('address/' + $(this).data('id') + '/update', 'Modifier une adresse',
            '#ajax-dlg', '600px', 'Modifier');
    });
    $('.del-address').click(function() {
        generic_ajaxform_dialog('address/' + $(this).data('id') + '/del', 'Supprimer une addresse',
            '#ajax-dlg', '500px', 'Supprimer');
    });

    $('#update-paper-id-btn').click(function() {
        generic_ajaxform_dialog('update/paper_id', 'Modifier le numéro du dossier papier',
            '#ajax-dlg', '500px', 'Modifier');
    });

    function nir_check(that) {
      $(that).find('#social-security-id input').keyup(function() {
        if ($(this).val().length < 13) {
             $('p#nir-key span').removeAttr('id')
             $('p#nir-key span').text('-');
         } else {
             $('p#nir-key span').attr('id', 'highlight')
             var nir = $(this).val();
             var minus = 0;
             if (nir.charAt(6) == 'A'){
               nir = nir.replace('A', '0');
               minus = 1000000;
             }
             if (nir.charAt(6) == 'B'){
               nir = nir.replace('B', '0');
               minus = 2000000;
             }
             nir = parseInt(nir, 10);
             nir = nir - minus;
             var key = 97 - (nir % 97);
             if (isNaN(key)) {
                 $('p#nir-key span').text('NIR invalide');
             } else {
                 $('p#nir-key span').text(key);
             }
         }
      });
    }

    $('#new-contact-btn').click(function() {
        generic_ajaxform_dialog('contact/new', 'Ajouter un contact',
            '#ajax-dlg', '800px', 'Ajouter', null, nir_check);
    });
    $('.update-contact-btn').click(function() {
        generic_ajaxform_dialog('contact/' + $(this).data('id') + '/update', 'Modifier un contact',
            '#ajax-dlg', '800px', 'Modifier', null, nir_check);
    });
    $('.del-contact').click(function() {
        generic_ajaxform_dialog('contact/' + $(this).data('id') + '/del?address=' + $(this).data('address-id'),
                'Supprimer un contact', '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-socialisation-duration-btn').click(function() {
        generic_ajaxform_dialog('socialisation/new', 'Ajouter une période de socialisation',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-duration-btn').click(function() {
        generic_ajaxform_dialog('socialisation/' + $(this).data('id') + '/update', 'Modifier une période de socialisation',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-duration').click(function() {
        generic_ajaxform_dialog('socialisation/' + $(this).data('id') + '/del', 'Supprimer une période de socialisation',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-mdph-request-btn').click(function() {
        generic_ajaxform_dialog('mdph_request/new', 'Ajouter une demande MDPH',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-mdph-request-btn').click(function() {
        generic_ajaxform_dialog('mdph_request/' + $(this).data('id') + '/update', 'Modifier une demande MDPH',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-mdph-request').click(function() {
        generic_ajaxform_dialog('mdph_request/' + $(this).data('id') + '/del', 'Supprimer une demande MDPH',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-mdph-response-btn').click(function() {
        generic_ajaxform_dialog('mdph_response/new', 'Ajouter une réponse MDPH',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-mdph-response-btn').click(function() {
        generic_ajaxform_dialog('mdph_response/' + $(this).data('id') + '/update', 'Modifier une réponse MDPH',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-mdph-response').click(function() {
        generic_ajaxform_dialog('mdph_response/' + $(this).data('id') + '/del', 'Supprimer une réponse MDPH',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-hctrait-btn').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/new', 'Ajouter une prise en charge de traitement',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('#new-hcdiag-btn').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/new', 'Ajouter une prise en charge de diagnostic',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('#new-notification-btn').click(function() {
        generic_ajaxform_dialog('healthcare_notification/new', 'Ajouter une notification',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('.update-hctrait-btn').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/' + $(this).data('id') + '/update', 'Modifier une prise en charge de traitement',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.update-hcdiag-btn').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/' + $(this).data('id') + '/update', 'Modifier une prise en charge de diagnostic',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.update-notification-btn').click(function() {
        generic_ajaxform_dialog('healthcare_notification/' + $(this).data('id') + '/update', 'Modifier une notification',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-hctrait').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/' + $(this).data('id') + '/del', 'Supprimer une prise en charge de traitement',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('.del-hcdiag').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/' + $(this).data('id') + '/del', 'Supprimer une prise en charge de diagnostic',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('.del-notification').click(function() {
        generic_ajaxform_dialog('healthcare_notification/' + $(this).data('id') + '/del', 'Supprimer une notification',
            '#ajax-dlg', '500px', 'Supprimer');
    });

    $('.update-patient-state-btn').click(function() {
        generic_ajaxform_dialog('state/' + $(this).data('id') + '/update', 'Modifier un état',
            '#ajax-dlg', '500px', 'Modifier', '#histo', add_datepickers);
    });
    $('.del-patient-state-btn').click(function() {
        generic_ajaxform_dialog('state/' + $(this).data('id') + '/del', 'Supprimer un état',
            '#ajax-dlg', '500px', 'Supprimer', '#histo');
    });

      $('.place_of_life').click(function() {
          if ((this.checked) == true) {
              var value = "true";
          } else {
              var value = "false";
          }
          $.ajax({
              url: '/api/v1/patientaddress/' + $(this).data("id") + '/?format=json',
              type: 'PATCH',
              contentType: 'application/json',
              data: '{"place_of_life": ' + value + '}',
              success: function(data) {
                  console.log('success');
              }
          });
      });

    $('input#id_id-birthdate', this).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });

    $('button.blind').next().hide();
    $('button.blind').click(function() {
      $(this).next().toggle('blind');
    });
    var tabid = $.url($(location).attr('href')).fparam('tab');
    if (tabid) {
        $tabs.tabs('select',  parseInt(tabid));
    }
  });
})(window.jQuery)
<commit_msg>js: add date picker to hc form dates.<commit_after>function add_datepickers(that) {
  $('input#id_start_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_request_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_agree_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_insist_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_end_date', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
  $('input#id_date_selected', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
}

function state_dialog(url, state_title, state_type) {
    $('#change-record').load(url,
            function () {
                var patient_id = $(this).data('id');
                var service_id = $(this).data('service-id');
                function onsuccess(response, status, xhr, form) {
                    var parse = $(response);
                    if ($('.errorlist', parse).length != 0) {
                        $('#change-record').html(response);
                        $('#change-record form').ajaxForm({
                            success: onsuccess,
                            data: { patient_id: patient_id,  state_type: state_type, service_id: service_id }
                        });
                        console.log('error');
                    } else {
                        console.log('success');
                        window.location.reload(true);
                    }
                }
                $('input.date', this).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
                $('form', this).ajaxForm({
                    success: onsuccess,
                    data: { patient_id: patient_id,  state_type: state_type, service_id: service_id }
                });
                $(this).dialog({title: "Changement - " + state_title,
                    width: '500px',
                    buttons: [ { text: "Annuler",
                        click: function() { $(this).dialog("close"); } },
                    { text: "Valider",
                        click: function() { $("#change-record form").submit(); } }]});
            });
}

(function($) {
  $(function() {
    var $tabs = $('#tabs').tabs();

    $('.atabs').click(function() {
        location.hash = 'tab=' + $(this).data('id');
    });

    $('#btn_all_state').click(function() {
      $('.checkbox_state').attr('checked', true);
    });
    $('#btn_none_state').click(function() {
      $('.checkbox_state').attr('checked', false);
    });
    $('.checkbox_state').click(function() {
        $("#search").click();
    });
    $('#id_general-pause').click(function() {
        $('#btn-maj').click();
    });
    $('#id_general-confidential').click(function() {
        $('#btn-maj').click();
    });
    $('.policyholder-radio').click(function() {
        $("#policyholder-form").submit();
    });
    $('.pr-line').click(function() {
        window.location.href = $(this).data('link');
    });
    $('button#reset').click(function() {
        window.location.href = window.location.pathname;
        return false;
    });
    $('#print-button').click(function() { window.print(); });

    $('#close-patientrecord').click(function() {
        state_dialog('update-state', 'Clore', 'CLOS');
    });
    $('#reopen-patientrecord').click(function() {
        state_dialog('update-state', 'Réaccueil', 'ACCUEIL');
    });
    $('#diagnostic-patientrecord').click(function() {
        state_dialog('update-state', 'Diagnostic', 'DIAGNOSTIC');
    });
    $('#traitement-patientrecord').click(function() {
        state_dialog('update-state', 'Traitement', 'TRAITEMENT');
    });
    $('#finaccueil-patientrecord').click(function() {
        state_dialog('update-state', "Fin d'accueil", 'FIN_ACCUEIL');
    });
    $('#bilan-patientrecord').click(function() {
        state_dialog('update-state', 'Bilan', 'BILAN');
    });
    $('#surveillance-patientrecord').click(function() {
        state_dialog('update-state', 'Surveillance', 'SURVEILLANCE');
    });
    $('#suivi-patientrecord').click(function() {
        state_dialog('update-state', 'Suivi', 'SUIVI');
    });
    $('#patientrecord-history').click(function() {
      $('#dossier-histo-dlg').dialog({title: 'Historique dossier',
        width: '500px',
        buttons: [ { text: "Fermer",
          click: function() { $(this).dialog("close"); } }]}
        );
    });
    if (location.hash.indexOf('histo') != -1) {
      $('#patientrecord-history').click();
      location.hash = '';
    }

    $('#new-patientrecord').click(function() {
        generic_ajaxform_dialog('new', 'Nouveau dossier',
            '#dossier-dlg', '500px', 'Ajouter', false, function(that) {
                    $('input#id_date_selected', that).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });
                    $(that).find('#id_last_name').keyup(function() {
                            var val = $(this).val();
                            if (val.length < 3) {
                               $(that).find('#last_name_matches').empty();
                               return;
                            }
                            $.ajax({
                               url: "/lookups/ajax_lookup/patientrecord?term=" + val,
                               success: function(json) {
                                  var list = $(that).find('#last_name_matches');
                                  list.empty();
                                  $(eval(json)).each(function(a, b) {
                                          list.append($('
' + b.value + '
'));
                                  });
                               }
                            });
                    });
            });
    });
    $('#patientrecord-delete').click(function() {
        generic_ajaxform_dialog('delete', 'Supprimer le dossier',
            '#ajax-dlg', '500px', 'Oui', '..');
    });

    $('#new-address-btn').click(function() {
        generic_ajaxform_dialog('address/new', 'Ajouter une adresse',
            '#ajax-dlg', '600px', 'Ajouter');
    });
    $('.update-address-btn').click(function() {
        generic_ajaxform_dialog('address/' + $(this).data('id') + '/update', 'Modifier une adresse',
            '#ajax-dlg', '600px', 'Modifier');
    });
    $('.del-address').click(function() {
        generic_ajaxform_dialog('address/' + $(this).data('id') + '/del', 'Supprimer une addresse',
            '#ajax-dlg', '500px', 'Supprimer');
    });

    $('#update-paper-id-btn').click(function() {
        generic_ajaxform_dialog('update/paper_id', 'Modifier le numéro du dossier papier',
            '#ajax-dlg', '500px', 'Modifier');
    });

    function nir_check(that) {
      $(that).find('#social-security-id input').keyup(function() {
        if ($(this).val().length < 13) {
             $('p#nir-key span').removeAttr('id')
             $('p#nir-key span').text('-');
         } else {
             $('p#nir-key span').attr('id', 'highlight')
             var nir = $(this).val();
             var minus = 0;
             if (nir.charAt(6) == 'A'){
               nir = nir.replace('A', '0');
               minus = 1000000;
             }
             if (nir.charAt(6) == 'B'){
               nir = nir.replace('B', '0');
               minus = 2000000;
             }
             nir = parseInt(nir, 10);
             nir = nir - minus;
             var key = 97 - (nir % 97);
             if (isNaN(key)) {
                 $('p#nir-key span').text('NIR invalide');
             } else {
                 $('p#nir-key span').text(key);
             }
         }
      });
    }

    $('#new-contact-btn').click(function() {
        generic_ajaxform_dialog('contact/new', 'Ajouter un contact',
            '#ajax-dlg', '800px', 'Ajouter', null, nir_check);
    });
    $('.update-contact-btn').click(function() {
        generic_ajaxform_dialog('contact/' + $(this).data('id') + '/update', 'Modifier un contact',
            '#ajax-dlg', '800px', 'Modifier', null, nir_check);
    });
    $('.del-contact').click(function() {
        generic_ajaxform_dialog('contact/' + $(this).data('id') + '/del?address=' + $(this).data('address-id'),
                'Supprimer un contact', '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-socialisation-duration-btn').click(function() {
        generic_ajaxform_dialog('socialisation/new', 'Ajouter une période de socialisation',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-duration-btn').click(function() {
        generic_ajaxform_dialog('socialisation/' + $(this).data('id') + '/update', 'Modifier une période de socialisation',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-duration').click(function() {
        generic_ajaxform_dialog('socialisation/' + $(this).data('id') + '/del', 'Supprimer une période de socialisation',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-mdph-request-btn').click(function() {
        generic_ajaxform_dialog('mdph_request/new', 'Ajouter une demande MDPH',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-mdph-request-btn').click(function() {
        generic_ajaxform_dialog('mdph_request/' + $(this).data('id') + '/update', 'Modifier une demande MDPH',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-mdph-request').click(function() {
        generic_ajaxform_dialog('mdph_request/' + $(this).data('id') + '/del', 'Supprimer une demande MDPH',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-mdph-response-btn').click(function() {
        generic_ajaxform_dialog('mdph_response/new', 'Ajouter une réponse MDPH',
            '#ajax-dlg', '800px', 'Ajouter', null, add_datepickers);
    });
    $('.update-mdph-response-btn').click(function() {
        generic_ajaxform_dialog('mdph_response/' + $(this).data('id') + '/update', 'Modifier une réponse MDPH',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-mdph-response').click(function() {
        generic_ajaxform_dialog('mdph_response/' + $(this).data('id') + '/del', 'Supprimer une réponse MDPH',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('#new-hctrait-btn').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/new', 'Ajouter une prise en charge de traitement',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('#new-hcdiag-btn').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/new', 'Ajouter une prise en charge de diagnostic',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('#new-notification-btn').click(function() {
        generic_ajaxform_dialog('healthcare_notification/new', 'Ajouter une notification',
            '#ajax-dlg', '600px', 'Ajouter', null, add_datepickers);
    });
    $('.update-hctrait-btn').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/' + $(this).data('id') + '/update', 'Modifier une prise en charge de traitement',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.update-hcdiag-btn').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/' + $(this).data('id') + '/update', 'Modifier une prise en charge de diagnostic',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.update-notification-btn').click(function() {
        generic_ajaxform_dialog('healthcare_notification/' + $(this).data('id') + '/update', 'Modifier une notification',
            '#ajax-dlg', '800px', 'Modifier', null, add_datepickers);
    });
    $('.del-hctrait').click(function() {
        generic_ajaxform_dialog('healthcare_treatment/' + $(this).data('id') + '/del', 'Supprimer une prise en charge de traitement',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('.del-hcdiag').click(function() {
        generic_ajaxform_dialog('healthcare_diagnostic/' + $(this).data('id') + '/del', 'Supprimer une prise en charge de diagnostic',
            '#ajax-dlg', '500px', 'Supprimer');
    });
    $('.del-notification').click(function() {
        generic_ajaxform_dialog('healthcare_notification/' + $(this).data('id') + '/del', 'Supprimer une notification',
            '#ajax-dlg', '500px', 'Supprimer');
    });

    $('.update-patient-state-btn').click(function() {
        generic_ajaxform_dialog('state/' + $(this).data('id') + '/update', 'Modifier un état',
            '#ajax-dlg', '500px', 'Modifier', '#histo', add_datepickers);
    });
    $('.del-patient-state-btn').click(function() {
        generic_ajaxform_dialog('state/' + $(this).data('id') + '/del', 'Supprimer un état',
            '#ajax-dlg', '500px', 'Supprimer', '#histo');
    });

      $('.place_of_life').click(function() {
          if ((this.checked) == true) {
              var value = "true";
          } else {
              var value = "false";
          }
          $.ajax({
              url: '/api/v1/patientaddress/' + $(this).data("id") + '/?format=json',
              type: 'PATCH',
              contentType: 'application/json',
              data: '{"place_of_life": ' + value + '}',
              success: function(data) {
                  console.log('success');
              }
          });
      });

    $('input#id_id-birthdate', this).datepicker({dateFormat: 'd/m/yy', showOn: 'button' });

    $('button.blind').next().hide();
    $('button.blind').click(function() {
      $(this).next().toggle('blind');
    });
    var tabid = $.url($(location).attr('href')).fparam('tab');
    if (tabid) {
        $tabs.tabs('select',  parseInt(tabid));
    }
  });
})(window.jQuery)
<|endoftext|>


<commit_before>// $Revision$

Ensembl.Panel.PopulationGraph = Ensembl.Panel.extend({
  init: function () {
    var panel = this;

    this.base();

    if (typeof Raphael === 'undefined') {
      $.getScript('/raphael/raphael-min.js', function () {
        $.getScript('/raphael/g.raphael-min.js', function () {
          $.getScript('/raphael/g.pie-modified-min.js', function () { panel.getContent(); });
        });
      });
    }
  },

  getContent: function () {
    var alleles   = [ 'A', 'T', 'G', 'C' ];
    var acolours  = [ '#00BB00', '#FF0000', '#FFD700', '#0000FF' ];
    var bcolours  = [ '#FF00FF', '#000000', '#008080', '#7B68EE' ]; // Other colours if the allele is not A, T, G or C
    var graphData = [];
    var i, j, k, raphael, pieData, pieColors, piePercent, b, colourFlag;

    $('input.population', this.el).each(function () {
      graphData.push(eval(this.value));
    });

    // For each graph
    for (i in graphData) {
      pieData    = [];
      pieColors  = [];
      piePercent = [];
      raphael    = Raphael('graphHolder' + i);
      raphael.g.txtattr.font = "12px 'Luxi Sans','Helvetica', sans-serif";

      // For each allele
      for (j in graphData[i]) {
        pieData.push(graphData[i][j][1]);
        piePercent.push(graphData[i][j][0] + ': %%');
        colourFlag = 0;

        // Normal allele colour
        for (k in alleles) {
          if (graphData[i][j][0] === alleles[k]) {
            pieColors.push(acolours[k]);
            colourFlag = 1;
          }
        }

        // Other colour
        if (colourFlag === 0 && b < bcolours.length) {
          pieColors.push(bcolours[b]);
          b++;
        }
      }

      raphael.g.piechart(25, 25, 20, pieData, { legend: piePercent, legendpos: "east", legendmark: 'arrow', colors: pieColors }); // Create the pie graph
    }
  }
});
<commit_msg>Fixed minor bug to colour alleles different from A,T,G,C<commit_after>// $Revision$

Ensembl.Panel.PopulationGraph = Ensembl.Panel.extend({
  init: function () {
    var panel = this;

    this.base();

    if (typeof Raphael === 'undefined') {
      $.getScript('/raphael/raphael-min.js', function () {
        $.getScript('/raphael/g.raphael-min.js', function () {
          $.getScript('/raphael/g.pie-modified-min.js', function () { panel.getContent(); });
        });
      });
    }
  },

  getContent: function () {
    var alleles   = [ 'A', 'T', 'G', 'C' ];
    var acolours  = [ '#00BB00', '#FF0000', '#FFD700', '#0000FF' ];
    var bcolours  = [ '#222222', '#FF00FF', '#008080', '#7B68EE' ]; // Other colours if the allele is not A, T, G or C
    var graphData = [];
    var i, j, k, raphael, pieData, pieColors, piePercent, b, colourFlag;

    $('input.population', this.el).each(function () {
      graphData.push(eval(this.value));
    });

    // For each graph
    for (i in graphData) {
      pieData    = [];
      pieColors  = [];
      piePercent = [];
      raphael    = Raphael('graphHolder' + i);
      raphael.g.txtattr.font = "12px 'Luxi Sans','Helvetica', sans-serif";
      b = 0;

      // For each allele
      for (j in graphData[i]) {
        pieData.push(graphData[i][j][1]);
        piePercent.push(graphData[i][j][0] + ': %%');
        colourFlag = 0;

        // Normal allele colour
        for (k in alleles) {
          if (graphData[i][j][0] === alleles[k]) {
            pieColors.push(acolours[k]);
            colourFlag = 1;
          }
        }

        // Other colour
        if (colourFlag === 0 && b < bcolours.length) {
          pieColors.push(bcolours[b]);
          b++;
        }
      }

      raphael.g.piechart(25, 25, 20, pieData, { legend: piePercent, legendpos: "east", legendmark: 'arrow', colors: pieColors }); // Create the pie graph
    }
  }
});
<|endoftext|>


<commit_before>// This script indexes documents in Cloudsearch.
var csd = require(__dirname + '/cloudsearchifyDocuments.js');
var cloudsearchdomain = require(__dirname + "/../../config/endpoints").cloudsearchdomain;
var amazonProductApi = require(__dirname + "/../../amazonProductAPI/product.controller");

var graphyc = require('../../../graphyc.js');
var numberOfDocuments = 50;
var similarHash = {};
var q = [];
var orderedProducts = [];
var hashCount = 0;
var similarHT = {};
var seenHash = {};
var simArray = [];
var count = 0;
var parCount = 0;
var t = new Date().getTime();
console.log(t);
var adjacencyList = [];
var graph = new graphyc.Graph([]);
var numberOfDocuments = 100000;
var count = 0;
var uniqueProductsContainer = {};

exports.indexDocuments = function(data) {
  var params = {
    contentType: 'application/json',
    documents: csd.cloudsearchifyDocuments(data)
  };

  return cloudsearchdomain.uploadDocuments(params, function(err, data) {
    if(err) {
      console.log(err, err.stack);
    }
    else {
      count += data.adds;
      console.log(count + " documents indexed!");
    }
  });
};

// Expecting: [[{product},[1,5,3,..],[{product},[1,5,3,..]...]
// add x, y, z coordinates to products that orients the products in the 3D world based on the popularity of a single product,
// and then webbing related products out from there
var insertModelCoordinates = function(productObjectPlusAdjacencyListArray, products) {
  products = products || [];

  // base case - initially one most popular product with 5 similar products, each with 5 similar, each with similar
  // so 1 + 5 * 5 * 5 = 126
  if(products.length > 126){
    return products;
  }

  productObjectPlusAdjacencyListArray.forEach(function(productObjectPlusAdjacencyList){
    var originalProduct = productObjectPlusAdjacencyList[0];
    var adjacencyList = productObjectPlusAdjacencyList[1];
    var productIndex;

    // add original product to products to return
    originalProduct = addCoordinatesToProduct(originalProduct, 0, 0, 0);
    products.push(originalProduct);

    // dequeue products from adjacency list and add coordinates to those products
    var i = 0;
    while(productIndex = adjacencyList.pop()){
      // assuming 5 products in adjacency list, create the following coordinates:
      //[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0],
      var coordinates = [0,0,0];
      coordinates[i % 3] = i < 3 ? 1 : -1;
      var relatedProductObjectPlusAdjacencyList = productObjectPlusAdjacencyListArray[productIndex];

      // recurse through related product
      insertModelCoordinates(relatedProductObjectPlusAdjacencyList);

      // add coordinates to related product
      relatedProductObjectPlusAdjacencyList = addCoordinatesToProduct(relatedProductObjectPlusAdjacencyList, coordinates[0], coordinates[1], coordinates[2]);

      // add related product from adjacency list to products to return
      products.push(relatedProductObjectPlusAdjacencyList);
      i++;
    }
  });
};

// helper function to add coordinates to product
var addCoordinatesToProduct = function(relatedProductObjectPlusAdjacencyList, x, y, z){
  relatedProductObjectPlusAdjacencyList[0].coordinates = {
    x: x,
    y: y,
    z: z
  };

  return relatedProductObjectPlusAdjacencyList;
};

recurse = function(pageNo) {

  // retrieve products from amazon products api
  amazonProductApi.searchCart(pageNo, function(err, results) {
    var _results = [];
    var r2 = results.ItemSearchResponse.Items[0].Item;

    // Log errors, if any
    if(results.ItemSearchResponse.Items[0].Request[0].Errors) {
      console.log(results.ItemSearchResponse.Items[0].Request[0].Errors[0].Error[0].Message[0]);
    }
    var i = 0;
    var flag = false;
    console.log(r2.length);
    while(r2 && r2[i] && i < r2.length && i < 15) {
      var obj = r2[i];
      var product = {};

      // Sometimes no ItemAttributes Returned
      if(obj.ItemAttributes &&
        obj.ItemAttributes[0].ListPrice &&
        obj.ItemAttributes[0].Title &&
        obj.MediumImage &&
        obj.CustomerReviews &&
        obj.CustomerReviews[0].IFrameURL &&
        obj.Offers &&
        obj.Offers[0].TotalOffers &&
        +obj.Offers[0].TotalOffers[0] > 0 &&
        obj.SimilarProducts &&
        obj.SimilarProducts[0].SimilarProduct) {

        // filter out adult products, like dildos
        if(!(obj.ItemAttributes[0].IsAdultProduct && obj.ItemAttributes[0].IsAdultProduct[0] === "1")) {

          //filter out duplicate 'items' sold by different vendors
          if(!uniqueProductsContainer[obj.ASIN[0]]) {
            uniqueProductsContainer[obj.ASIN[0]] = true;
            // similarHash[obj.ASIN[0]] = obj.SimilarProducts[0].SimilarProduct;
            simArray = simArray.concat(obj.SimilarProducts[0].SimilarProduct.map(function(e) {
              e.linkASIN = obj.ASIN[0];
              e.ASIN = e.ASIN[0];
              return e;
            }));
            // build product entry to return to client
            product.product_id = obj.ASIN[0];
            product.price = parseInt(obj.ItemAttributes[0].ListPrice[0].Amount[0] / 100, 10);
            product.title = obj.ItemAttributes[0].Title[0];
            product.img_url = obj.MediumImage[0].URL[0];
            product.prod_attributes = JSON.stringify(obj.ItemAttributes[0]);
            product.category = obj.ItemAttributes[0].ProductGroup[0];

            // add coordinates to place products on screen as 3d models
            product.x = 0;
            product.y = 0;
            product.z = 0;

            _results.push(product);

            hashCount++;
            flag = true;
            simArray.forEach(function(e, i) {
                if (!(e.ASIN in seenHash)) {
                  seenHash[e.ASIN] = q.length;
                  q.push(e.ASIN);
                  orderedProducts.push(e);
                  // graph.add(e, [])
                }
            });
            //decrement number of total documents we want to return
            numberOfDocuments--;
          }
        }
      }

      //increment counter within while loop
      i++;
    }
    console.log(q.length, "Queue");
    processQ(0);
  });
};

recurse(1);
console.log('recurse');
var someC = 0;
function processQ(index) {
  console.log("ParCount");
  this.index = index;
  var someC = 0;
  q.forEach(function(e) {
    if (!(e in seenHash)) {
      someC++;
    }
  });
  setTimeout(function(){
    var e = q[index];
    var element = q[index]
    console.log("INDEX", e);
    if (!(e in seenHash) || 1){
      amazonProductApi.lookup(e, function(err, results) {
        if (results.ItemLookupErrorResponse && results.ItemLookupErrorResponse.Error) {
          console.log (results.ItemLookupErrorResponse.Error[0])
        }
        else {
          var r2 = results.ItemLookupResponse.Items[0].Item;

          // Log errors, if any
          if(results.ItemLookupResponse.Items[0].Request[0].Errors) {
            console.log(results.ItemLookupResponse.Items[0].Request[0].Errors[0].Error[0].Message[0]);
          }

          var obj = r2[0];
          var product = {};
          // Sometimes no ItemAttributes Returned
          if(obj.ItemAttributes &&
            obj.ItemAttributes[0].ListPrice &&
            obj.ItemAttributes[0].Title &&
            obj.MediumImage &&
            obj.CustomerReviews &&
            obj.CustomerReviews[0].IFrameURL &&
            obj.Offers &&
            obj.Offers[0].TotalOffers &&
            +obj.Offers[0].TotalOffers[0] > 0 &&
            obj.SimilarProducts &&
            obj.SimilarProducts[0].SimilarProduct) {

            // filter out adult products, like dildos
            if(!(obj.ItemAttributes[0].IsAdultProduct && obj.ItemAttributes[0].IsAdultProduct[0] === "1")) {
              var index = this.index;
              //filter out duplicate 'items' sold by different vendors
              if(!uniqueProductsContainer[obj.ASIN[0]]) {
                uniqueProductsContainer[obj.ASIN[0]] = true;
                // similarHash[obj.ASIN[0]] = obj.SimilarProducts[0].SimilarProduct;
                var someFlag = false;
                var simProds = [];
                var newArray = obj.SimilarProducts[0].SimilarProduct.map(function(e) {
                  e.linkASIN = obj.ASIN[0];
                  e.ASIN = e.ASIN[0];
                  simProds.push(e.ASIN)
                  if (!(e.ASIN in seenHash)) {
                    seenHash[e.ASIN] = q.length;
                    someFlag = true;
                    q.push(e.ASIN);
                    orderedProducts.push(e);
                  }
                  return e;
                });
                var simProds = simProds.map(function(e) {
                  return seenHash[e];
                })
                graph.add([orderedProducts[this.index], simProds]);
                exports.indexDocuments(graph.al[graph.al.length - 1]);
                if (!someFlag) {console.log('flag did not toggle')}

                product.product_id = obj.ASIN[0];
                product.price = parseInt(obj.ItemAttributes[0].ListPrice[0].Amount[0] / 100, 10);
                product.title = obj.ItemAttributes[0].Title[0];
                product.img_url = obj.MediumImage[0].URL[0];
                product.prod_attributes = JSON.stringify(obj.ItemAttributes[0]);
                product.category = obj.ItemAttributes[0].ProductGroup[0];

                similarHash[hashCount] = product.product_id;
                similarHT[product.product_id] = hashCount;
                hashCount++;

                //decrement number of total documents we want to return
                numberOfDocuments--;
              }
            }
          }
        }
        console.log('ONE CALL NOW', this.index, q.length);
        processQ(this.index + 1);
      }.bind(this));
    }
  }.bind(this), 1250);
};

<commit_msg>solve index adjacency list errpor<commit_after>// This script indexes documents in Cloudsearch.
var csd = require(__dirname + '/cloudsearchifyDocuments.js');
var cloudsearchdomain = require(__dirname + "/../../config/endpoints").cloudsearchdomain;
var amazonProductApi = require(__dirname + "/../../amazonProductAPI/product.controller");

var graphyc = require('../../../graphyc.js');
var numberOfDocuments = 50;
var similarHash = {};
var q = [];
var orderedProducts = [];
var hashCount = 0;
var similarHT = {};
var seenHash = {};
var simArray = [];
var count = 0;
var parCount = 0;
var t = new Date().getTime();
console.log(t);
var adjacencyList = [];
var graph = new graphyc.Graph([]);
var numberOfDocuments = 100000;
var count = 0;
var uniqueProductsContainer = {};

exports.indexDocuments = function(data) {
  var obj = data[0];
  obj.adjacencyList = data[1];
  var params = {
    contentType: 'application/json',
    documents: csd.cloudsearchifyDocuments(obj)
  };

  return cloudsearchdomain.uploadDocuments(params, function(err, data) {
    if(err) {
      console.log(err, err.stack);
    }
    else {
      count += data.adds;
      console.log(count + " documents indexed!");
    }
  });
};

// Expecting: [[{product},[1,5,3,..],[{product},[1,5,3,..]...]
// add x, y, z coordinates to products that orients the products in the 3D world based on the popularity of a single product,
// and then webbing related products out from there
var insertModelCoordinates = function(productObjectPlusAdjacencyListArray, products) {
  products = products || [];

  // base case - initially one most popular product with 5 similar products, each with 5 similar, each with similar
  // so 1 + 5 * 5 * 5 = 126
  if(products.length > 126){
    return products;
  }

  productObjectPlusAdjacencyListArray.forEach(function(productObjectPlusAdjacencyList){
    var originalProduct = productObjectPlusAdjacencyList[0];
    var adjacencyList = productObjectPlusAdjacencyList[1];
    var productIndex;

    // add original product to products to return
    originalProduct = addCoordinatesToProduct(originalProduct, 0, 0, 0);
    products.push(originalProduct);

    // dequeue products from adjacency list and add coordinates to those products
    var i = 0;
    while(productIndex = adjacencyList.pop()){
      // assuming 5 products in adjacency list, create the following coordinates:
      //[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0],
      var coordinates = [0,0,0];
      coordinates[i % 3] = i < 3 ? 1 : -1;
      var relatedProductObjectPlusAdjacencyList = productObjectPlusAdjacencyListArray[productIndex];

      // recurse through related product
      insertModelCoordinates(relatedProductObjectPlusAdjacencyList);

      // add coordinates to related product
      relatedProductObjectPlusAdjacencyList = addCoordinatesToProduct(relatedProductObjectPlusAdjacencyList, coordinates[0], coordinates[1], coordinates[2]);

      // add related product from adjacency list to products to return
      products.push(relatedProductObjectPlusAdjacencyList);
      i++;
    }
  });
};

// helper function to add coordinates to product
var addCoordinatesToProduct = function(relatedProductObjectPlusAdjacencyList, x, y, z){
  relatedProductObjectPlusAdjacencyList[0].coordinates = {
    x: x,
    y: y,
    z: z
  };

  return relatedProductObjectPlusAdjacencyList;
};

recurse = function(pageNo) {

  // retrieve products from amazon products api
  amazonProductApi.searchCart(pageNo, function(err, results) {
    var _results = [];
    var r2 = results.ItemSearchResponse.Items[0].Item;

    // Log errors, if any
    if(results.ItemSearchResponse.Items[0].Request[0].Errors) {
      console.log(results.ItemSearchResponse.Items[0].Request[0].Errors[0].Error[0].Message[0]);
    }
    var i = 0;
    var flag = false;
    console.log(r2.length);
    while(r2 && r2[i] && i < r2.length && i < 15) {
      var obj = r2[i];
      var product = {};

      // Sometimes no ItemAttributes Returned
      if(obj.ItemAttributes &&
        obj.ItemAttributes[0].ListPrice &&
        obj.ItemAttributes[0].Title &&
        obj.MediumImage &&
        obj.CustomerReviews &&
        obj.CustomerReviews[0].IFrameURL &&
        obj.Offers &&
        obj.Offers[0].TotalOffers &&
        +obj.Offers[0].TotalOffers[0] > 0 &&
        obj.SimilarProducts &&
        obj.SimilarProducts[0].SimilarProduct) {

        // filter out adult products, like dildos
        if(!(obj.ItemAttributes[0].IsAdultProduct && obj.ItemAttributes[0].IsAdultProduct[0] === "1")) {

          //filter out duplicate 'items' sold by different vendors
          if(!uniqueProductsContainer[obj.ASIN[0]]) {
            uniqueProductsContainer[obj.ASIN[0]] = true;
            // similarHash[obj.ASIN[0]] = obj.SimilarProducts[0].SimilarProduct;
            simArray = simArray.concat(obj.SimilarProducts[0].SimilarProduct.map(function(e) {
              e.linkASIN = obj.ASIN[0];
              e.ASIN = e.ASIN[0];
              return e;
            }));
            // build product entry to return to client
            product.product_id = obj.ASIN[0];
            product.price = parseInt(obj.ItemAttributes[0].ListPrice[0].Amount[0] / 100, 10);
            product.title = obj.ItemAttributes[0].Title[0];
            product.img_url = obj.MediumImage[0].URL[0];
            product.prod_attributes = JSON.stringify(obj.ItemAttributes[0]);
            product.category = obj.ItemAttributes[0].ProductGroup[0];

            // add coordinates to place products on screen as 3d models
            product.x = 0;
            product.y = 0;
            product.z = 0;

            _results.push(product);

            hashCount++;
            flag = true;
            simArray.forEach(function(e, i) {
                if (!(e.ASIN in seenHash)) {
                  seenHash[e.ASIN] = q.length;
                  q.push(e.ASIN);
                  orderedProducts.push(e);
                  // graph.add(e, [])
                }
            });
            //decrement number of total documents we want to return
            numberOfDocuments--;
          }
        }
      }

      //increment counter within while loop
      i++;
    }
    console.log(q.length, "Queue");
    processQ(0);
  });
};

recurse(1);
console.log('recurse');
var someC = 0;
function processQ(index) {
  console.log("ParCount");
  this.index = index;
  var someC = 0;
  q.forEach(function(e) {
    if (!(e in seenHash)) {
      someC++;
    }
  });
  setTimeout(function(){
    var e = q[index];
    var element = q[index]
    console.log("INDEX", e);
    if (!(e in seenHash) || 1){
      amazonProductApi.lookup(e, function(err, results) {
        if (results.ItemLookupErrorResponse && results.ItemLookupErrorResponse.Error) {
          console.log (results.ItemLookupErrorResponse.Error[0])
        }
        else {
          var r2 = results.ItemLookupResponse.Items[0].Item;

          // Log errors, if any
          if(results.ItemLookupResponse.Items[0].Request[0].Errors) {
            console.log(results.ItemLookupResponse.Items[0].Request[0].Errors[0].Error[0].Message[0]);
          }

          var obj = r2[0];
          var product = {};
          // Sometimes no ItemAttributes Returned
          if(obj.ItemAttributes &&
            obj.ItemAttributes[0].ListPrice &&
            obj.ItemAttributes[0].Title &&
            obj.MediumImage &&
            obj.CustomerReviews &&
            obj.CustomerReviews[0].IFrameURL &&
            obj.Offers &&
            obj.Offers[0].TotalOffers &&
            +obj.Offers[0].TotalOffers[0] > 0 &&
            obj.SimilarProducts &&
            obj.SimilarProducts[0].SimilarProduct) {

            // filter out adult products, like dildos
            if(!(obj.ItemAttributes[0].IsAdultProduct && obj.ItemAttributes[0].IsAdultProduct[0] === "1")) {
              var index = this.index;
              //filter out duplicate 'items' sold by different vendors
              if(!uniqueProductsContainer[obj.ASIN[0]]) {
                uniqueProductsContainer[obj.ASIN[0]] = true;
                // similarHash[obj.ASIN[0]] = obj.SimilarProducts[0].SimilarProduct;
                var someFlag = false;
                var simProds = [];
                var newArray = obj.SimilarProducts[0].SimilarProduct.map(function(e) {
                  e.linkASIN = obj.ASIN[0];
                  e.ASIN = e.ASIN[0];
                  simProds.push(e.ASIN)
                  if (!(e.ASIN in seenHash)) {
                    seenHash[e.ASIN] = q.length;
                    someFlag = true;
                    q.push(e.ASIN);
                    orderedProducts.push(e);
                  }
                  return e;
                });
                var simProds = simProds.map(function(e) {
                  return seenHash[e];
                })
                graph.add([orderedProducts[this.index], simProds]);
                exports.indexDocuments(graph.al[graph.al.length - 1]);
                if (!someFlag) {console.log('flag did not toggle')}

                product.product_id = obj.ASIN[0];
                product.price = parseInt(obj.ItemAttributes[0].ListPrice[0].Amount[0] / 100, 10);
                product.title = obj.ItemAttributes[0].Title[0];
                product.img_url = obj.MediumImage[0].URL[0];
                product.prod_attributes = JSON.stringify(obj.ItemAttributes[0]);
                product.category = obj.ItemAttributes[0].ProductGroup[0];

                similarHash[hashCount] = product.product_id;
                similarHT[product.product_id] = hashCount;
                hashCount++;

                //decrement number of total documents we want to return
                numberOfDocuments--;
              }
            }
          }
        }
        console.log('ONE CALL NOW', this.index, q.length);
        processQ(this.index + 1);
      }.bind(this));
    }
  }.bind(this), 1250);
};

<|endoftext|>


<commit_before>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class ListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, disableTouchRipple, rippleDisplayCenter, renderer, itemRenderer, readOnly,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                            <Radio className="list-item-select"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}
                                   disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-select"
                                      theme={selectTheme}
                                      checked={checked}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}
                                      disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
};

ListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.any,
    desc: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    disableTouchRipple: PropTypes.bool,
    rippleDisplayCenter: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.string,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,

    onTouchTap: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

ListItem.defaultProps = {

    index: 0,

    className: null,
    style: null,
    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    data: null,
    value: null,
    text: null,
    desc: null,

    disabled: false,
    isLoading: false,
    disableTouchRipple: false,
    rippleDisplayCenter: false,
    checked: false,
    readOnly: false,

    iconCls: null,
    rightIconCls: null,

    tip: null,
    tipPosition: Position.BOTTOM,

    radioUncheckedIconCls: null,
    radioCheckedIconCls: null,
    checkboxUncheckedIconCls: 'fa fa-square-o',
    checkboxCheckedIconCls: 'fa fa-check-square',
    checkboxIndeterminateIconCls: 'fa fa-minus-square'

};<commit_msg>update ListItem<commit_after>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class ListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;
    static propTypes = {

        index: PropTypes.number,

        className: PropTypes.string,
        style: PropTypes.object,
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
        selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

        data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        text: PropTypes.any,
        desc: PropTypes.string,

        disabled: PropTypes.bool,
        isLoading: PropTypes.bool,
        disableTouchRipple: PropTypes.bool,
        rippleDisplayCenter: PropTypes.bool,
        checked: PropTypes.bool,
        readOnly: PropTypes.bool,

        iconCls: PropTypes.string,
        rightIconCls: PropTypes.string,

        tip: PropTypes.string,
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

        radioUncheckedIconCls: PropTypes.string,
        radioCheckedIconCls: PropTypes.string,
        checkboxUncheckedIconCls: PropTypes.string,
        checkboxCheckedIconCls: PropTypes.string,
        checkboxIndeterminateIconCls: PropTypes.string,

        itemRenderer: PropTypes.func,
        renderer: PropTypes.func,

        onTouchTap: PropTypes.func,
        onSelect: PropTypes.func,
        onDeselect: PropTypes.func,
        onMouseEnter: PropTypes.func,
        onMouseLeave: PropTypes.func

    };
    static defaultProps = {

        index: 0,

        className: null,
        style: null,
        theme: Theme.DEFAULT,

        selectTheme: Theme.DEFAULT,
        selectMode: SelectMode.SINGLE_SELECT,

        data: null,
        value: null,
        text: null,
        desc: null,

        disabled: false,
        isLoading: false,
        disableTouchRipple: false,
        rippleDisplayCenter: false,
        checked: false,
        readOnly: false,

        iconCls: null,
        rightIconCls: null,

        tip: null,
        tipPosition: Position.BOTTOM,

        radioUncheckedIconCls: null,
        radioCheckedIconCls: null,
        checkboxUncheckedIconCls: 'fa fa-square-o',
        checkboxCheckedIconCls: 'fa fa-check-square',
        checkboxIndeterminateIconCls: 'fa fa-minus-square'

    };

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, disableTouchRipple, rippleDisplayCenter, renderer, itemRenderer, readOnly,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                            <Radio className="list-item-select"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}
                                   disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-select"
                                      theme={selectTheme}
                                      checked={checked}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}
                                      disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
};<|endoftext|>


<commit_before>app.Layout = class {
    /**
     * @param {!Map<!app.Person, !g.Vec} positions
     * @param {!Map<!app.Person, number} rotations
     * @param {!Array<!Object>} scaffolding
     * @param {number} personRadius
     */
    constructor(positions, rotations, scaffolding, personRadius) {
        this.positions = positions;
        this.rotations = rotations;
        this.scaffolding = scaffolding;
        this.personRadius = personRadius;
    }

    /**
     * @return {x: number, y: number, width: number, height: number}
     */
    boundingBox() {
        if (this._boundingBox)
            return this._boundingBox;
        if (!this.positions.size)
            return {x: 0, y: 0, width: 0, height: 0};
        var minX = Infinity;
        var minY = Infinity;
        var maxX = -Infinity;
        var maxY = -Infinity;
        for (var position of this.positions.values()) {
            minX = Math.min(minX, position.x);
            minY = Math.min(minY, position.y);
            maxX = Math.max(maxX, position.x);
            maxY = Math.max(maxY, position.y);
        }
        return {x: minX, y: minY, width: maxX - minX, height: maxY - minY};
    }

    /**
     * @return {!app.Layout}
     */
    static empty() {
        return new app.Layout(new Map(), new Map(), [], 0);
    }
}

app.LayoutEngine = class extends EventEmitter {
    /**
     * @return {boolean}
     */
    isDirty() { }

    /**
     * @return {!app.Layout}
     */
    layout() { }
}

app.LayoutEngine.Events = {
    LayoutRecalculated: Symbol("LayoutRecalculated"),
}

app.SunLayout = class extends app.LayoutEngine {
    constructor() {
        super();
        this._familyTree = null;
        this._size = 600;
        this._nodeRadius = 25;
        this._overlap = 0;
        this._initialRotation = 0;

        /** @type {!Map<!app.Person, number>} */
        this._subtreeSize = new Map();
        /** @type {!Map<!app.Person, number>} */
        this._subtreeDepth = new Map();
        /** @type {!Map<!app.Person, number>} */
        this._nodeDepth = new Map();

        this._isDirty = false;
        /** @type {!app.Layout} */
        this._lastLayout = app.Layout.empty();
    }

    /**
     * @return {boolean}
     */
    isDirty() {
        return this._isDirty;
    }

    /**
     * @param {!app.FamilyTree} familyTree
     */
    setFamilyTree(familyTree) {
        this._isDirty = true;
        this._familyTree = familyTree;
        this._initializeSubtreeSizesAndDepths();
    }

    /**
     * @param {number} radius
     */
    setPersonRadius(radius) {
        if (g.eq(radius, this._nodeRadius))
            return;
        this._nodeRadius = radius;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    personRadius() {
        return this._nodeRadius;
    }

    setInitialRotation(rotation) {
        if (g.eq(rotation, this._initialRotation))
            return;
        this._initialRotation = rotation;
        this._isDirty = true;
    }

    initialRotation() {
        return this._initialRotation;
    }

    /**
     * @param {number} size
     */
    setSize(size) {
        if (g.eq(size, this._size))
            return;
        this._size = size;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    size() {
        return this._size;
    }

    /**
     * @param {number} overlap
     */
    setOverlap(overlap) {
        if (g.eq(overlap, this._overlap))
            return;
        this._overlap = overlap;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    overlap() {
        return this._overlap;
    }

    /**
     * @return {number}
     */
    _depthRadiusStep() {
        return (this._size - this._nodeRadius) / 2 / this._familyTreeDepth;
    }

    /**
     * @return {!app.Layout}
     */
    layout() {
        if (!this._isDirty)
            return this._lastLayout;
        this._isDirty = false;
        if (!this._familyTree) {
            this._lastLayout = app.Layout.empty();
            return this._lastLayout;
        }
        var rotations = this._computeRotations();
        var positions = this._computePositions(rotations);
        var scaffolding = this._computeScaffolding(rotations, positions);
        this._lastLayout = new app.Layout(positions, rotations, scaffolding, this._nodeRadius);
        this.dispatch(app.LayoutEngine.Events.LayoutRecalculated);
        return this._lastLayout;
    }

    _computeScaffolding(rotations, positions) {
        var scaffolding = [];

        // Render family arcs.
        for (var node of rotations.keys()) {
            if (!this._familyTree.isFamilyMain(node))
                continue;
            for (var family of this._familyTree.families(node)) {
                var familyR = this._nodeDepth.get(family.main) * this._depthRadiusStep();
                if (family.alt) {
                    var rotation1 = rotations.get(family.main);
                    var rotation2 = rotations.get(family.alt);
                    scaffolding.push(new g.Arc(g.zeroVec, familyR, Math.min(rotation1, rotation2), Math.max(rotation1, rotation2)));
                }
                var children = family.children;
                if (!children.size)
                    continue;
                var r = (this._nodeDepth.get(node) + 1) * this._depthRadiusStep();
                var min = Infinity;
                var max = -Infinity;
                for (var child of children) {
                    var angle = rotations.get(child);
                    min = Math.min(min, angle);
                    max = Math.max(max, angle);
                }

                var familyMiddle = rotations.get(family.main);
                if (family.alt)
                    familyMiddle = (familyMiddle + rotations.get(family.alt)) / 2;
                if (g.eq(min, max)) {
                    // There is a sole child. Render a level join straight to it.
                    var joinStart = g.Vec.fromRadial(familyR, familyMiddle);
                    var joinEnd = g.Vec.fromRadial(r, familyMiddle);
                    scaffolding.push(new g.Line(joinStart, joinEnd));
                    continue;
                }

                var offset = this._nodeRadius * 2;
                // Level join.
                var joinStart = g.Vec.fromRadial(familyR, familyMiddle);
                var joinEnd = g.Vec.fromRadial(r - offset, familyMiddle);
                scaffolding.push(new g.Line(joinStart, joinEnd));

                // Do scaffolding.
                var totalArcLength = g.segmentRadToLength(r - offset, max - min);
                var maxCurveRadius = this._nodeRadius;
                var curveRadius = Math.min(totalArcLength / 2, maxCurveRadius);
                var curveOffsetRad = g.segmentLengthToRad(r - offset, curveRadius);

                var start1 = g.Vec.fromRadial(r - offset, min + curveOffsetRad);
                var start2 = g.Vec.fromRadial(r - offset, max - curveOffsetRad);
                var end1 = g.Vec.fromRadial(r - offset + curveRadius, min);
                var end2 = g.Vec.fromRadial(r - offset + curveRadius, max);
                var cp1 = g.Vec.fromRadial(r - offset, min);
                var cp2 = g.Vec.fromRadial(r - offset, max);
                scaffolding.push(new g.Arc(g.zeroVec, r - offset, min + curveOffsetRad, max - curveOffsetRad));
                scaffolding.push(new g.Bezier(start1, end1, cp1));
                scaffolding.push(new g.Bezier(start2, end2, cp2));

                for (var child of children) {
                    var rotation = rotations.get(child);
                    var from = g.Vec.fromRadial(r - offset, rotation);
                    if (g.eq(rotation, min))
                        from = end1;
                    else if (g.eq(rotation, max))
                        from = end2;
                    scaffolding.push(new g.Line(from, g.Vec.fromRadial(r, rotation)));
                }
            }
        }
        return scaffolding;
    }

    /**
     * @param {!Map<!app.Person, number>} rotations
     * @return {!Map<!app.Person, !g.Vec>}
     */
    _computePositions(rotations) {
        var positions = new Map();
        positions.set(this._familyTree.root(), new g.Vec(0, 0));
        for (var node of rotations.keys()) {
            var r = this._nodeDepth.get(node) * this._depthRadiusStep();
            var angle = rotations.get(node);
            positions.set(node, g.Vec.fromRadial(r, angle));
        }
        return positions;
    }

    /**
     * @return {!Map<!app.Person, number>}
     */
    _computeRotations() {
        /** @type {!Map<!app.Person, number>} */
        var rotations = new Map();
        var leafs = [];
        populateLeafNodes.call(this, this._familyTree.root(), leafs);

        // assign initial positions to leafs.
        var required = 0;
        for (var i = 1; i < leafs.length; ++i)
            required += minAngleForNode.call(this, leafs[i]);
        var total = 2 * Math.PI + this._overlap;
        var free = total - required;
        var freeQuant = free / leafs.length;

        var last = this._initialRotation;
        setNodeRotations.call(this, leafs[0], last);
        for (var i = 1; i < leafs.length; ++i) {
            var prevLeaf = leafs[i - 1];
            var leaf = leafs[i];
            var value = last + minAngleForNode.call(this, prevLeaf) + freeQuant;
            setNodeRotations.call(this, leaf, value);
            last = value;
        }
        layoutTree.call(this, this._familyTree.root());
        return rotations;

        /**
         * @param {!app.Person] node
         */
        function layoutTree(node) {
            if (rotations.has(node))
                return;
            for (var child of node.children)
                layoutTree.call(this, child);
            var min = Infinity;
            var max = -Infinity;
            var families = Array.from(this._familyTree.families(node));
            if (families.length === 2) {
                for (var child of families[0].children)
                    max = Math.max(max, rotations.get(child));
                for (var child of families[1].children)
                    min = Math.min(min, rotations.get(child));
            } else {
                for (var child of node.children) {
                    min = Math.min(min, rotations.get(child));
                    max = Math.max(max, rotations.get(child));
                }
            }
            setNodeRotations.call(this, node, (min + max) / 2);
        }

        function setNodeRotations(node, nodeRotation) {
            var families = Array.from(this._familyTree.families(node));
            var span = minAngleForNode.call(this, node);
            if (families.length === 1) {
                if (families[0].alt) {
                    rotations.set(families[0].main, nodeRotation + span / 4);
                    rotations.set(families[0].alt, nodeRotation - span / 4);
                } else {
                    rotations.set(families[0].main, nodeRotation);
                }
            } else {
                rotations.set(node, nodeRotation);
                if (families[0].alt)
                    rotations.set(families[0].alt, nodeRotation - span / 3);
                if (families[1].alt)
                    rotations.set(families[1].alt, nodeRotation + span / 3);
            }
        }

        /**
         * @param {!app.Person} node
         * @param {!Array<!app.Person>} leafs
         */
        function populateLeafNodes(node, leafs) {
            if (!node.children.size) {
                leafs.push(node);
                return;
            }
            for (var family of this._familyTree.families(node)) {
                var children = Array.from(family.children);
                children.sort(childComparator.bind(this));
                for (var child of children)
                    populateLeafNodes.call(this, child, leafs);
            }
        }

        /**
         * @param {!app.Person} a
         * @param {!app.Person} b
         * @return {number}
         */
        function childComparator(a, b) {
            return this._subtreeSize.get(a) - this._subtreeSize.get(b);
        }

        /**
         * @param {!app.Person} node
         * @return {number}
         */
        function minAngleForNode(node) {
            var radius = this._nodeDepth.get(node) * this._depthRadiusStep();
            var peopleCount = 1 + node.partners.size;
            return g.segmentLengthToRad(radius, peopleCount * this._nodeRadius * 3);
        }
    }

    /**
     * @param {!app.FamilyTree} FamilyTree
     */
    _initializeSubtreeSizesAndDepths() {
        this._nodeDepth.clear();
        this._subtreeSize.clear();
        this._subtreeDepth.clear();
        dfs.call(this, this._familyTree.root(), 0);
        this._familyTreeDepth = this._subtreeDepth.get(this._familyTree.root());

        /**
         * @param {!app.Person} node
         * @param {number} depth
         */
        function dfs(node, depth) {
            var subtreeSize = 1;
            var subtreeDepth = 1;
            for (var child of node.children) {
                dfs.call(this, child, depth + 1);
                subtreeDepth = Math.max(subtreeDepth, 1 + this._subtreeDepth.get(child));
                subtreeSize += this._subtreeSize.get(child);
            }
            this._subtreeDepth.set(node, subtreeDepth);
            this._subtreeSize.set(node, subtreeSize);
            this._nodeDepth.set(node, depth);
            for (var partner of node.partners) {
                this._nodeDepth.set(partner, depth);
                this._subtreeDepth.set(partner, subtreeDepth);
                this._subtreeSize.set(partner, subtreeSize);
            }
        }
    }
}

<commit_msg>support two partners<commit_after>app.Layout = class {
    /**
     * @param {!Map<!app.Person, !g.Vec} positions
     * @param {!Map<!app.Person, number} rotations
     * @param {!Array<!Object>} scaffolding
     * @param {number} personRadius
     */
    constructor(positions, rotations, scaffolding, personRadius) {
        this.positions = positions;
        this.rotations = rotations;
        this.scaffolding = scaffolding;
        this.personRadius = personRadius;
    }

    /**
     * @return {x: number, y: number, width: number, height: number}
     */
    boundingBox() {
        if (this._boundingBox)
            return this._boundingBox;
        if (!this.positions.size)
            return {x: 0, y: 0, width: 0, height: 0};
        var minX = Infinity;
        var minY = Infinity;
        var maxX = -Infinity;
        var maxY = -Infinity;
        for (var position of this.positions.values()) {
            minX = Math.min(minX, position.x);
            minY = Math.min(minY, position.y);
            maxX = Math.max(maxX, position.x);
            maxY = Math.max(maxY, position.y);
        }
        return {x: minX, y: minY, width: maxX - minX, height: maxY - minY};
    }

    /**
     * @return {!app.Layout}
     */
    static empty() {
        return new app.Layout(new Map(), new Map(), [], 0);
    }
}

app.LayoutEngine = class extends EventEmitter {
    /**
     * @return {boolean}
     */
    isDirty() { }

    /**
     * @return {!app.Layout}
     */
    layout() { }
}

app.LayoutEngine.Events = {
    LayoutRecalculated: Symbol("LayoutRecalculated"),
}

app.SunLayout = class extends app.LayoutEngine {
    constructor() {
        super();
        this._familyTree = null;
        this._size = 600;
        this._nodeRadius = 25;
        this._overlap = 0;
        this._initialRotation = 0;

        /** @type {!Map<!app.Person, number>} */
        this._subtreeSize = new Map();
        /** @type {!Map<!app.Person, number>} */
        this._subtreeDepth = new Map();
        /** @type {!Map<!app.Person, number>} */
        this._nodeDepth = new Map();

        this._isDirty = false;
        /** @type {!app.Layout} */
        this._lastLayout = app.Layout.empty();
    }

    /**
     * @return {boolean}
     */
    isDirty() {
        return this._isDirty;
    }

    /**
     * @param {!app.FamilyTree} familyTree
     */
    setFamilyTree(familyTree) {
        this._isDirty = true;
        this._familyTree = familyTree;
        this._initializeSubtreeSizesAndDepths();
    }

    /**
     * @param {number} radius
     */
    setPersonRadius(radius) {
        if (g.eq(radius, this._nodeRadius))
            return;
        this._nodeRadius = radius;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    personRadius() {
        return this._nodeRadius;
    }

    setInitialRotation(rotation) {
        if (g.eq(rotation, this._initialRotation))
            return;
        this._initialRotation = rotation;
        this._isDirty = true;
    }

    initialRotation() {
        return this._initialRotation;
    }

    /**
     * @param {number} size
     */
    setSize(size) {
        if (g.eq(size, this._size))
            return;
        this._size = size;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    size() {
        return this._size;
    }

    /**
     * @param {number} overlap
     */
    setOverlap(overlap) {
        if (g.eq(overlap, this._overlap))
            return;
        this._overlap = overlap;
        this._isDirty = true;
    }

    /**
     * @return {number}
     */
    overlap() {
        return this._overlap;
    }

    /**
     * @return {number}
     */
    _depthRadiusStep() {
        return (this._size - this._nodeRadius) / 2 / this._familyTreeDepth;
    }

    /**
     * @return {!app.Layout}
     */
    layout() {
        if (!this._isDirty)
            return this._lastLayout;
        this._isDirty = false;
        if (!this._familyTree) {
            this._lastLayout = app.Layout.empty();
            return this._lastLayout;
        }
        var rotations = this._computeRotations();
        var positions = this._computePositions(rotations);
        var scaffolding = this._computeScaffolding(rotations, positions);
        this._lastLayout = new app.Layout(positions, rotations, scaffolding, this._nodeRadius);
        this.dispatch(app.LayoutEngine.Events.LayoutRecalculated);
        return this._lastLayout;
    }

    _computeScaffolding(rotations, positions) {
        var scaffolding = [];

        // Render family arcs.
        for (var node of rotations.keys()) {
            if (!this._familyTree.isFamilyMain(node))
                continue;
            var families = Array.from(this._familyTree.families(node));
            for (var i = 0; i < families.length; ++i) {
                var family = families[i];
                var familyR = this._nodeDepth.get(family.main) * this._depthRadiusStep();
                if (family.alt) {
                    var rotation1 = rotations.get(family.main);
                    var rotation2 = rotations.get(family.alt);
                    scaffolding.push(new g.Arc(g.zeroVec, familyR, Math.min(rotation1, rotation2), Math.max(rotation1, rotation2)));
                }
                var children = family.children;
                if (!children.size)
                    continue;
                var r = (this._nodeDepth.get(node) + 1) * this._depthRadiusStep();
                var min = Infinity;
                var max = -Infinity;
                for (var child of children) {
                    var angle = rotations.get(child);
                    min = Math.min(min, angle);
                    max = Math.max(max, angle);
                }

                var familyMiddle = rotations.get(family.main);
                if (family.alt)
                    familyMiddle = (familyMiddle + rotations.get(family.alt)) / 2;
                if (g.eq(min, max) && families.length !== 2) {
                    // There is a sole child. Render a level join straight to it.
                    var joinStart = g.Vec.fromRadial(familyR, familyMiddle);
                    var joinEnd = g.Vec.fromRadial(r, familyMiddle);
                    scaffolding.push(new g.Line(joinStart, joinEnd));
                    continue;
                }

                var offset = this._nodeRadius * 2;

                var tip1 = 1;
                var tip2 = 1;
                if (families.length === 2) {
                    if (i === 0) {
                        tip2 = -1;
                        max = familyMiddle;
                    } else {
                        tip1 = -1;
                        min = familyMiddle;
                    }
                }
                var [end1, end2] = curvyArc.call(this, r - offset, min, max, tip1, tip2);

                // Level join.
                var joinStart = g.Vec.fromRadial(familyR, familyMiddle);
                var joinEnd = g.Vec.fromRadial(r - offset, familyMiddle);
                if (tip1 === -1)
                    joinEnd = end1;
                else if (tip2 === -1)
                    joinEnd = end2;
                scaffolding.push(new g.Line(joinStart, joinEnd));

                for (var child of children) {
                    var rotation = rotations.get(child);
                    var from = g.Vec.fromRadial(r - offset, rotation);
                    if (g.eq(rotation, min))
                        from = end1;
                    else if (g.eq(rotation, max))
                        from = end2;
                    scaffolding.push(new g.Line(from, g.Vec.fromRadial(r, rotation)));
                }
            }
        }
        return scaffolding;

        function curvyArc(r, from, to, curvyTip1, curvyTip2) {
            var totalArcLength = g.segmentRadToLength(r, to - from);
            var maxCurveRadius = this._nodeRadius;
            var curveRadius = Math.min(totalArcLength / 2, maxCurveRadius);
            var curveOffsetRad = g.segmentLengthToRad(r, curveRadius);

            var start1 = g.Vec.fromRadial(r, min + curveOffsetRad);
            var start2 = g.Vec.fromRadial(r, max - curveOffsetRad);
            var end1 = g.Vec.fromRadial(r + curveRadius * curvyTip1, min);
            var end2 = g.Vec.fromRadial(r + curveRadius * curvyTip2, max);
            var cp1 = g.Vec.fromRadial(r, min);
            var cp2 = g.Vec.fromRadial(r, max);
            scaffolding.push(new g.Arc(g.zeroVec, r, min + curveOffsetRad, max - curveOffsetRad));
            scaffolding.push(new g.Bezier(start1, end1, cp1));
            scaffolding.push(new g.Bezier(start2, end2, cp2));
            return [end1, end2];
        }
    }

    /**
     * @param {!Map<!app.Person, number>} rotations
     * @return {!Map<!app.Person, !g.Vec>}
     */
    _computePositions(rotations) {
        var positions = new Map();
        positions.set(this._familyTree.root(), new g.Vec(0, 0));
        for (var node of rotations.keys()) {
            var r = this._nodeDepth.get(node) * this._depthRadiusStep();
            var angle = rotations.get(node);
            positions.set(node, g.Vec.fromRadial(r, angle));
        }
        return positions;
    }

    /**
     * @return {!Map<!app.Person, number>}
     */
    _computeRotations() {
        /** @type {!Map<!app.Person, number>} */
        var rotations = new Map();
        var leafs = [];
        populateLeafNodes.call(this, this._familyTree.root(), leafs);

        // assign initial positions to leafs.
        var required = 0;
        for (var i = 1; i < leafs.length; ++i)
            required += minAngleForNode.call(this, leafs[i]);
        var total = 2 * Math.PI + this._overlap;
        var free = total - required;
        var freeQuant = free / leafs.length;

        var last = this._initialRotation;
        setNodeRotations.call(this, leafs[0], last);
        for (var i = 1; i < leafs.length; ++i) {
            var prevLeaf = leafs[i - 1];
            var leaf = leafs[i];
            var value = last + minAngleForNode.call(this, prevLeaf) + freeQuant;
            setNodeRotations.call(this, leaf, value);
            last = value;
        }
        layoutTree.call(this, this._familyTree.root());
        return rotations;

        /**
         * @param {!app.Person] node
         */
        function layoutTree(node) {
            if (rotations.has(node))
                return;
            for (var child of node.children)
                layoutTree.call(this, child);
            var min = Infinity;
            var max = -Infinity;
            var families = Array.from(this._familyTree.families(node));
            if (families.length === 2) {
                for (var child of families[0].children)
                    max = Math.max(max, rotations.get(child));
                for (var child of families[1].children)
                    min = Math.min(min, rotations.get(child));
            } else {
                for (var child of node.children) {
                    min = Math.min(min, rotations.get(child));
                    max = Math.max(max, rotations.get(child));
                }
            }
            setNodeRotations.call(this, node, (min + max) / 2);
        }

        function setNodeRotations(node, nodeRotation) {
            var families = Array.from(this._familyTree.families(node));
            var span = minAngleForNode.call(this, node);
            if (families.length === 1) {
                if (families[0].alt) {
                    rotations.set(families[0].main, nodeRotation + span / 4);
                    rotations.set(families[0].alt, nodeRotation - span / 4);
                } else {
                    rotations.set(families[0].main, nodeRotation);
                }
            } else {
                rotations.set(node, nodeRotation);
                if (families[0].alt)
                    rotations.set(families[0].alt, nodeRotation - span / 3);
                if (families[1].alt)
                    rotations.set(families[1].alt, nodeRotation + span / 3);
            }
        }

        /**
         * @param {!app.Person} node
         * @param {!Array<!app.Person>} leafs
         */
        function populateLeafNodes(node, leafs) {
            if (!node.children.size) {
                leafs.push(node);
                return;
            }
            for (var family of this._familyTree.families(node)) {
                var children = Array.from(family.children);
                children.sort(childComparator.bind(this));
                for (var child of children)
                    populateLeafNodes.call(this, child, leafs);
            }
        }

        /**
         * @param {!app.Person} a
         * @param {!app.Person} b
         * @return {number}
         */
        function childComparator(a, b) {
            return this._subtreeSize.get(a) - this._subtreeSize.get(b);
        }

        /**
         * @param {!app.Person} node
         * @return {number}
         */
        function minAngleForNode(node) {
            var radius = this._nodeDepth.get(node) * this._depthRadiusStep();
            var peopleCount = 1 + node.partners.size;
            return g.segmentLengthToRad(radius, peopleCount * this._nodeRadius * 3);
        }
    }

    /**
     * @param {!app.FamilyTree} FamilyTree
     */
    _initializeSubtreeSizesAndDepths() {
        this._nodeDepth.clear();
        this._subtreeSize.clear();
        this._subtreeDepth.clear();
        dfs.call(this, this._familyTree.root(), 0);
        this._familyTreeDepth = this._subtreeDepth.get(this._familyTree.root());

        /**
         * @param {!app.Person} node
         * @param {number} depth
         */
        function dfs(node, depth) {
            var subtreeSize = 1;
            var subtreeDepth = 1;
            for (var child of node.children) {
                dfs.call(this, child, depth + 1);
                subtreeDepth = Math.max(subtreeDepth, 1 + this._subtreeDepth.get(child));
                subtreeSize += this._subtreeSize.get(child);
            }
            this._subtreeDepth.set(node, subtreeDepth);
            this._subtreeSize.set(node, subtreeSize);
            this._nodeDepth.set(node, depth);
            for (var partner of node.partners) {
                this._nodeDepth.set(partner, depth);
                this._subtreeDepth.set(partner, subtreeDepth);
                this._subtreeSize.set(partner, subtreeSize);
            }
        }
    }
}

<|endoftext|>