kopia lustrzana https://github.com/FacilMap/facilmap
Add dialog to edit filter and make it possible to save it in view
rodzic
74f888977f
commit
a1bbb51d9c
|
@ -0,0 +1,5 @@
|
||||||
|
.alert pre {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="$dismiss()"><span aria-hidden="true">×</span></button>
|
||||||
|
<h3 class="modal-title">Filter</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="form-horizontal" ng-submit="!error && save()">
|
||||||
|
<textarea class="form-control" ng-model="filter"></textarea>
|
||||||
|
<div uib-alert class="alert-danger" ng-show="error"><pre>{{error.message || error}}</pre></div>
|
||||||
|
|
||||||
|
<p class="text-right">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="$dismiss()">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" ng-click="$close(filter)" ng-disabled="error">Apply</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h3>Syntax</h3>
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Variable</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Example</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>name</code></td>
|
||||||
|
<td>Marker/Line name</td>
|
||||||
|
<td><code>name == "Berlin"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>type</code></td>
|
||||||
|
<td><code>marker</code> / <code>line</code></td>
|
||||||
|
<td><code>type == "marker"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>typeId</code></td>
|
||||||
|
<td><span ng-repeat="type in types"><span ng-if="$index != 0"> / </span> <code>{{type.id}}</code> ({{type.name}})</span></td>
|
||||||
|
<td><code>typeId == 1</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>data.<field></code> / <code>prop(data, <field>)</code></td>
|
||||||
|
<td>Field values (example: <code>data.Description</code> or <code>prop(data, "Description")</code></td>
|
||||||
|
<td><code>data.Description ~= "camp"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>lat</code>, <code>lon</code></td>
|
||||||
|
<td>Marker coordinates</td>
|
||||||
|
<td><code>lat < 50</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>colour</code></td>
|
||||||
|
<td>Marker/line colour</td>
|
||||||
|
<td><code>colour == "ff0000"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>size</code></td>
|
||||||
|
<td>Marker size</td>
|
||||||
|
<td><code>size > 30</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>symbol</code></td>
|
||||||
|
<td>Marker icon</td>
|
||||||
|
<td><code>symbol == "accommodation_camping"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>mode</code></td>
|
||||||
|
<td>Line routing mode (empty / <code>car</code> / <code>bicycle</code> / <code>pedestrian</code> / <code>track</code>)</td>
|
||||||
|
<td><code>mode in ("bicycle", "pedestrian")</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>width</code></td>
|
||||||
|
<td>Line width</td>
|
||||||
|
<td><code>width > 10</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>distance</code></td>
|
||||||
|
<td>Line distance in kilometers</td>
|
||||||
|
<td><code>distance < 50</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>time</code></td>
|
||||||
|
<td>Line routing time in seconds</td>
|
||||||
|
<td><code>time > 3600</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>routePoints</code></td>
|
||||||
|
<td>Line point coordinates</td>
|
||||||
|
<td><code>routePoints.0.lon > 60 and routePoints.2.lat < 50</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Operator</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Example</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>number</code></td>
|
||||||
|
<td>Numerical value</td>
|
||||||
|
<td><code>distance < 1.5</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>"text"</code></td>
|
||||||
|
<td>Text value</td>
|
||||||
|
<td><code>name == "Athens"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code>, <code>^</code></td>
|
||||||
|
<td>Mathematical operations (<code>%</code>: modulo, <code>^</code>: power)</td>
|
||||||
|
<td><code>distance / time > 30</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>and</code>, <code>or</code>, <code>not</code>, <code>()</code></td>
|
||||||
|
<td>Logical operators</td>
|
||||||
|
<td><code>not (size>10) or (type=="line" and length<=10)</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>? :</code></td>
|
||||||
|
<td>if/then/else operator</td>
|
||||||
|
<td><code>(type=="marker" ? size : width) > 10</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>==</code>, <code>!=</code>, <code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td>
|
||||||
|
<td>Comparison (<code>!=</code>: not equal) (case sensitive)</td>
|
||||||
|
<td><code>type != "marker"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>in</code>, <code>not in</code></td>
|
||||||
|
<td>List operator (case sensitive)</td>
|
||||||
|
<td><code>typeId not in (1,2)</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>~=</code></td>
|
||||||
|
<td>Regular expression match (case sensitive)</td>
|
||||||
|
<td><code>name ~= "^[Cc]amp$"</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>ceil()</code>, <code>floor()</code>, <code>round()</code></td>
|
||||||
|
<td>Round (<code>ceil</code>: up, <code>floor</code>: down)</td>
|
||||||
|
<td><code>floor(distance/100) == 5</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>abs()</code>, <code>log()</code>, <code>sqrt()</code></td>
|
||||||
|
<td>Mathematical functions</td>
|
||||||
|
<td><code>abs(lat) < 30</code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>min()</code>, <code>max()</code></td>
|
||||||
|
<td>Smallest/highest value</td>
|
||||||
|
<td><code>min(routePoints.0.lat,routePoints.1.lat) < 50</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="$dismiss()">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" ng-click="$close(filter)" ng-disabled="error">Apply</button>
|
||||||
|
</div>
|
|
@ -1,14 +1,26 @@
|
||||||
(function(fm, $, ng, undefined) {
|
(function(fm, $, ng, undefined) {
|
||||||
|
|
||||||
fm.app.factory("fmFilter", function(compileExpression) {
|
fm.app.factory("fmFilter", function(compileExpression, $rootScope, $uibModal) {
|
||||||
var currentVal;
|
var currentVal;
|
||||||
|
|
||||||
var fmFilter = {
|
var fmFilter = {
|
||||||
customFuncs: {
|
customFuncs: {
|
||||||
prop: function(obj, key) {
|
prop: function(obj, key) {
|
||||||
return obj && obj[key];
|
return obj && obj[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
random: function() { } // Does not work well with angular digest cycles
|
||||||
|
},
|
||||||
|
|
||||||
|
hasError: function(expr) {
|
||||||
|
try {
|
||||||
|
if(expr && expr.trim())
|
||||||
|
compileExpression(expr, fmFilter.customFuncs);
|
||||||
|
} catch(e) {
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
compileExpression: function(expr) {
|
compileExpression: function(expr) {
|
||||||
if(!expr || !expr.trim())
|
if(!expr || !expr.trim())
|
||||||
return function() { return true; };
|
return function() { return true; };
|
||||||
|
@ -86,10 +98,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
showFilterDialog: function(currentFilter, types) {
|
||||||
|
var dialog = $uibModal.open({
|
||||||
|
templateUrl: "filter/filter-dialog.html",
|
||||||
|
scope: $rootScope,
|
||||||
|
controller: "fmFilterDialogCtrl",
|
||||||
|
size: "lg",
|
||||||
|
resolve: {
|
||||||
|
currentFilter: function() { return currentFilter; },
|
||||||
|
types: function() { return types; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog.result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return fmFilter;
|
return fmFilter;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fm.app.controller("fmFilterDialogCtrl", function(currentFilter, types, $scope, fmFilter) {
|
||||||
|
$scope.filter = currentFilter;
|
||||||
|
$scope.types = types;
|
||||||
|
|
||||||
|
$scope.$watch("filter", function(newFilter) {
|
||||||
|
$scope.error = fmFilter.hasError(newFilter);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
})(FacilMap, jQuery, angular);
|
})(FacilMap, jQuery, angular);
|
|
@ -188,7 +188,7 @@
|
||||||
map.mapEvents.$emit("layerchange");
|
map.mapEvents.$emit("layerchange");
|
||||||
});
|
});
|
||||||
|
|
||||||
map.getCurrentView = function() {
|
map.getCurrentView = function(addFilter) {
|
||||||
var ret = fmUtils.leafletToFmBbox(map.map.getBounds());
|
var ret = fmUtils.leafletToFmBbox(map.map.getBounds());
|
||||||
ret.layers = [ ];
|
ret.layers = [ ];
|
||||||
|
|
||||||
|
@ -199,6 +199,9 @@
|
||||||
ret.layers.push(it.options.fmKey);
|
ret.layers.push(it.options.fmKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(addFilter)
|
||||||
|
ret.filter = map.socket.filterExpr;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,6 +227,8 @@
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
map.map.setView(bounds.getCenter(), map.map.getBoundsZoom(bounds, !view));
|
map.map.setView(bounds.getCenter(), map.map.getBoundsZoom(bounds, !view));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map.socket.setFilter(view.filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
map.map.createPane("fmClickListener");
|
map.map.createPane("fmClickListener");
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
<ul uib-dropdown-menu aria-labelledby="toolbox-layers-dropdown" class="dropdown-menu-right">
|
<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 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="importFile()">Import File</a></li>
|
||||||
|
<li><a href="javascript:" ng-click="filter()">Filter</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="exportGpx()">Export GPX</a></li>
|
||||||
<li ng-if="padId"><a href="javascript:" ng-click="showTable()">View as table</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 ng-if="!readonly && padId"><a href="javascript:" ng-click="editPadSettings()">Settings</a></li>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
(function(fm, $, ng, undefined) {
|
(function(fm, $, ng, undefined) {
|
||||||
|
|
||||||
fm.app.factory("fmMapToolbox", function($compile, $templateCache, fmTable) {
|
fm.app.factory("fmMapToolbox", function($compile, $templateCache, fmTable, fmFilter) {
|
||||||
return function(map) {
|
return function(map) {
|
||||||
var scope = map.socket.$new();
|
var scope = map.socket.$new();
|
||||||
|
|
||||||
|
@ -49,6 +49,12 @@
|
||||||
map.padUi.createPad();
|
map.padUi.createPad();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope.filter = function() {
|
||||||
|
fmFilter.showFilterDialog(map.socket.filterExpr, map.socket.types).then(function(newFilter) {
|
||||||
|
map.socket.setFilter(newFilter);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var ret = {
|
var ret = {
|
||||||
div: $($templateCache.get("map/toolbox/toolbox.html"))
|
div: $($templateCache.get("map/toolbox/toolbox.html"))
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,18 +3,31 @@
|
||||||
<h3 class="modal-title">Save current view</h3>
|
<h3 class="modal-title">Save current view</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form class="form-horizontal" ng-submit="save(false)">
|
<form class="form-horizontal" ng-submit="save()">
|
||||||
<div uib-alert class="alert-danger" ng-if="error">{{error}}</div>
|
<div uib-alert class="alert-danger" ng-if="error">{{error.message || error}}</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="save-view-name" class="col-sm-3 control-label">Name</label>
|
<label for="save-view-name" class="col-sm-3 control-label">Name</label>
|
||||||
<div class="col-sm-9"><input id="save-view-name" ng-model="name" class="form-control"></div>
|
<div class="col-sm-9"><input id="save-view-name" ng-model="name" class="form-control"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox" ng-show="filter">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" ng-model="saveFilter">
|
||||||
|
Save current filter (<code ng-bind="filter"></code>)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" ng-model="makeDefault">
|
||||||
|
Make default view
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="hidden"></button>
|
<button type="submit" class="hidden"></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" ng-click="$dismiss()">Cancel</button>
|
<button type="button" class="btn btn-default" ng-click="$dismiss()">Cancel</button>
|
||||||
<button type="submit" class="btn btn-primary" ng-click="save(false)">Save</button>
|
<button type="submit" class="btn btn-primary" ng-click="save()">Save</button>
|
||||||
<button type="button" class="btn btn-success" ng-click="save(true)">Save and make default view</button>
|
|
||||||
</div>
|
</div>
|
|
@ -33,12 +33,13 @@
|
||||||
|
|
||||||
fm.app.controller("fmMapViewsSaveCtrl", function($scope, map) {
|
fm.app.controller("fmMapViewsSaveCtrl", function($scope, map) {
|
||||||
$scope.name = null;
|
$scope.name = null;
|
||||||
|
$scope.filter = map.socket.filterExpr;
|
||||||
|
|
||||||
$scope.save = function(makeDefault) {
|
$scope.save = function() {
|
||||||
var view = map.getCurrentView();
|
var view = map.getCurrentView($scope.saveFilter);
|
||||||
view.name = $scope.name;
|
view.name = $scope.name;
|
||||||
map.socket.emit("addView", view).then(function(view) {
|
map.socket.emit("addView", view).then(function(view) {
|
||||||
if(makeDefault)
|
if($scope.makeDefault)
|
||||||
return map.socket.emit("editPad", { defaultViewId: view.id });
|
return map.socket.emit("editPad", { defaultViewId: view.id });
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
$scope.$close();
|
$scope.$close();
|
||||||
|
|
|
@ -160,7 +160,8 @@ var View = conn.define("View", {
|
||||||
top : getLatType(),
|
top : getLatType(),
|
||||||
bottom : getLatType(),
|
bottom : getLatType(),
|
||||||
left : getLonType(),
|
left : getLonType(),
|
||||||
right : getLonType()
|
right : getLonType(),
|
||||||
|
filter: { type: Sequelize.TEXT, allowNull: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
Pad.hasMany(View, { foreignKey: "padId" });
|
Pad.hasMany(View, { foreignKey: "padId" });
|
||||||
|
@ -309,6 +310,10 @@ function connect(force) {
|
||||||
if(!attributes[col])
|
if(!attributes[col])
|
||||||
return queryInterface.addColumn('Types', col, Type.attributes[col]);
|
return queryInterface.addColumn('Types', col, Type.attributes[col]);
|
||||||
}));
|
}));
|
||||||
|
}),
|
||||||
|
queryInterface.describeTable('Views').then(function(attributes) {
|
||||||
|
if(!attributes.filter)
|
||||||
|
return queryInterface.addColumn('Views', 'filter', View.attributes.filter);
|
||||||
})
|
})
|
||||||
].concat([ 'Pads', 'Markers', 'Lines' ].map(function(table) {
|
].concat([ 'Pads', 'Markers', 'Lines' ].map(function(table) {
|
||||||
// allow null on Pad.name, Marker.name, Line.name
|
// allow null on Pad.name, Marker.name, Line.name
|
||||||
|
|
|
@ -208,7 +208,7 @@ var serverP = Promise.denodeify(server.listen.bind(server))(config.port, config.
|
||||||
|
|
||||||
addView : function(data) {
|
addView : function(data) {
|
||||||
return Promise.resolve().then(function() {
|
return Promise.resolve().then(function() {
|
||||||
if(!utils.stripObject(data, { name: "string", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number" }))
|
if(!utils.stripObject(data, { name: "string", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
||||||
throw "Invalid parameters.";
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
if(!socket.writable)
|
if(!socket.writable)
|
||||||
|
@ -220,7 +220,7 @@ var serverP = Promise.denodeify(server.listen.bind(server))(config.port, config.
|
||||||
|
|
||||||
editView : function(data) {
|
editView : function(data) {
|
||||||
return Promise.resolve().then(function() {
|
return Promise.resolve().then(function() {
|
||||||
if(!utils.stripObject(data, { id: "number", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number" }))
|
if(!utils.stripObject(data, { id: "number", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
||||||
throw "Invalid parameters.";
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
if(!socket.writable)
|
if(!socket.writable)
|
||||||
|
|
Ładowanie…
Reference in New Issue