From 79588d05e1ce4fbbbcef8091f9c6e51cbdd61078 Mon Sep 17 00:00:00 2001 From: Candid Dauth Date: Wed, 1 Mar 2017 20:02:57 +0100 Subject: [PATCH] Render table as static html under fixed URL (fixes #58) --- frontend/app/map/toolbox/toolbox.html | 9 ++- frontend/app/map/toolbox/toolbox.js | 6 +- frontend/app/table/table.css | 7 --- frontend/app/table/table.html | 44 -------------- frontend/app/table/table.js | 79 ------------------------ frontend/gulpfile.js | 2 +- frontend/package.json | 3 +- frontend/table/table.css | 7 +++ frontend/table/table.ejs | 88 +++++++++++++++++++++++++++ frontend/table/table.js | 27 ++++++++ frontend/webpack.config.js | 15 ++++- frontend/yarn.lock | 8 ++- server/table.js | 44 ++++++++++++++ server/webserver.js | 23 +++++++ 14 files changed, 218 insertions(+), 144 deletions(-) delete mode 100644 frontend/app/table/table.css delete mode 100644 frontend/app/table/table.html delete mode 100644 frontend/app/table/table.js create mode 100644 frontend/table/table.css create mode 100644 frontend/table/table.ejs create mode 100644 frontend/table/table.js create mode 100644 server/table.js diff --git a/frontend/app/map/toolbox/toolbox.html b/frontend/app/map/toolbox/toolbox.html index 9ac45b83..175b9be5 100644 --- a/frontend/app/map/toolbox/toolbox.html +++ b/frontend/app/map/toolbox/toolbox.html @@ -39,11 +39,14 @@ diff --git a/frontend/app/map/toolbox/toolbox.js b/frontend/app/map/toolbox/toolbox.js index 31bac185..a86836c1 100644 --- a/frontend/app/map/toolbox/toolbox.js +++ b/frontend/app/map/toolbox/toolbox.js @@ -1,7 +1,7 @@ import fm from '../../app'; import $ from 'jquery'; -fm.app.factory("fmMapToolbox", function($compile, fmTable, fmFilter) { +fm.app.factory("fmMapToolbox", function($compile, fmFilter) { return function(map) { var scope = map.socket.$new(); @@ -32,10 +32,6 @@ fm.app.factory("fmMapToolbox", function($compile, fmTable, fmFilter) { scope.editObjectTypes = map.typesUi.editTypes.bind(map.typesUi); - scope.showTable = function() { - fmTable.showTable(map.socket.padId); - }; - scope.importFile = function() { map.importUi.openImportDialog(); }; diff --git a/frontend/app/table/table.css b/frontend/app/table/table.css deleted file mode 100644 index 31840261..00000000 --- a/frontend/app/table/table.css +++ /dev/null @@ -1,7 +0,0 @@ -table th.sort-none,table th.sort-up { - cursor: s-resize; -} - -table th.sort-down { - cursor: n-resize; -} \ No newline at end of file diff --git a/frontend/app/table/table.html b/frontend/app/table/table.html deleted file mode 100644 index 1b1539df..00000000 --- a/frontend/app/table/table.html +++ /dev/null @@ -1,44 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/app/table/table.js b/frontend/app/table/table.js deleted file mode 100644 index 4533cf53..00000000 --- a/frontend/app/table/table.js +++ /dev/null @@ -1,79 +0,0 @@ -import fm from '../app'; -import $ from 'jquery'; - -fm.app.filter("fmType", function() { - return function(input, typeId) { - var res = [ ]; - angular.forEach(input, function(it) { - if(it.typeId == typeId) - res.push(it); - }); - return res; - }; -}); - -fm.app.factory("fmTable", function(fmSocket, $rootScope, $uibModal) { - return { - showTable : function(padId) { - var socket = fmSocket(padId); - socket.updateBbox({ top: 90, left: -180, right: 180, bottom: -90, zoom: 0 }); - - $uibModal.open({ - template: require("./table.html"), - scope: socket, - controller: "fmTableCtrl", - size: "fs" - }); - } - }; -}); - -fm.app.controller("fmTableCtrl", function($scope, fmTypeFields) { - function _getField(type, fieldName) { - for(var i=0; i").append(fmTypeFields.formatField(_getField(type, f), it.data[f])).text()); }; - }; - - $scope.getSortClass = function(type, fieldName) { - if(($scope.sortField[type.id] == null ? "__name" : $scope.sortField[type.id]) == fieldName) { - return $scope.sortOrder[type.id] ? "sort-up" : "sort-down"; - } else { - return "sort-none"; - } - }; - - $scope.getSortIcon = function(type, fieldName) { - if(($scope.sortField[type.id] == null ? "__name" : $scope.sortField[type.id]) == fieldName) { - return { - 'glyphicon': true, - 'glyphicon-triangle-bottom': !$scope.sortOrder[type.id], - 'glyphicon-triangle-top': $scope.sortOrder[type.id] - }; - } else { - return { }; - } - }; - - $scope.sortField = { }; - $scope.sortOrder = { }; -}); diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 09aaa5d3..ab2f6d4c 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -74,7 +74,7 @@ gulp.task("webpack", [ "icons" ], function() { return Promise.denodeify(fs.unlink)(staticFrontendFile); }).then(() => { // Create symlink with fixed file name so that people can include https://facilmap.org/frontend.js - return Promise.denodeify(fs.symlink)(`frontend-${stats.hash}.js`, `${__dirname}/build/frontend.js`); + return Promise.denodeify(fs.symlink)(`frontend-index-${stats.hash}.js`, `${__dirname}/build/frontend.js`); }); }); }); diff --git a/frontend/package.json b/frontend/package.json index 9f73a7ae..f7a2a238 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,7 +46,8 @@ "leaflet.markercluster": "^1.0.3", "linkifyjs": "^2.1.3", "marked": "^0.3.6", - "osmtogeojson": "^2.2.12" + "osmtogeojson": "^2.2.12", + "tablesorter": "^2.28.5" }, "devDependencies": { "babel-core": "^6.21.0", diff --git a/frontend/table/table.css b/frontend/table/table.css new file mode 100644 index 00000000..2df7a419 --- /dev/null +++ b/frontend/table/table.css @@ -0,0 +1,7 @@ +h2[aria-expanded=true] .glyphicon-chevron-right { + transform: rotate(90deg); +} + +table.collapse.in { + display: table; +} \ No newline at end of file diff --git a/frontend/table/table.ejs b/frontend/table/table.ejs new file mode 100644 index 00000000..f4eedd1f --- /dev/null +++ b/frontend/table/table.ejs @@ -0,0 +1,88 @@ + + + + + <%=padData.name%> – FacilMap + + +<% + if(!padData || padData.searchEngines) { +%> + + " /> +<% + } else { +%> + +<% + } +%> + + + +
+

<%=padData.name%> – FacilMap

+<% + for(let type of Object.values(types)) { +%> +

<%=type.name%>

+ + + + +<% + if(type.type == "marker") { +%> + +<% + } else { +%> + + +<% + } + + for(let field of type.fields) { +%> + +<% + } +%> + + + +<% + for(let object of type.type == "marker" ? type.markers : type.lines) { +%> + + +<% + if(type.type == "marker") { +%> + +<% + } else { +%> + + +<% + } + + for(let field of type.fields) { +%> + +<% + } +%> + +<% + } +%> + +
NamePositionDistanceTime<%=field.name%>
<%=object.name%><%=object.lat%>,<%=object.lon%><%=format.round(object.distance, 2)%> km<% if(object.time != null) { %><%=format.formatTime(object.time)%> h <%=format.routingMode(object.mode)%><% } %><%-format.formatField(field, object.data[field.name])%>
+<% + } +%> +
+ + \ No newline at end of file diff --git a/frontend/table/table.js b/frontend/table/table.js new file mode 100644 index 00000000..0ca0c8c1 --- /dev/null +++ b/frontend/table/table.js @@ -0,0 +1,27 @@ +import 'bootstrap'; +import $ from 'jquery'; +import 'tablesorter/dist/js/jquery.tablesorter'; +import 'tablesorter/dist/js/widgets/widget-uitheme.min.js'; +import 'tablesorter/dist/css/theme.bootstrap_3.min.css'; +import './table.css'; + +// Dereferrer +$(document).on("click", "a", function(e) { + var el = $(this); + var href = el.attr("href"); + if(href && href.match(/^\s*(https?:)?\/\//i)) { + el.attr("href", "deref.html?"+encodeURIComponent(href)); + + setTimeout(function() { + el.attr("href", href); + }, 0); + } +}); + +$(document).ready(() => { + $("table.tablesorter").tablesorter({ + theme: "bootstrap", + headerTemplate: "{content} {icon}", + widgets: ["uitheme"] + }); +}); diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 27bb2939..c62faac9 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -34,9 +34,12 @@ for(let i in addDeps) { } module.exports = { - entry: __dirname + "/index/index.js", + entry: { + index: __dirname + "/index/index.js", + table: __dirname + "/table/table.js" + }, output: { - filename: "frontend-[hash].js", + filename: "frontend-[name]-[hash].js", path: __dirname + "/build/" }, resolve: { @@ -72,7 +75,13 @@ module.exports = { }), new htmlPlugin({ template: `${__dirname}/index/index.ejs`, - filename: "index.ejs" + filename: "index.ejs", + chunks: ["index"] + }), + new htmlPlugin({ + template: `${__dirname}/table/table.ejs`, + filename: "table.ejs", + chunks: ["table"] }), new webpack.ProvidePlugin({ $: "jquery", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index dab9cab3..3386e47e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2538,7 +2538,7 @@ jquery-ui@^1.12.1: version "1.12.1" resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" -jquery@<3.0.0, jquery@>=1.9.0: +jquery@<3.0.0, jquery@>=1.2.6, jquery@>=1.9.0: version "2.2.4" resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02" @@ -4478,6 +4478,12 @@ svgo@^0.7.0, svgo@^0.7.1: sax "~1.2.1" whet.extend "~0.9.9" +tablesorter@^2.28.5: + version "2.28.5" + resolved "https://registry.yarnpkg.com/tablesorter/-/tablesorter-2.28.5.tgz#94b6f8326e9ff8b001ae7cf57804476caf8b3437" + dependencies: + jquery ">=1.2.6" + tapable@^0.2.5, tapable@~0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" diff --git a/server/table.js b/server/table.js new file mode 100644 index 00000000..32c81b48 --- /dev/null +++ b/server/table.js @@ -0,0 +1,44 @@ +const ejs = require("ejs"); +const fs = require("fs"); +const Promise = require("promise"); + +const commonUtils = require("facilmap-frontend/common/utils"); +const commonFormat = require("facilmap-frontend/common/format"); + +const utils = require("./utils"); + +const table = module.exports = { + createTable(database, padId, template) { + return utils.promiseAuto({ + padData: database.getPadData(padId), + + types: () => { + var types = { }; + return utils.streamEachPromise(database.getTypes(padId), function(type) { + types[type.id] = type; + type.markers = []; + type.lines = []; + }).then(() => types); + }, + + markers: (types) => { + return utils.streamEachPromise(database.getPadMarkers(padId), function(marker) { + types[marker.typeId].markers.push(marker); + }); + }, + + lines: (types) => { + return utils.streamEachPromise(database.getPadLines(padId), function(line) { + types[line.typeId].lines.push(line); + }); + } + }).then((results) => { + return ejs.render(template, { + padData: results.padData, + types: results.types, + utils: commonUtils, + format: commonFormat + }) + }) + } +}; \ No newline at end of file diff --git a/server/webserver.js b/server/webserver.js index 72e3e9fe..99db59f8 100644 --- a/server/webserver.js +++ b/server/webserver.js @@ -8,6 +8,7 @@ const Promise = require("promise"); const database = require("./database/database"); const gpx = require("./gpx"); +const table = require("./table"); const utils = require("./utils"); const frontendPath = path.dirname(require.resolve("facilmap-frontend/package.json")); // Do not resolve main property @@ -80,6 +81,7 @@ const webserver = module.exports = { app.get("/", padMiddleware); app.get("/index.ejs", padMiddleware); + app.get("/table.ejs", padMiddleware); app.use(staticMiddleware); @@ -104,6 +106,27 @@ const webserver = module.exports = { }).catch(next); }); + app.get("/:padId/table", function(req, res, next) { + Promise.resolve().then(() => { + if (process.env.FM_DEV) { + let intercept = utils.interceptWriteStream(res); + req.url = req.originalUrl = "/table.ejs"; + staticMiddleware(req, res, next); + return intercept; + } else { + // We don't want express.static's ETag handling, as it sometimes returns an empty template, + // so we have to read it directly from the file system + + return Promise.denodeify(fs.readFile)(`${frontendPath}/build/table.ejs`, "utf8"); + } + }).then((template) => { + return table.createTable(database, req.params.padId, template); + }).then((renderedTable) => { + res.type("html"); + res.send(renderedTable); + }).catch(next); + }); + let server = http.createServer(app); return Promise.denodeify(server.listen.bind(server))(port, host).then(() => server); }