diff --git a/main.py b/__init__.py
similarity index 91%
rename from main.py
rename to __init__.py
index b17acee..4accd8c 100644
--- a/main.py
+++ b/__init__.py
@@ -5,13 +5,14 @@
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:test@localhost/immp'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True;
+app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit
db = SQLAlchemy(app)
# Database schema
class Maps(db.Model):
id = db.Column(db.Integer(), primary_key=True)
url = db.Column(db.String(512), unique=True)
- csv = db.Column(db.String())
+ csv = db.Column(db.Text())
mapping = db.Column(db.String())
def __init__(self, url):
@@ -41,10 +42,10 @@ def postmap():
return redirect('/map/id='+str(map.id))
-@app.route('/postcsv')
+@app.route('/postcsv', methods=['POST'])
def postcsv():
- id = request.args.get('mapID', 0, type=int)
- csv = request.args.get('csv')
+ id = request.json['mapID']
+ csv = request.json['csv']
map = Maps.query.filter_by(id=id).first()
map.csv = csv
db.session.commit()
diff --git a/static/imgareaselect-default.css b/static/imgareaselect-default.css
deleted file mode 100644
index f4fe341..0000000
--- a/static/imgareaselect-default.css
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * imgAreaSelect default style
- */
-
-.imgareaselect-border1 {
- background: url(border-v.gif) repeat-y left top;
-}
-
-.imgareaselect-border2 {
- background: url(border-h.gif) repeat-x left top;
-}
-
-.imgareaselect-border3 {
- background: url(border-v.gif) repeat-y right top;
-}
-
-.imgareaselect-border4 {
- background: url(border-h.gif) repeat-x left bottom;
-}
-
-.imgareaselect-border1, .imgareaselect-border2,
-.imgareaselect-border3, .imgareaselect-border4 {
- filter: alpha(opacity=50);
- opacity: 0.5;
-}
-
-.imgareaselect-handle {
- background-color: #fff;
- border: solid 1px #000;
- filter: alpha(opacity=50);
- opacity: 0.5;
-}
-
-.imgareaselect-outer {
- background-color: #000;
- filter: alpha(opacity=50);
- opacity: 0.5;
-}
-
-.imgareaselect-selection {
-}
\ No newline at end of file
diff --git a/static/js/create_map.js b/static/js/create_map.js
index 713b19a..886e330 100644
--- a/static/js/create_map.js
+++ b/static/js/create_map.js
@@ -1,21 +1,56 @@
// createMode indicates if we are or are not in create mode
var createMode = false;
-var x;
-var y;
+var deleteMode = false;
+
+/** Coordinates of last point clicked on image. */
+var x, y;
+
+/** data store for tagged features/rows */
var ids = [];
-function mapData(x, y, id){
+function insertAndMapData(x, y, id){
+ $('#cancelTag').hide();
console.log("Mapping id: " + id + " to coordinates " + x + "," + y);
if (id == " " || x == null){
console.log("Invalid ID");
+ alert("ID is not valid.");
+ return;
+ }
+ if(ids[id]){
+ console.log("Data already mapped");
return;
}
ids[id] = x + "," + y; // adds to ID array
+ mapData(x,y,id);
+}
+
+//////
+/*var a = jQuery('', { 'shape': "circle", 'id': id, 'data-name': "1,all", 'coords': x + "," + y + ",10", 'href': "#" });
+
+var a = $('', {
+ 'shape': 'circle',
+ 'id': '1',
+ 'da'
+})
+a['shape'] = 'circle';
+a['id'] = '1';
+a['data-name'] = '1,all';
+a['coords'] = '191,81,10';
+a['href'] = '#';
+$(".mapper-map").append(a);
+*/
+
+//////
+
+function mapData(x,y,id){
var mapHTML = "";
$(".mapper-map").append(mapHTML);
+
+ if(!deleteMode)
+ $('#tagged' + id).show();
map = $('#mapper');
map.mapster('unbind')
@@ -26,28 +61,62 @@ function mapData(x, y, id){
.mapster('set', true, 'all')
.mapster('set_options', single_opts);
}
- }).bind('mouseout', function () {
- if (!inArea) {
- map.mapster('set', false, 'all');
- }
});
}
+function unmapData(id){
+ var mapHTML;
+ console.log("Delete tag for id: " + id + " from coords " + ids[id]);
+ if (id === ""){
+ console.log("Invalid ID");
+ return;
+ }
+
+ $('#deleteTag' + id).hide();
+
+ delete ids[id];
+
+ $(".mapper-map").empty(); //clear html ?
+
+ map = $('#mapper');
+ map.mapster('unbind')
+
+ for (var row in ids){
+ console.log(row + " " + ids[row]);
+ if(ids[row]){
+ var coords = ids[row].split(',');
+ console.log(coords[0] + "," + coords[1]);
+ mapData(coords[0],coords[1],row);
+ }
+ }
+}
+
+
+/**
+ * gets called from map.html document ready function if
+ * data already exists for map
+ */
function build_mappings(mappingsString){
var maps = mappingsString.split('\n');
for(var i = 0; i < maps.length - 1; i++){
to_map = maps[i].split(',');
- mapData(to_map[1],to_map[2],to_map[0]);
+ insertAndMapData(to_map[1],to_map[2],to_map[0]);
}
-
}
+/**
+ * onClick handler which is attached to the rows after clicking on image
+ *
+ *
+ */
function dataClick(id){
- if(createMode){
- mapData(x,y,id);
+ if (createMode) {
+ insertAndMapData(x,y,id);
}
createMode = false;
+
+ // use toggle class, elements same as line 120 in csv.js @ 1111c0a373f706c7a6e0d8aa46d3a06258ea7a27
$('.unlinked').removeClass("unlinked");
$('.linked').removeClass("linked");
$('.greyOut').removeClass("greyOut");
-}
\ No newline at end of file
+}
diff --git a/static/js/csv-to-json.js b/static/js/csv-to-json-gist.js
similarity index 100%
rename from static/js/csv-to-json.js
rename to static/js/csv-to-json-gist.js
diff --git a/static/js/csv.js b/static/js/csv.js
index d930396..b8e6d2a 100644
--- a/static/js/csv.js
+++ b/static/js/csv.js
@@ -1,12 +1,13 @@
var fileInput = $('#files');
var uploadButton = $('#upload');
+var deleteTagButton = $('#deleteTag');
-uploadButton.on('click', function() {
+uploadButton.on('click', function uploadButtonClick() {
if (!window.FileReader) {
alert('Your browser is not supported')
}
var input = fileInput.get(0);
-
+
// Create a reader object
var reader = new FileReader();
if (input.files.length) {
@@ -15,19 +16,27 @@ uploadButton.on('click', function() {
$(reader).on('load', processFile);
} else {
alert('Please upload a file before continuing');
- }
+ }
});
+/**
+ * this and show_* runs once when new image
+ *
+ * and once on load
+ * hide is called every time image is loaded in build_table? shouldn't it just be once?
+ */
function hide_csv_import(){
$('#result').empty();
$('#upload').hide();
$('#files').hide();
+ $('#hint').hide();
}
function show_csv_import(){
$('#upload').show();
$('#files').show();
+ $('#hint').show();
}
function processFile(e) {
@@ -45,6 +54,9 @@ function processFile(e) {
}
+/**
+ * HAPPENS ON LAUNCH (document.ready in map.html)
+ */
function build_table(csv){
var csvarray = $.csv.toArrays(csv);
var csvtable = generateTable(csvarray);
@@ -55,46 +67,76 @@ function build_table(csv){
function push_csv_to_db(csv){
var id = window.location.pathname;
id = id.substring(id.indexOf("id=")+3, id.length);
- $.getJSON($SCRIPT_ROOT + '/postcsv', {
- mapID: id,
- csv: csv
- }, function(data) {
+ $.ajax({
+ type: 'POST',
+ url: $SCRIPT_ROOT + '/postcsv',
+ data: JSON.stringify({ mapID: id, csv: csv }),
+ contentType: 'application/json',
+ success: function(data){console.log("success")},
+ error: function(data){console.log("failure")}
});
}
+/**
+ * Normalizes data to Matrix of Strings
+ *
+ * Converts List of strings to list of 1-length lists of strings, passes
+ * through list of list of strings as passed in.
+ *
+ * @param csvData ['string'] or [['string']]
+ * @return matrix of strings
+ */
+function normalizeCSV(csvData) {
+ if (csvData[0].constructor === String) {
+ return csvData.map(function (row) {
+ return [row];
+ });
+ } else if (csvData[0].constructor === Array &&
+ csvData[0][0].constructor === String) {
+ return csvData;
+ } else {
+ // alert('Error: csvData malformed, see console.');
+ console.log(csvData);
+ throw new Error('csvData');
+ }
+}
+
// build HTML table data from an array (one or two dimensional)
+/**
+ * normalizes data
+ *
+ * array: if csv
+ * @param data array[''] or array[[]], array[{}]
+ */
function generateTable(data) {
var html = '';
if(typeof(data[0]) === 'undefined') {
return null;
}
- if(data[0].constructor === String) {
- html += '
\r\n';
- for(var item in data) {
- html += '
' + data[item] + '
\r\n';
+
+ data = normalizeCSV(data);
+
+ /**
+ * constructs html for rows with listeners for tableOver, tableOut, and Click on rows
+ *
+ * * tableData tag with column info
+ * * button tag to delete with unmapData onclick listener
+ *
+ * @param tableOver, tableOut, dataClick, unmapData
+ */
+ for(var row in data) {
+ //HTML addition for new table row (one for every row in CSV)
+ html += '
\r\n';
+ if(row != 0)
+ html += '
\r\n';
+ else
+ html += '
'
+ for(var item in data[row]) {
+ html += '
' + data[row][item] + '
\r\n';
}
html += '
\r\n';
}
- if(data[0].constructor === Array) {
- for(var row in data) {
- //HTML addition for new table row (one for every row in CSV)
- html += '
\r\n';
- for(var item in data[row]) {
- html += '
' + data[row][item] + '
\r\n';
- }
- html += '
\r\n';
- }
- }
- if(data[0].constructor === Object) {
- for(var row in data) {
- html += '
\r\n';
- for(var item in data[row]) {
- html += '
' + item + ':' + data[row][item] + '
\r\n';
- }
- html += '
\r\n';
- }
- }
-
+
return html;
}
@@ -110,6 +152,9 @@ function generateTableFromJSON(json) {
}
}
+/**
+ * refactor to use $.fn.toggleClass?
+ */
function grey_out(item, index){
$('#result').find('#' + index).removeClass("unlinked");
$('#result').find('#' + index).addClass("linked");
@@ -120,4 +165,29 @@ function create_visual(ids){
$('#result').find('tr').addClass("unlinked");
$('#result').find('#0').removeClass("unlinked");
ids.forEach(grey_out);
+}
+
+/**
+ * show/hide delete buttons next to tagged objects
+ */
+function showDeletes(){
+ $('#delete').hide();
+ $('#create').show();
+ for (var row in ids){
+ if(ids[row]){
+ $('#tagged' + row).hide();
+ $('#deleteTag' + row).show();
+ }
+ }
+}
+
+function hideDeletes(){
+ $('#create').hide();
+ $('#delete').show();
+ for (var row in ids){
+ if(ids[row]){
+ $('#tagged' + row).show();
+ $('#deleteTag' + row).hide();
+ }
+ }
}
\ No newline at end of file
diff --git a/static/js/home.js b/static/js/home.js
index 22e1eb6..61dc7bc 100644
--- a/static/js/home.js
+++ b/static/js/home.js
@@ -9,11 +9,12 @@ function deleteMap(id){
$(document).ready(function(){
console.log("listening for deletes");
$('.delete').click(function(){
-
- console.log($(this).parent().attr('id'));
- deleteMap($(this).parent().attr('id'));
- $(this).parent().remove();
- return false;
-
+ var conf = confirm("Delete this map?");
+ if(conf){
+ console.log($(this).parent().attr('id'));
+ deleteMap($(this).parent().attr('id'));
+ $(this).parent().remove();
+ return false;
+ }
});
});
\ No newline at end of file
diff --git a/static/js/imagemap.js b/static/js/jQuery-image-mapster-setup.js
similarity index 93%
rename from static/js/imagemap.js
rename to static/js/jQuery-image-mapster-setup.js
index 9a8d93b..7fa7c10 100644
--- a/static/js/imagemap.js
+++ b/static/js/jQuery-image-mapster-setup.js
@@ -16,7 +16,7 @@ var inArea,
},
all_opts = {
fillColor: 'ffffff',
- fillOpacity: 0.6,
+ fillOpacity: 0.4,
stroke: true,
strokeWidth: 2,
strokeColor: 'ffffff'
@@ -28,6 +28,8 @@ var inArea,
onMouseover: function (data) {
inArea = true;
$( "#" + data.key ).addClass("hovered");
+ var e1 = document.getElementById(data.key);
+ e1.scrollIntoView(true);
},
onMouseout: function (data) {
inArea = false;
@@ -49,14 +51,13 @@ var inArea,
.mapster('set', true, 'all')
.mapster('set_options', single_opts);
}
- }).bind('mouseout', function () {
- if (!inArea) {
- map.mapster('set', false, 'all');
- }
});
// These are called when user hovers over data in table
function tableOver(num){
+ //no hover action for title row
+ if(num === 0)
+ return;
map = $('#mapper');
$( "#" + num ).addClass("hovered");
map.mapster('set_options', single_opts)
diff --git a/static/js/main.js b/static/js/main.js
index aedcfc6..4090650 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -6,10 +6,15 @@ function createMapping(e, img){
3. Wait for user to click data to map to (TODO)
4. Send to "mapData" function that takes x, y, and id, and adds to map (TODO)
*/
- var offset = $(".mapper").offset();
- // x = event.offsetX?(event.offsetX):event.pageX-img.offsetLeft;
- // y = event.offsetY?(event.offsetY):event.pageY-img.offsetTop;
+ //don't allow creation of mappings while delete mode active
+ if(deleteMode){
+ return;
+ }
+
+ $('#cancelTag').show();
+
+ var offset = $(".mapper").offset();
var offset_t = $(img).offset().top - $(window).scrollTop();
var offset_l = $(img).offset().left - $(window).scrollLeft();
@@ -17,8 +22,6 @@ function createMapping(e, img){
var left = Math.round( (e.clientX - offset_l) );
var top = Math.round( (e.clientY - offset_t) );
- // console.log("Left: " + left + " Top: " + top);
-
x = left;
y = top;
createMode = true;
@@ -27,19 +30,26 @@ function createMapping(e, img){
}
+document.getElementById("canceltag").addEventListener("click", function(){
+ createMode = false;
+ $('.unlinked').removeClass("unlinked");
+ $('.linked').removeClass("linked");
+ $('.greyOut').removeClass("greyOut");
+ $('#cancelTag').hide();
+})
-
+/**
+ * stores current mappings in mappingString
+ * ?
+ */
document.getElementById("save").addEventListener("click", function(){
console.log("Saving...");
var mappingString = "";
for (var i = 0; i < ids.length; i++){
if(ids[i] != null){
- // console.log("Converting id: " + i + " into string");
mappingString += i + "," + ids[i] + "\n";
- // console.log("Current maps to store: \n" + mappingString);
}
}
- // console.log("Pushing to database...");
var id = window.location.pathname;
id = id.substring(id.indexOf("id=")+3, id.length);
@@ -48,7 +58,28 @@ document.getElementById("save").addEventListener("click", function(){
mapID: id,
mappings: mappingString
}, function(data) {
- // console.log(data.mappings);
});
+ alert("Map saved.");
+});
+
+document.getElementById("cancel").addEventListener("click", function(){
+ if(confirm("Leave map without saving?")){
+ console.log("Redirecting to homepage");
+ document.location.replace('/');
+ }
+})
-});
\ No newline at end of file
+document.getElementById("help").addEventListener("click",function(){
+ alert("To add a mapping, click anywhere on the image, then click the data row you wish to map to that location. If you click the 'Delete Mappings' button, a red X will appear next to each mapped row which allows you to delete mappings you've created. Click 'Create Mappings to return to create mode. Click 'Save' in the bottom right to save your map once you've finished.")
+});
+
+document.getElementById("deleteTags").addEventListener("click", function(){
+ if(deleteMode === true){
+ deleteMode = false;
+ hideDeletes();
+ }
+ else{
+ deleteMode = true;
+ showDeletes();
+ }
+})
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index 5d53e33..77e2fb2 100644
--- a/static/style.css
+++ b/static/style.css
@@ -155,12 +155,61 @@ margin:0;
background-color: grey;
}
+#canceltag {
+ position: fixed;
+ top: 60px;
+ right: 10px;
+}
+
+#deleteTags {
+ position: fixed;
+ bottom: 50px;
+ left: 10px;
+ z-index: 2;
+}
+
#save {
position: fixed;
bottom: 50px;
right: 10px;
}
+#cancel {
+ position: fixed;
+ bottom: 50px;
+ right: 70px;
+}
+
+#help {
+ position: fixed;
+ bottom: 50px;
+ right: 142px;
+}
+
+#result {
+ border: 0;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 100px;
+}
+
+td {
+ min-width: 25px;
+ min-height: 25px;
+ text-align: center;
+ border-color: #bcbcbc;
+}
+
+tr td:first-child {
+ border: 0;
+ background-color: #f5f5f5 !important;
+}
+
+tr:first-child td:not(:first-child){
+ background-color: #bcbcbc;
+ border-color: #000000 !important;
+}
+
.csvstuff {
position: fixed;
top: 51px;
@@ -172,12 +221,13 @@ margin:0;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
+ z-index: 1;
}
.map-loader {
text-align: center;
- margin-left: auto ;
- margin-right: auto ;
+ margin-left: auto;
+ margin-right: auto;
}
.maploadbox {
diff --git a/templates/base.html b/templates/base.html
index 858e600..a8b6c3b 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -46,7 +46,7 @@