~qeef/damn-client.js

d13b212ea2f17f47f7ae466248d34bc02d915d27 — Jiri Vlasak 4 months ago a8c0e53 + 41f567c v0.28.0
Merge branch 'mappy'
8 files changed, 603 insertions(+), 17 deletions(-)

M damn-api.js
M def.css
M gen_static_js_client.sh
M html.js
M i2.html
M qa.html
M route-common.js
A route-mappy.js
M damn-api.js => damn-api.js +27 -0
@@ 28,6 28,9 @@ API:
    D.s.get_finished_area(fn, cb, fcb)
    D.s.put_area(area, cb, fcb)

    D.s.get_area_geometry(aid, cb, fcb)
    D.s.get_area_square_gpx(aid, sid, cb, fcb)

    D.s.get_commits(aid, cb, fcb)
    D.s.post_commit(commit, aid, cb, fcb)
    D.s.get_squares(aid, cb, fcb)


@@ 85,6 88,9 @@ var damn_api = function(current_url, finished_url) {
    var is_aid = function(aid) {
        return aid && isFinite(aid) && aid >= 1000 && aid <= 9999;
    };
    var is_sid = function(sid) {
        return sid && isFinite(sid) && sid > 0;
    };
    var update_thin_area = function(r) {
        var a = areas[r.aid];
        a.tags = r.tags;


@@ 171,6 177,19 @@ var damn_api = function(current_url, finished_url) {
        };
        req.send(JSON.stringify(obj));
    }
    var ajax_get = function (url, cb, fcb) {
        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.onreadystatechange = function () {
            var done = 4, ok = 200;
            if (req.readyState === done && req.status === ok && req.response) {
                cb(req.response);
            } else if (req.readyState === done && typeof fcb === "function") {
                fcb(req.response);
            }
        };
        req.send(null);
    };
    // API
    var areas = {};
    var geometries = {};


@@ 317,6 336,14 @@ var damn_api = function(current_url, finished_url) {
    that.put_area = function(area, cb, fcb) {
        ajaj_put(ep("/area/" + area.aid), area, cb, fcb);
    };
    that.get_area_geometry = function(aid, cb, fcb) {
        if (!is_aid(aid)) { return; }
        ajax_get(ep("/area/" + aid + "/geometry"), cb, fcb);
    };
    that.get_area_square_gpx = function(aid, sid, cb, fcb) {
        if (!is_aid(aid) || !is_sid(sid)) { return; }
        ajax_get(ep("/area/" + aid + "/square/" + sid + "/gpx"), cb, fcb);
    };
    that.get_commits = function(aid, cb, fcb) {
        if (!is_aid(aid)) { return; }
        var since = "2020-01-01T00:00:00.0";

M def.css => def.css +5 -1
@@ 89,6 89,10 @@ polygon.border {
    border: var(--text_border_color);
    fill: var(--text_border_color);
}
polygon.square {
    border: var(--to_map);
    stroke: var(--to_map);
}
path { fill: none; }
text { fill: var(--to_map); }



@@ 99,7 103,7 @@ div#footer {
    text-align: center;
    margin-top: 4px;
}
div#square_menu {
div#mouse-menu {
    position: absolute;
    display: hidden;
    z-index: 10;

M gen_static_js_client.sh => gen_static_js_client.sh +10 -0
@@ 69,6 69,16 @@ case "$1" in
        > $WHERE/panel/index.html
        version panel >> $WHERE/panel/index.html

        [ -d $WHERE/mappy ] || mkdir $WHERE/mappy
        base 'Mappy' \
        | custom_js 'damn-api' \
        | custom_js 'L' \
        | custom_js 'html' \
        | custom_js 'route-common' \
        | custom_js 'route-mappy' \
        > $WHERE/mappy/index.html
        version mappy >> $WHERE/mappy/index.html

        [ -d $WHERE/intersecting ] || mkdir $WHERE/intersecting
        base 'Intersecting Areas' \
        | custom_js 'damn-api' \

M html.js => html.js +429 -12
@@ 6,12 6,189 @@ H.set = function(id, w) {
H.ac = function(id, w) {
    if (H.ge(id)) { H.ge(id).addEventListener("click", w); }
};
H.amd = function(id, w) {
    if (H.ge(id)) { H.ge(id).addEventListener("mousedown", w); }
};
H.amu = function(id, w) {
    if (H.ge(id)) { H.ge(id).addEventListener("mouseup", w); }
};
H.ach = function(id, w) {
    if (H.ge(id)) { H.ge(id).addEventListener("change", w); }
};
H.rc = function(id, w) {
    if (H.ge(id)) { H.ge(id).removeEventListener("click", w); }
};
H.prevent_def = function(e) { e.preventDefault(); };
H.enable_mouse_menu = function() {
    document.addEventListener("contextmenu", H.prevent_def);
};
H.disable_mouse_menu = function() {
    document.removeEventListener("contextmenu", H.prevent_def);
};
H.show_mouse_menu = function(e, aid, sid) {
    e = e || window.event;
    if (!(e.button === 2 || e.which === 3)) { return false; }//right click only
    if (!aid || !sid) { return false; }
    var li = function(aid, sid, type) {
        var lt;
        lt = "javascript:H.post_commit(";
        if (type == "get square GPX") {
            lt = "javascript:H.download(";
        }
        lt += aid + ", " + sid + ", \"" + type + "\");";
        return "<li><a href='" + lt + "' title='" + type + "'>"
        + type + "</a></li>";
    };
    var o = function(w) {
        return "<option value='" + w + "'>" + w + "</option>";
    }
    var mm = H.ge("mouse-menu");
    mm.style.display = "block";
    mm.style.left = e.pageX + "px";
    mm.style.top = e.pageY + "px";
    mm.innerHTML = "<ul>"
    + li(aid, sid, "lock")
    + li(aid, sid, "lock & JOSM")
    + li(aid, sid, "lock & iD")
    + li(aid, sid, "lock & RapiD")
    + "</ul><ul>"
    + li(aid, sid, "needs mapping")
    + li(aid, sid, "needs review")
    + li(aid, sid, "is done")
    + "</ul><ul>"
    + li(aid, sid, "merge")
    + li(aid, sid, "split")
        + H.itv_tinput_s6_nd("ma-grid-cntx", "number of columns, 1 to 9", "2")
        + H.itv_tinput_s6_nd("ma-grid-cnty", "number of rows, 1 to 9", "2")
    + "</ul><ul>"
    + li(aid, sid, "get square GPX")
    + "</ul>";
};
H.post_commit = function(aid, sid, type) {
    H.hide_mouse_menu();
    var c = {}, f;
    if (sid) { c.sid = sid; }
    switch (type) {
    case "lock & JOSM":
        c.type = "lock";
        f = function(rsquare) {
            H.open_mapper_editor(H.lt_editor(aid, rsquare, "josm"));
            var close_josm_window, f = function() {
                if (D.mw_mapper_editor) { D.mw_mapper_editor.close(); }
                clearTimeout(close_josm_window);
            };
            close_josm_window = setInterval(f, 1000);
        };
        break;
    case "lock & iD":
        c.type = "lock";
        f = function(rsquare) {
            H.open_mapper_editor(H.lt_editor(aid, rsquare, "id"));
        };
        break;
    case "lock & RapiD":
        c.type = "lock";
        f = function(rsquare) {
            H.open_mapper_editor(H.lt_editor(aid, rsquare, "rapid"));
        };
        break;
    case "split":
        c.type = "split";
        cntx = H.ge("ma-grid-cntx");
        cnty = H.ge("ma-grid-cnty");
        if (cntx || cnty) {
            c.grid = {};
            if (cntx) {
                c.grid.cntx = Math.min(9, Math.max(1, Math.round(cntx.value)));
            }
            if (cnty) {
                c.grid.cnty = Math.min(9, Math.max(1, Math.round(cnty.value)));
            }
        }
        break;
    default:
        c.type = type;
    }
    D.s.post_commit(c, aid, H.commit_posted(f));
};
H.download = function(aid, sid, type) {
    H.hide_mouse_menu();
    var f;
    switch (type) {
    case "get square GPX":
        f = function(r) {
            var data = new Blob([r], {type: "text/xml"});
            var url = window.URL.createObjectURL(data);
            var a = document.createElement("a");
            a.href = url;
            a.download = "square.gpx";
            a.click();
            window.URL.revokeObjectURL(url);
        };
        D.s.get_area_square_gpx(aid, sid, f);
        break;
    case "area GeoJSON":
        f = function(r) {
            var data = new Blob([r], {type: "application/json"});
            var url = window.URL.createObjectURL(data);
            var a = document.createElement("a");
            a.href = url;
            a.download = "area.geojson";
            a.click();
            window.URL.revokeObjectURL(url);
        };
        D.s.get_area_geometry(aid, f);
        break;
    }
};
H.commit_posted = function(cb) {
    return function(r) {
        if (cb) { cb(r); }
        R.route_again();
    };
};
H.hide_mouse_menu = function() {
    if (H.ge("mouse-menu")) { H.ge("mouse-menu").style.display = "none"; }
};
H.zoom_from = function(e) {
    e = e || window.event;
    D.m.mousex = e.pageX;
    D.m.mousey = e.pageY;
    H.hide_mouse_menu();
};
H.zoom_to = function(e) {
    e = e || window.event;
    if (!(e.button === 0 || e.which === 1)) { return false; }// left click only
    var dx = Math.abs(e.pageX - D.m.mousex);
    var dy = Math.abs(e.pageY - D.m.mousey);
    var ed = Math.sqrt(dx*dx + dy*dy);
    var sm = document.getElementById("svg-map");
    var sx, sy, tx, ty;
    if (ed < 10) {
        sm.style.transform = "scale(1)";
        D.m.lastsx = 1;
        D.m.lastsy = 1;
        D.m.lasttx = 0;
        D.m.lastty = 0;
    } else {
        sx = D.m.lastsx * sm.width.baseVal.value / dx;
        sy = D.m.lastsy * sm.height.baseVal.value / dy;
        tx = D.m.lasttx
        + Math.min(D.m.mousex, e.pageX) / D.m.lastsx
        - sm.parentNode.offsetLeft / D.m.lastsx;
        ty = D.m.lastty
        + Math.min(D.m.mousey, e.pageY) / D.m.lastsy
        - sm.parentNode.offsetTop / D.m.lastsy;
        sm.style.transform = ""
        + " scaleX(" + sx + ")" + " scaleY(" + sy + ")"
        + " translateX(" + (-1.0*tx) + "px)"
        + " translateY(" + (-1.0*ty) + "px)";
        D.m.lastsx = sx;
        D.m.lastsy = sy;
        D.m.lasttx = tx;
        D.m.lastty = ty;
    }
};
H.filter = function(fv) {
    if (typeof fv !== "string" || fv === "") {
        return;


@@ 416,6 593,56 @@ H.h_panel_footer = function() {
H.h_mapper_stats_menu = function(aid) {
    return "<a href='#area=" + aid + "&stats'>show stats graph</a>";
};
H.h_mappy_menu = function(to) {
    var aid = to.area;
    var w = Object.keys(to)[1];
    var i, h = "";
    var menu = [
        "stats",
        "desc",
        "info",
    ];
    var link = function(w) {
        return "<a href='#area=" + aid + "&" + w + "'>" + w + "</a>";
    };
    if (!D.s.user()) { h += H.lt_auth() + " "; }
    if (!w || !menu.includes(w)) {
        h += "map";
    } else {
        h += "<a href='#area=" + aid + "'>map</a>";
    }
    for (i = 0; i < menu.length; i++) {
        h += " ";
        if (w == menu[i]) {
            h += menu[i];
        } else {
            h += link(menu[i]);
        }
    }
    return h;
};
H.h_mappy_info_menu = function(aid) {
    return "<ul>"
    + "<li><a href='javascript:H.download("
    + aid + ", 0, \"area GeoJSON\");'"
    + H.tt("download area boundary GeoJSON")
    + ">download area GeoJSON</a>"
    + "</li>"
    + "</ul>";
};
H.h_mouse_menu = function() {
    return "<div id='mouse-menu'></div>";
};
H.h_mappy_osm_contrib = function() {
    return "Maps & Data (c) OpenStreetMap.org contributors";
};
H.h_mappy_howto = function() {
    return "<h3>how to mappy</h3><ul>"
    + "<li>push and move left mouse button to zoom in</li>"
    + "<li>click left mouse button to zoom to the default</li>"
    + "<li>click right mouse button for square context menu</li>"
    + "</ul>";
};
H.h_body_max_width_679 = function() {
    return "<style>"
    + "body {"


@@ 622,6 849,10 @@ H.itv_tinput_s6 = function(id, title, value) {
    return "<input type='text' size='6'"
    + H.tid(id) + H.tt(title) + H.tp(title) + H.tv(value) + " disabled />";
};
H.itv_tinput_s6_nd = function(id, title, value) {
    return "<input type='text' size='6'"
    + H.tid(id) + H.tt(title) + H.tp(title) + H.tv(value) + " />";
};

H.itv_ta = function(id, title, value) {
    return "<textarea" + H.tid(id) + H.tt(title)


@@ 854,6 1085,7 @@ H.area = function(a) {
    + " " + pm + "%/" + pr + "%/" + pd + "%: " + a.tags;

    var aahref = H.gf_itv_ahref("#area=" + a.aid);
    var aahrefw = function(w) { return H.gf_itv_ahref("#area=" + a.aid + w); }

    var that = {};
    that.briefly = function() {


@@ 1052,14 1284,15 @@ H.area = function(a) {
        return "<h1 title='" + aahref_title + "'>"
        + a.aid + " " + H.ttohtmla(a.tags) + "</h1>";
    };
    that.mapper_description = function() {
    that.mapper_description = function(w) {
        var ad_aahref = w ? aahrefw(w) : aahref;
        var languages = "";
        var descriptions = "";
        var lang_id, desc_id, tr_id;
        for (var lang in a.description) {
            lang_id = "ad-lang-" + H.ttohtml(lang);
            lang_title = "description in " + H.ttohtml(lang);
            languages += " " + aahref(lang_id, lang_title, lang);
            languages += " " + ad_aahref(lang_id, lang_title, lang);

            desc_id = H.tid("ad-text-" + lang);
            descriptions += "<p class='desc'" + desc_id + ">"


@@ 1067,9 1300,9 @@ H.area = function(a) {
            + "</p>";
        }
        tr_id = "ad-lang-translate";
        languages += " " + aahref(tr_id, "add new translation", "translate");
        languages += " "+ ad_aahref(tr_id, "add new translation", "translate");
        return "<div id='ad-show-all'>"
        + aahref("ad-show", "", "show description")
        + ad_aahref("ad-show", "", "show description")
        + "</div>"

        + "<div id='ad-all' class='desc text'>"


@@ 1079,9 1312,9 @@ H.area = function(a) {
        + " tabindex='1'"
        + " placeholder='language code' />"
        + "&nbsp;"
        + aahref("ad-tr-add' tabindex='2", "add translation", "add")
        + ad_aahref("ad-tr-add' tabindex='2", "add translation", "add")
        + "&nbsp;or&nbsp;"
        + aahref(
        + ad_aahref(
            "ad-tr-cancel' tabindex='2",
            "cancel translation",
            "cancel")


@@ 1356,15 1589,199 @@ H.area = function(a) {
};

H.square = function(s) {
    var geometry;
    try {
        geometry = JSON.parse(s.border);
    } catch(e) {
        geometry = s.border;
    }
    var that = {};
    that.bbox = function() {
        var border;
        try {
            border = JSON.parse(s.border);
        } catch(e) {
            border = s.border;
        return H.bbox(geometry);
    };
    that.polygons = function(commit) {
        var i, j, p;
        var h = "", c;
        var omu = function() {
            return " onMouseUp='javascript:"
            + "H.show_mouse_menu("
            + "event, " + commit.aid + ", " + commit.sid + ");'";
        };
        if ("to map" == commit.type) { c = "to_map"; }
        else if ("to review" == commit.type) { c = "to_rev"; }
        else if ("done" == commit.type) { c = "done"; }
        else if ("locked" == commit.type) { c = "locked"; }
        else { return ""; }
        c = " class='" + c + " square'";  // TODO H.type_to_css(type)
        p = function(coordinates) {
            var i, h = " points='";
            for (i = 0; i < coordinates.length; i++) {
                x = coordinates[i][0] - D.m.bbox.lonmid;
                x *= D.m.bbox.scalex;
                x += D.m.bbox.imgw / 2;
                x *= D.m.bbox.scales;
                y = coordinates[i][1] - D.m.bbox.latmid;
                y *= D.m.bbox.scaley;
                y -= D.m.bbox.imgh / 2;
                y *= -1;
                y *= D.m.bbox.scales;
                h += x + "," + y + " ";
            }
            h += "'";
            return h;
        }
        return H.bbox(border);
        switch (geometry.type) {
        case "LineString":
            h += "<polygon" + c + p(geometry.coordinates)
            + omu()
            + "><title>square " + s.sid + " " + commit.type + "</title>"
            + "</polygon>";
            break;
        case "Polygon":
            for (i = 0; i < geometry.coordinates.length; i++) {
                h += "<polygon" + c + p(geometry.coordinates[i])
                + omu()
                + "><title>square " + s.sid + " " + commit.type + "</title>"
                + "</polygon>";
            }
            break;
        case "MultiPolygon":
            for (i = 0; i < geometry.coordinates.length; i++) {
                for (j = 0; j < geometry.coordinates[i].length; j++) {
                    h += "<polygon" + c + p(geometry.coordinates[i][j])
                    + omu()
                    + "><title>square " + s.sid + " " + commit.type
                        + "</title>"
                    + "</polygon>";
                }
            }
            break;
        }
        return h;
    };
    return that;
};

H.squares = function(los) {
    var that = {};
    that.bbox = function() {
        var max = [-180, -90];  // lon, lat
        var min = [180, 90];  // lon, lat
        var mid = [0, 0];
        var zoom = 0, scale = 360, scalex, scaley, imgw;
        var i, j, s, bb;
        for (i = 0; i < los.length; i++) {
            s = los[i];
            bb = H.bbox(s.border);
            if (bb.lonmin < min[0]) { min[0] = bb.lonmin; }
            if (bb.latmin < min[1]) { min[1] = bb.latmin; }
            if (bb.lonmax > max[0]) { max[0] = bb.lonmax; }
            if (bb.latmax > max[1]) { max[1] = bb.latmax; }
        }
        mid[0] = (max[0] - min[0]) / 2 + min[0];
        mid[1] = (max[1] - min[1]) / 2 + min[1];
        if ((max[0] - min[0] - 0.001*3.125) <= 0) {
            zoom = 18;
            scale = 0.001;
        } else if ((max[0] - min[0] - 0.003*3.125) <= 0) {
            zoom = 17;
            scale = 0.003;
        } else if ((max[0] - min[0] - 0.005*3.125) <= 0) {
            zoom = 16;
            scale = 0.005;
        } else if ((max[0] - min[0] - 0.011*3.125) <= 0) {
            zoom = 15;
            scale = 0.011;
        } else if ((max[0] - min[0] - 0.022*3.125) <= 0) {
            zoom = 14;
            scale = 0.022;
        } else if ((max[0] - min[0] - 0.044*3.125) <= 0) {
            zoom = 13;
            scale = 0.044;
        } else if ((max[0] - min[0] - 0.088*3.125) <= 0) {
            zoom = 12;
            scale = 0.088;
        } else if ((max[0] - min[0] - 0.176*3.125) <= 0) {
            zoom = 11;
            scale = 0.176;
        } else if ((max[0] - min[0] - 0.352*3.125) <= 0) {
            zoom = 10;
            scale = 0.352;
        } else if ((max[0] - min[0] - 0.703*3.125) <= 0) {
            zoom = 9;
            scale = 0.703;
        } else if ((max[0] - min[0] - 1.406*3.125) <= 0) {
            zoom = 8;
            scale = 1.406;
        } else if ((max[0] - min[0] - 2.813*3.125) <= 0) {
            zoom = 7;
            scale = 2.813;
        } else if ((max[0] - min[0] - 5.625*3.125) <= 0) {
            zoom = 6;
            scale = 5.625;
        } else if ((max[0] - min[0] - 11.25*3.125) <= 0) {
            zoom = 5;
            scale = 11.25;
        } else if ((max[0] - min[0] - 22.5*3.125) <= 0) {
            zoom = 4;
            scale = 22.5;
        } else if ((max[0] - min[0] - 45*3.125) <= 0) {
            zoom = 3;
            scale = 45;
        } else if ((max[0] - min[0] - 90*3.125) <= 0) {
            zoom = 2;
            scale = 90;
        } else if ((max[0] - min[0] - 180*3.125) <= 0) {
            zoom = 1;
            scale = 180;
        } else {
            zoom = 0;
            scale = 360;
        }
        scalex = 256 / scale;
        scaley = 256 / (scale * Math.cos(max[1] * Math.PI / 180));
        imgw = Math.floor((max[0] - min[0]) * scalex);
        D.m.bbox = {
            lonmin: min[0],
            latmin: min[1],
            lonmax: max[0],
            latmax: max[1],
            lonmid: mid[0],
            latmid: mid[1],
            zoom: zoom,
            scale: scale,
            scalex: scalex,
            scaley: scaley,
            imgw: imgw,
            imgh: Math.floor((max[1] - min[1]) * scaley),
            scales: 0.98*window.screen.width / imgw,
            imgurl: "https://osmsm.damn-project.org/" + los[0].aid + ".png",
        };
    };
    that.svg = function(square_commits) {
        that.bbox();
        var i, t, h;
        t = " scaleX(" + D.m.lastsx + ")"
        + " scaleY(" + D.m.lastsy + ")"
        + " translateX(" + (-1.0*D.m.lasttx) + "px)"
        + " translateY(" + (-1.0*D.m.lastty) + "px)";
        h = "<svg id='svg-map'"
        + " width='" + (D.m.bbox.imgw * D.m.bbox.scales) + "'"
        + " height='" + (D.m.bbox.imgh * D.m.bbox.scales) + "'"
        + " style='"
        + "background-image: url(" + D.m.bbox.imgurl + ");"
        + "background-repeat: no-repeat;"
        + "background-position: center;"
        + "background-size: 100% 100%;"
        + "transform-origin: 0 0;"
        + "transform: " + t + ";"
        + "'"
        + ">";
        for (i = 0; i < los.length; i++) {
            h += H.square(los[i]).polygons(square_commits[los[i].sid]);
        }
        h += "</svg>";
        return h;
    };
    return that;
};

M i2.html => i2.html +10 -4
@@ 91,14 91,14 @@ SOFTWARE.
        if (m === null || m[1] == "") {
            if (R.route && R.route()) { return; }
        } else {
            var go = {};
            R.go = {};
            var v = m[1].split("&");
            for (var i = 0; i < v.length; i++) {
                var p = v[i].split("=");
                go[decodeURI(p[0])] = p[1] ? decodeURI(p[1]) : true;
                R.go[decodeURI(p[0])] = p[1] ? decodeURI(p[1]) : true;
            }
            if (R.route_common && R.route_common(go)) { return; }
            if (R.route && R.route(go)) { return; }
            if (R.route_common && R.route_common(R.go)) { return; }
            if (R.route && R.route(R.go)) { return; }
        }
    };
    D.gen_mw_functions = function(mapping_cb, review_cb, done_cb) {


@@ 116,6 116,12 @@ SOFTWARE.
        D.mw.id = D.mw.gf_unlock_square("is done", done_cb);
        D.mw.ns = D.mw.gf_unlock_square("split", done_cb);
    };
    D.m = {};
    // default scale and translation for svg map
    D.m.lastsx = 1;
    D.m.lastsy = 1;
    D.m.lasttx = 0;
    D.m.lastty = 0;
    window.addEventListener("load", D.r);
    window.addEventListener("hashchange", D.r);
</script>

M qa.html => qa.html +4 -0
@@ 7,6 7,10 @@
        <a href='panel'>panel</a>
        -- Integration without integration to web editor (like iD).
    </li>
    <li>
        <a href='mappy'>mappy</a>
        -- Manual square lock, split, merge.
    </li>
</ul>



M route-common.js => route-common.js +3 -0
@@ 204,3 204,6 @@ R.route_common = function(to) {
    }
    return true;
};
R.route_again = function() {
    if (R.go && R.route) { return R.route(R.go); }
};

A route-mappy.js => route-mappy.js +115 -0
@@ 0,0 1,115 @@
R.route = function(to) {
    H.disable_mouse_menu();
    if (to && to.area) {
        D.s.get_area(to.area, function(rarea) {
            var a = H.area(rarea);
            H.set("main",
                    a.mapper_header()
                    + H.h_mouse_menu()
                    + H.h_mappy_menu(to));
            if (to.stats) {
                D.s.get_commits(to.area, function(rloc) {
                    var loc = H.commits(rloc);
                    loc.compute_transitions_sums_and_stats_counts();
                    H.set("more", loc.plot_mrd_stats());
                });
            } else if (to.desc) {
                D.s.get_area(to.area, function(rarea) {
                    H.set("more", H.area(rarea).mapper_description("&desc"));
                    H.ge("ad-show-all").hidden = true;
                    H.ac("ad-show", function() {
                        H.ge("ad-show-all").hidden = true;
                        H.ge("ad-all").hidden = false;
                    });
                    var gf_show_desc = function(lang) {
                        return function() {
                            var i;
                            var desc_l = document
                                .querySelectorAll("[id^=ad-text-]");
                            for (i = 0; i < desc_l.length; i++) {
                                desc_l[i].hidden = true;
                            }
                            H.ge("ad-text-" + lang).hidden = false;
                        };
                    };
                    var first_lang = false;
                    var lang;
                    for (lang in rarea.description) {
                        if (!first_lang) { first_lang = lang; }
                        if (D.s.user() && D.s.user().lang === lang) {
                            first_lang = lang;
                        }
                        H.ac("ad-lang-" + lang, gf_show_desc(lang));
                    }
                    if (first_lang) { (gf_show_desc(first_lang))(); }
                    var gf_show_desc_tran = function(show) {
                        return function() {
                            H.ge("ad-tr").hidden = !show;
                            if (show) { H.ge("ad-tr-lang-translate").focus(); }
                        };
                    };
                    H.ac("ad-lang-translate", gf_show_desc_tran(true));
                    H.ac("ad-tr-cancel", gf_show_desc_tran(false));
                    var translate_desc = function() {
                        var new_lang = H.ge("ad-tr-lang-translate").value;
                        if (Object.keys(rarea.description).includes(new_lang)) {
                            H.set("info",
                                "Already translated to '" + nl + "'.");
                            return;
                        }
                        var new_text = H.ge("ad-tr-text-translate").value;
                        var new_area = {};
                        new_area.aid = rarea.aid;
                        new_area.description = rarea.description;
                        new_area.description[new_lang] = new_text;
                        var f = function() { window.location.reload(); };
                        D.s.put_area(new_area, f);
                    };
                    H.ac("ad-tr-add", translate_desc);
                    (gf_show_desc_tran(false))();
                    if (!D.s.user()) {
                        H.ge("ad-lang-translate").hidden = true;
                    }
                });
            } else if (to.info) {
                H.set("more", H.h_mappy_info_menu(to.area));
            } else {
                H.enable_mouse_menu();
                D.s.get_commits(to.area, function(rloc) {
                    var show_square_types = [
                        "to map", "to review", "done", "locked"];
                    var i, square_commits = {};
                    for (i = rloc.length - 1; 0 <= i; i--) {
                        if (!Object.keys(square_commits).includes(rloc[i].sid)){
                            square_commits[rloc[i].sid] = {
                                type: rloc[i].type,
                                aid: rloc[i].aid,
                                sid: rloc[i].sid};
                        }
                    }
                    D.s.get_squares(to.area, function(rlos) {
                        H.set("more",
                            H.squares(rlos).svg(square_commits)
                            + H.h_mappy_osm_contrib()
                            + H.h_mappy_howto());
                        H.amd("svg-map", H.zoom_from);
                        H.amu("svg-map", H.zoom_to);
                    });
                });
            }
        });
    } else {
        D.s.get_areas(function(r) {
            var fv = "";
            if (to && to.filter && typeof to.filter === "string") {
                fv = to.filter;
            }
            var h = H.h_loa_menu_card(fv);
            var li = H.loa(r, fv);
            for (var a in li) { h += H.area(li[a]).mapper_list_card(); }
            H.set("main", h);
            R.bind_loa();
        });
    }
    return true;
};