diff --git a/Dockerfile b/Dockerfile index 4804ac2..3580dc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,8 @@ FROM nginx:alpine COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY horde-map /horde-map/horde-map/assets + COPY --from=builder /clips/dist /clips/clips/assets COPY --from=builder /gifted-subs/dist /gifted-subs/gifted-subs/assets COPY --from=builder /merchandise/dist /merchandise/merchandise/assets diff --git a/horde-map/map.css b/horde-map/map.css new file mode 100644 index 0000000..66843f3 --- /dev/null +++ b/horde-map/map.css @@ -0,0 +1,20 @@ +.map { + display: flex; + flex-flow: column; + height: 100%; + min-height: 600px; +} + +.add-control { + top: 4em; + left: 0.5em; +} + +.ol-touch .add-control { + top: 5em; +} + +.add-control button { + width: 8em; + font-size: 11pt; +} diff --git a/horde-map/map.js b/horde-map/map.js new file mode 100644 index 0000000..e6c1c7a --- /dev/null +++ b/horde-map/map.js @@ -0,0 +1,216 @@ +const apiUrl = '/horde-members/api/features'; +const apiKey = 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTU1Mzg5MjYwNSwiZXhwIjoxODY5MjUyNjA1fQ.ImNsaWVudCI.iX2EEEsKbMYpbeU-HB_FcqeepwZn8rkq8XdyfUmr6RJk2-64I744xLdKfrikxskF6_IlJbjBH3jNNcVfWyvswQ'; + +var nick = ''; + +function store(feature) { + var writer = new ol.format.GeoJSON({ + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857' + }); + + $.ajax({ + url: apiUrl, + type: 'post', + contentType: 'application/json', + data: writer.writeFeature(feature), + headers: {'X-Members-API-Key': apiKey} + }); +} + +function getColor(nick, alpha) { + var r = 0; + var g = 0; + var b = 0; + + var p1 = nick.length * 351 / 1000 >> 0; + var p2 = p1 + nick.length * 206 / 1000 >> 0; + + nick.slice(0, p1).split('').map((c) => { + r += c.charCodeAt(0) + }); + nick.slice(p1, p2).split('').map((c) => { + g += c.charCodeAt(0) + }); + nick.slice(p2).split('').map((c) => { + b += c.charCodeAt(0) + }); + + r %= 256; + g %= 256; + b %= 256; + + var k = (299 * r + 587 * g + 114 * b) / 1000; + + while (k > 123) { + if (r > 0) r--; + if (g > 0) g--; + if (b > 0) b--; + + k = (299 * r + 587 * g + 114 * b) / 1000; + } + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + +var AddControl = (function (Control) { + function AddControl(opt_options) { + var options = opt_options || {}; + + var button = document.createElement('button'); + button.innerHTML = 'Add yourself'; + + var element = document.createElement('div'); + element.className = 'add-control ol-unselectable ol-control'; + element.appendChild(button); + + Control.call(this, { + element: element, + target: options.target + }); + + button.addEventListener('click', this.handleAdd.bind(this), false); + } + + if (Control) + AddControl.__proto__ = Control; + + AddControl.prototype = Object.create(Control && Control.prototype); + AddControl.prototype.constructor = AddControl; + + AddControl.prototype.handleAdd = function handleAdd() { + nick = prompt('Please enter your nickname', ''); + }; + + return AddControl; +}(ol.control.Control)); + +var source = new ol.source.Vector({ + url: apiUrl, + wrapX: false, + format: new ol.format.GeoJSON({ + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857' + }) +}); + +var raster = new ol.layer.Tile({ + source: new ol.source.OSM() +}); + +var vector = new ol.layer.Vector({ + source: source, + style: (feature) => new ol.style.Style({ + image: new ol.style.Circle({ + radius: 6, + fill: new ol.style.Fill({ + color: getColor(feature.get('nick'), 0.8) + }) + }), + text: new ol.style.Text({ + text: feature.get('nick'), + font: '12pt sans-serif', + textAlign: 'left', + offsetY: -20, + fill: new ol.style.Fill({ + color: 'rgba(0, 0, 0, 0.8)' + }), + backgroundFill: new ol.style.Fill({ + color: 'rgba(255, 255, 255, 0.8)' + }), + backgroundStroke: new ol.style.Stroke({ + color: 'rgba(0, 0, 0, 0.8)', + width: 1 + }), + padding: [2, 3, 1, 3] + }) + }) +}); + +var map = new ol.Map({ + target: 'map', + layers: [raster, vector], + view: new ol.View({ + center: [0, 0], + minZoom: 2, + zoom: 2 + }), + controls: ol.control.defaults().extend([ + new AddControl() + ]) +}); + +var snap = new ol.interaction.Snap({ + source: source +}); + +map.addInteraction(snap); + +var modify = new ol.interaction.Modify({ + source: source, + condition: (evt) => { + if (!nick) + return false; + + var feature = source.getClosestFeatureToCoordinate(evt.coordinate); + + if (!feature) + return false; + + return feature.get('current') !== undefined; + } +}); + +modify.on('modifyend', (evt) => { + var feature = evt.features.getArray().find((feature) => feature.getRevision() > 1); + + store(feature); +}); + +map.addInteraction(modify); + +var draw = new ol.interaction.Draw({ + source: source, + type: 'Point', + condition: (evt) => { + if (!nick) + return false; + + var feature = source.forEachFeature((feature) => { + if (feature.get('nick') == nick) + return feature; + }); + + if (feature === undefined) { + return true; + } + + if (feature.get('current') === undefined) + return false; + + var features = []; + + map.forEachFeatureAtPixel(evt.pixel, (feature) => { + features.push(feature); + }); + + return features.length > 1; + } +}); + +draw.on('drawend', (evt) => { + var feature = source.forEachFeature((feature) => { + if (feature.get('nick') == nick) + return feature; + }); + + if (feature !== undefined) + source.removeFeature(feature); + + evt.feature.set('nick', nick); + evt.feature.set('current', true); + + store(evt.feature); +}); + +map.addInteraction(draw); diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 7f59b7c..ae01f2c 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -58,6 +58,10 @@ http { root /clips; } + location ^~ /horde-map/assets/ { + root /horde-map; + } + location ^~ /gifted-subs/assets/ { root /gifted-subs; }