Replace index page by a map where you can optionally start a pad

pull/54/merge
Candid Dauth 2016-10-11 18:09:01 +03:00
rodzic 7b97b59e00
commit 54657ef048
15 zmienionych plików z 252 dodań i 147 usunięć

Wyświetl plik

@ -1,3 +1,6 @@
RewriteEngine on
RewriteRule ^$ build/index.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^.+$ build/index.html

Wyświetl plik

@ -29,7 +29,6 @@ var FacilPad = {
});
fp.app.run([ "$rootScope", "fpUtils", function($rootScope, fpUtils) {
$rootScope.padId = location.pathname.match(/[^\/]*$/)[0];
$rootScope.urlPrefix = location.protocol + "//" + location.host + location.pathname.replace(/[^\/]*$/, "");
$rootScope.round = fpUtils.round;
@ -79,14 +78,19 @@ var FacilPad = {
};
fp.app.controller("PadCtrl", function($scope, fpMap, $timeout) {
$scope.padId = location.pathname.match(/[^\/]*$/)[0];
$timeout(function() {
var map = fpMap.getMap("map");
if(map.socket.padData)
$scope.padName = map.socket.padData.name;
map.socket.$watch("padData.name", function(newVal) {
$scope.padName = newVal;
});
map.socket.$watch("padId", function(padId) {
if(padId)
history.replaceState(null, "", $scope.urlPrefix + padId);
});
}, 0);
});

Wyświetl plik

@ -4,7 +4,7 @@
<img src="spinner.gif" alt="Loading…">
</div>
</div>
<div class="fp-map-disabled-cover" ng-show="!loaded || disconnected"></div>
<div class="fp-map-loading" ng-hide="loaded">
<div class="fp-map-disabled-cover" ng-show="disconnected || serverError"></div>
<div class="fp-map-loading" ng-hide="loaded || serverError">
Loading...
</div>

Wyświetl plik

@ -19,20 +19,18 @@
};
ret.initMap = function(el, id, padId) {
return maps[id] = new Map(el, id, padId);
return maps[id] = new Map(el, padId);
};
return ret;
function Map(el, id, padId) {
function Map(el, padId) {
var map = this;
map.el = el;
map.mapEvents = $rootScope.$new(true); /* Event types: click, layerchange */
map.socket = fpSocket(padId);
//map.socket.id = id; // To be in scope for template
map.layers = { };
[
L.tileLayer("http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {
@ -91,9 +89,12 @@
direction: "right"
};
var scope = map.socket.$new();
scope.loaded = false;
var tpl = $($templateCache.get("map/map/map.html"));
el.append(tpl);
$compile(tpl)(map.socket);
$compile(tpl)(scope);
map.map = L.map(el.find(".fp-map")[0]);
@ -268,18 +269,22 @@
fpMapLegend(map);
var loadedWatcher = map.socket.$watch("loaded", function(loaded) {
if(loaded) {
setTimeout(function() {
map.displayView(map.socket.padData.defaultView);
}, 0);
loadedWatcher();
}
});
if(padId) {
var loadedWatcher = map.socket.$watch("padData", function(padData) {
if(padData != null) {
loadedWatcher();
map.displayView(padData.defaultView);
scope.loaded = true;
}
});
} else {
map.displayView();
scope.loaded = true;
}
var errorMessage = null;
map.socket.$watch("disconnected", function(disconnected) {
if(disconnected && !errorMessage)
if(disconnected && !errorMessage && !map.socket.serverError)
errorMessage = map.messages.showMessage("danger", "The connection to the server was lost.");
else if(!disconnected && errorMessage) {
errorMessage.close();
@ -287,8 +292,16 @@
}
});
map.socket.$watch("serverError", function(serverError) {
if(serverError) {
errorMessage && errorMessage.close();
map.messages.showMessage("danger", serverError);
}
});
map.map.on("moveend", function() {
map.socket.updateBbox(fpUtils.leafletToFpBbox(map.map.getBounds(), map.map.getZoom()));
if(map.socket.padId)
map.socket.updateBbox(fpUtils.leafletToFpBbox(map.map.getBounds(), map.map.getZoom()));
});
}
});

Wyświetl plik

@ -1,28 +1,38 @@
<div class="modal-header">
<button class="close" ng-click="$dismiss()"><span aria-hidden="true">&times;</span></button>
<h3 class="modal-title">Pad settings</h3>
<h3 class="modal-title">{{create ? 'Start collaborative map' : 'Pad settings'}}</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div uib-alert class="alert-danger" ng-show="error">{{error}}</div>
<div class="form-group">
<label for="pad-link-input" class="col-sm-3 control-label">Link</label>
<div class="col-sm-9"><input id="pad-link-input" value="{{urlPrefix}}{{padId}}" class="form-control" readonly /></div>
<label for="pad-link-input" class="col-sm-3 control-label">Edit-only Link</label>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-addon">{{urlPrefix}}</span>
<input id="pad-link-input" ng-model="writeId" class="form-control" />
</div>
</div>
</div>
<div class="form-group">
<label for="pad-rolink-input" class="col-sm-3 control-label">Read-only link</label>
<div class="col-sm-9"><input id="pad-rolink-input" value="{{urlPrefix}}{{padData.id}}" class="form-control" readonly /></div>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-addon">{{urlPrefix}}</span>
<input id="pad-rolink-input" ng-model="readId" class="form-control" />
</div>
</div>
</div>
<div class="form-group">
<label for="pad-name-input" class="col-sm-3 control-label">Pad name</label>
<div class="col-sm-9"><input id="pad-name-input" ng-model="padData.name" class="form-control" /></div>
<div class="col-sm-9"><input id="pad-name-input" ng-model="padName" class="form-control" /></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="$dismiss()">Cancel</button>
<button type="button" class="btn btn-primary" ng-click="save()">Save</button>
<button type="button" class="btn btn-primary" ng-click="save()">{{create ? 'Create' : 'Save'}}</button>
</div>

Wyświetl plik

@ -3,22 +3,29 @@
fp.app.factory("fpMapPad", function($uibModal, fpUtils) {
return function(map) {
var ret = {
editPadSettings : function() {
createPad : function() {
ret.editPadSettings(true);
},
editPadSettings : function(create) {
var dialog = $uibModal.open({
templateUrl: "map/pad/pad-settings.html",
scope: map.socket,
controller: "fpMapPadSettingsCtrl",
size: "lg",
resolve: {
map: function() { return map; }
map: function() { return map; },
create: function() { return create; }
}
});
var preserve = fpUtils.preserveObject(map.socket, "padData", "padData", function() {
dialog.dismiss();
});
if(!create) {
// TODO: use child scope!
var preserve = fpUtils.preserveObject(map.socket, "padData", "padData", function() {
dialog.dismiss();
});
dialog.result.then(preserve.leave.bind(preserve), preserve.revert.bind(preserve));
dialog.result.then(preserve.leave.bind(preserve), preserve.revert.bind(preserve));
}
}
};
@ -43,16 +50,43 @@
};
});
fp.app.controller("fpMapPadSettingsCtrl", function($scope, map) {
$scope.save = function() {
var padData = $.extend({ }, map.socket.padData);
delete padData.defaultView;
map.socket.emit("editPad", padData, function(err) {
if(err)
return $scope.error = err;
fp.app.controller("fpMapPadSettingsCtrl", function($scope, map, create, fpUtils) {
$scope.create = create;
$scope.$close();
});
if(create) {
$scope.writeId = fpUtils.generateRandomPadId(14);
$scope.readId = fpUtils.generateRandomPadId(12);
$scope.padName = "New FacilPad";
} else {
$scope.writeId = map.socket.padData.writeId;
$scope.readId = map.socket.padData.id;
$scope.padName = map.socket.padData.name;
}
$scope.save = function() {
var newData = {
name: $scope.padName,
id: $scope.readId,
writeId: $scope.writeId
};
if(create) {
map.socket.emit("createPad", newData, function(err) {
if(err)
return $scope.error = err;
map.socket.updateBbox(fpUtils.leafletToFpBbox(map.map.getBounds(), map.map.getZoom()));
$scope.$close();
});
} else {
map.socket.emit("editPad", newData, function(err) {
if(err)
return $scope.error = err;
$scope.$close();
});
}
};
});

Wyświetl plik

@ -3,7 +3,10 @@
<nav class="navbar navbar-default" ng-class="{'hidden-xs': !showXs}">
<div class="container-fluid" id="fp-toolbox-content">
<ul class="nav navbar-nav">
<li role="presentation" uib-dropdown ng-if="!readonly">
<li role="presentation" ng-if="!padId">
<a href="javascript:" ng-click="startPad()">Start collaborative map</a>
</li>
<li role="presentation" uib-dropdown ng-if="!readonly && padId">
<a href="javascript:" id="toolbox-add-dropdown" uib-dropdown-toggle role="button">Add <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelledby="toolbox-add-dropdown">
<li ng-repeat="type in types"><a href="javascript:" ng-click="addObject(type)">{{type.name}}</a></li>
@ -11,7 +14,7 @@
<li><a href="javascript:" ng-click="editObjectTypes()">Manage types</a></li>
</ul>
</li>
<li role="presentation" uib-dropdown>
<li role="presentation" uib-dropdown ng-if="padId">
<a href="javascript:" id="toolbox-views-dropdown" uib-dropdown-toggle role="button">Saved views <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelledby="toolbox-views-dropdown">
<li ng-repeat="(id, view) in views"><a href="javascript:" ng-click="displayView(view)">{{view.name}}</a></li>
@ -36,9 +39,9 @@
<ul uib-dropdown-menu aria-labelledby="toolbox-layers-dropdown" class="dropdown-menu-right">
<!--<li ng-if="!readonly"><a href="javascript:" ng-click="openDialog('copy-pad-dialog')">Copy pad</a></li>-->
<!--<li><a href="javascript:" ng-click="importFile()">Import File</a></li>-->
<li><a href="javascript:" ng-click="exportGpx()">Export GPX</a></li>
<li><a href="javascript:" ng-click="showTable()">View as table</a></li>
<li ng-if="!readonly"><a href="javascript:" ng-click="editPadSettings()">Settings</a></li>
<li ng-if="padId"><a href="javascript:" ng-click="exportGpx()">Export GPX</a></li>
<li ng-if="padId"><a href="javascript:" ng-click="showTable()">View as table</a></li>
<li ng-if="!readonly && padId"><a href="javascript:" ng-click="editPadSettings()">Settings</a></li>
<li><a href="javascript:" ng-click="showAbout()">About FacilPad</a></li>
</ul>
</li>

Wyświetl plik

@ -43,6 +43,10 @@
map.aboutUi.showAbout();
};
scope.startPad = function() {
map.padUi.createPad();
};
var ret = {
div: $($templateCache.get("map/toolbox/toolbox.html"))
};

Wyświetl plik

@ -5,7 +5,6 @@
return function(padId) {
var scope = $rootScope.$new();
scope.padId = padId;
scope.padData = null;
scope.readonly = null;
scope.markers = { };
@ -41,16 +40,21 @@
return socket.emit.apply(socket, arguments);
};
scope.on("serverError", function(data) {
scope.serverError = data;
});
scope.on("padData", function(data) {
scope.padData = data;
if(data.writable != null)
scope.readonly = !data.writable;
scope.disconnected = false;
var id = scope.readonly ? data.id : data.writeId;
if(id != null)
scope.padId = id;
if(!scope.loaded)
scope.loaded = true;
scope.disconnected = false;
});
scope.on("marker", function(data) {
@ -128,17 +132,31 @@
});
scope.on("reconnect", function() {
scope.emit("setPadId", scope.padId);
scope.emit("updateBbox", scope.bbox);
if(scope.padId)
scope.emit("setPadId", scope.padId);
else
scope.disconnected = false; // Otherwise it gets set when padData arrives
if(scope.bbox)
scope.emit("updateBbox", scope.bbox);
});
scope.setPadId = function(padId) {
if(scope.padId != null)
return;
scope.padId = padId;
scope.emit("setPadId", padId);
};
scope.updateBbox = function(bbox) {
scope.emit("updateBbox", bbox);
scope.bbox = bbox;
};
scope.emit("setPadId", scope.padId);
if(padId)
scope.setPadId(padId);
scope.$on("$destroy", function() {
socket.removeAllListeners();

Wyświetl plik

@ -7,9 +7,12 @@
var LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var LENGTH = 12;
fpUtils.generateRandomPadId = function() {
fpUtils.generateRandomPadId = function(length) {
if(length == null)
length = LENGTH;
var randomPadId = "";
for(var i=0; i<LENGTH; i++) {
for(var i=0; i<length; i++) {
randomPadId += LETTERS[Math.floor(Math.random() * LETTERS.length)];
}
return randomPadId;

Wyświetl plik

@ -2,7 +2,7 @@
<html ng-controller="PadCtrl">
<head>
<meta charset="utf-8">
<title fp-title="padName + ' – FacilPad'">FacilPad</title>
<title fp-title="padName ? padName + ' – FacilPad' : 'FacilPad'">FacilPad</title>
<meta name="robots" content="index,nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="shortcut icon" href="assets/img/favicon.png">
@ -20,61 +20,17 @@
</style>
</head>
<body>
<div id="introContainer">
<h1>FacilPad</h1>
<p>Welcome to FacilPad.</p>
<noscript><p><strong>FacilPad requires JavaScript to work.</strong></p></noscript>
<p><a id="create-pad-link" rel="nofollow">Create a new pad</a></p>
</div>
<div id="loading" style="display:none">Loading...</div>
<div id="mapContainer" style="display:none">
<div id="map" fp-map fp-pad-id="{{padId}}"></div>
</div>
<noscript><p><strong>FacilPad requires JavaScript to work.</strong></p></noscript>
<div id="loading" ng-if="false">Loading...</div>
<div id="map" fp-map fp-pad-id="{{padId}}"></div>
<script>
(function() {
var loaded = 0;
var l = document.getElementById("create-pad-link");
var LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var LENGTH = 12;
var randomPadId = "";
for(var i=0; i<LENGTH; i++) {
randomPadId += LETTERS[Math.floor(Math.random() * LETTERS.length)];
var interval = setInterval(function() {
if(window.angular != null && window.io != null) {
clearInterval(interval);
angular.bootstrap(document, [ "facilpad" ]);
}
l.href = randomPadId;
l.onclick = function() {
if(window.history && history.pushState) {
history.pushState(null, null, randomPadId);
check();
return false;
}
};
var interval = setInterval(check, 100);
function check() {
if(loaded == 0 && !location.pathname.match(/\/$/)) {
document.getElementById("introContainer").style.display = "none";
document.getElementById("loading").style.display = "";
loaded = 1;
}
if(loaded == 1 && window.FacilPad != null && window.io != null && $("#map").css("height") == "100%") {
$("#introContainer,#loading").remove();
$("#map").unwrap();
angular.bootstrap(document, [ "facilpad" ]);
loaded = 2;
clearInterval(interval);
}
}
check();
})();
}, 100);
</script>
<!-- inject:js -->

Wyświetl plik

@ -16,22 +16,29 @@ function getPadData(padId, callback) {
if(err)
return callback(err);
else if(data != null)
return callback(null, utils.extend(JSON.parse(JSON.stringify(data)), { writable: true, writeId: null }));
return callback(null, utils.extend(JSON.parse(JSON.stringify(data)), { writable: true }));
backend.getPadData(padId, function(err, data) {
if(err || data != null)
return callback(err, utils.extend(JSON.parse(JSON.stringify(data)), { writable: false, writeId: null }));
if(err)
return callback(err);
backend.createPad(utils.generateRandomId(10), padId, function(err, data) {
if(err)
return callback(err);
if(data == null)
return callback("This pad does not exist.");
async.each(DEFAULT_TYPES, function(it, next) {
backend.createType(data.id, it, next);
}, function(err) {
callback(err, utils.extend(JSON.parse(JSON.stringify(data)), { writable: true, writeId: null }));
});
});
return callback(null, utils.extend(JSON.parse(JSON.stringify(data)), { writable: false, writeId: null }));
});
});
}
function createPad(data, callback) {
backend.createPad(data, function(err, data) {
if(err)
return callback(err);
async.each(DEFAULT_TYPES, function(it, next) {
backend.createType(data.id, it, next);
}, function(err) {
callback(err, utils.extend(JSON.parse(JSON.stringify(data)), { writable: true }));
});
});
}
@ -41,7 +48,17 @@ function updatePadData(padId, data, callback) {
if(err)
return callback(err);
listeners.notifyPadListeners(padId, "padData", data);
listeners.notifyPadListeners(padId, "padData", function(listener) {
var dataClone = JSON.parse(JSON.stringify(data));
if(!listener.writable)
dataClone.writeId = null;
return dataClone;
});
if(data.id && data.id != padId)
listeners.changePadId(padId, data.id);
callback(null, data);
});
}
@ -350,8 +367,8 @@ function _setLinePoints(padId, lineId, trackPoints, callback) {
if(err)
return callback(err);
listeners.notifyPadListeners(padId, "linePoints", function(bboxWithZoom) {
return { reset: true, id: lineId, trackPoints : (bboxWithZoom ? routing.prepareForBoundingBox(trackPoints, bboxWithZoom) : [ ]) };
listeners.notifyPadListeners(padId, "linePoints", function(listener) {
return { reset: true, id: lineId, trackPoints : (listener && listener.bbox ? routing.prepareForBoundingBox(trackPoints, listener.bbox) : [ ]) };
});
callback(null);
@ -478,8 +495,8 @@ function _calculateRouting(line, callback) {
}
function _getMarkerDataFunc(marker) {
return function(bbox) {
if(!bbox || !utils.isInBbox(marker, bbox))
return function(listener) {
if(!listener || !listener.bbox || !utils.isInBbox(marker, listener.bbox))
return null;
return marker;
@ -513,6 +530,7 @@ function _getLinePoints(lineId, bboxWithZoom, callback) {
module.exports = {
connect : backend.connect,
getPadData : getPadData,
createPad : createPad,
updatePadData : updatePadData,
getViews : getViews,
createView : createView,

Wyświetl plik

@ -62,9 +62,9 @@ function _makeNotNullForeignKey(type, field, error) {
/* Pads */
var Pad = conn.define("Pad", {
id : { type: Sequelize.STRING, allowNull: false, unique: "padId", primaryKey: true },
id : { type: Sequelize.STRING, allowNull: false, unique: "padId", primaryKey: true, validate: { is: /^.+$/ } },
name: { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "New FacilPad"; } },
writeId: { type: Sequelize.STRING, allowNull: false, unique: "padId" }
writeId: { type: Sequelize.STRING, allowNull: false, unique: "padId", validate: { is: /^.+$/ } }
});
@ -262,8 +262,8 @@ function getPadDataByWriteId(writeId, callback) {
_promiseComplete(Pad.findOne({ where: { writeId: writeId }, include: [ { model: View, as: "defaultView" } ] }), callback);
}
function createPad(padId, writeId, callback) {
_promiseComplete(Pad.create({ id: padId, writeId: writeId }), callback);
function createPad(data, callback) {
_promiseComplete(Pad.create(data), callback);
}
function updatePadData(padId, data, callback) {
@ -272,7 +272,7 @@ function updatePadData(padId, data, callback) {
_promiseComplete(Pad.update(data, { where: { id: padId } }), next);
},
function(affectedNumber, next) {
getPadData(padId, next);
getPadData(data.id || padId, next);
}
], callback);
}

Wyświetl plik

@ -7,7 +7,7 @@ function notifyPadListeners(padId, eventType, getData) {
var isFunc = (typeof getData == "function");
( listeners[padId] || [ ]).forEach(function(listener) {
var data = isFunc ? getData(listener.bbox) : getData;
var data = isFunc ? getData(listener) : getData;
if(data == null)
return;
@ -34,8 +34,21 @@ function removePadListener(listener) {
listeners[listener.padId] = l.slice(0, idx).concat(l.slice(idx+1));
}
function changePadId(oldPadId, newPadId) {
if(oldPadId == newPadId)
return;
listeners[newPadId] = listeners[oldPadId];
delete listeners[oldPadId];
listeners[newPadId].forEach(function(listener) {
listener.padId = newPadId;
});
}
module.exports = {
notifyPadListeners : notifyPadListeners,
addPadListener : addPadListener,
removePadListener: removePadListener
removePadListener: removePadListener,
changePadId: changePadId
};

Wyświetl plik

@ -66,19 +66,7 @@ database.connect(function(err) {
if(err)
return _sendData(socket, "padData", err);
socket.padId = data.id;
socket.writable = data.writable;
listeners.addPadListener(socket);
_sendData(socket, "padData", null, data);
_sendStreamData(socket, "view", database.getViews(socket.padId));
_sendStreamData(socket, "type", database.getTypes(socket.padId));
_sendStreamData(socket, "line", database.getPadLines(socket.padId));
if(socket.bbox) { // In case bbox is set while fetching pad data
_sendStreamData(socket, "marker", database.getPadMarkers(socket.padId, socket.bbox));
_sendStreamData(socket, "linePoints", database.getLinePoints(socket.padId, socket.bbox));
}
_setPadId(socket, data);
});
},
@ -103,8 +91,25 @@ database.connect(function(err) {
listeners.removePadListener(socket);
},
createPad : function(data, callback) {
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
return callback("Invalid parameters.");
if(socket.padId)
return callback("Pad already loaded.");
database.createPad(data, function(err, padData) {
if(err)
return callback(err);
_setPadId(socket, padData);
callback(null, padData);
});
},
editPad : function(data, callback) {
if(!utils.stripObject(data, { name: "string", defaultViewId: "number" }))
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
return callback("Invalid parameters.");
if(!socket.writable)
@ -241,6 +246,9 @@ database.connect(function(err) {
},
exportGpx : function(data, callback) {
if(socket.padId == null)
return callback("No pad ID set.");
gpx.exportGpx(socket.padId, data.useTracks, callback);
},
@ -278,7 +286,9 @@ database.connect(function(err) {
function _sendData(socket, eventName, err, data) {
if(err) {
console.warn("_sendData", err, err.stack);
return socket.emit("error", err);
socket.emit("serverError", err);
socket.disconnect();
return;
}
socket.emit(eventName, data);
@ -292,4 +302,20 @@ function _sendStreamData(socket, eventName, stream) {
console.warn("_sendStreamData", err, err.stack);
socket.emit("error", err);
})
}
function _setPadId(socket, data) {
socket.padId = data.id;
socket.writable = data.writable;
listeners.addPadListener(socket);
_sendData(socket, "padData", null, data);
_sendStreamData(socket, "view", database.getViews(socket.padId));
_sendStreamData(socket, "type", database.getTypes(socket.padId));
_sendStreamData(socket, "line", database.getPadLines(socket.padId));
if(socket.bbox) { // In case bbox is set while fetching pad data
_sendStreamData(socket, "marker", database.getPadMarkers(socket.padId, socket.bbox));
_sendStreamData(socket, "linePoints", database.getLinePoints(socket.padId, socket.bbox));
}
}