const membersApiUrl = 'https://lilia.mraveniste.cc/horde-members/api/members'; const featuresApiUrl = 'https://lilia.mraveniste.cc/horde-members/api/features'; const apiKey = 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTU1Mzg5MjYwNSwiZXhwIjoxODY5MjUyNjA1fQ.ImNsaWVudCI.iX2EEEsKbMYpbeU-HB_FcqeepwZn8rkq8XdyfUmr6RJk2-64I744xLdKfrikxskF6_IlJbjBH3jNNcVfWyvswQ'; const discordClientId = '562565397451374593'; const cheeseHordeGuildId = '285154599038353408'; var discordUser = undefined; function store(feature) { var writer = new ol.format.GeoJSON({ dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); $.ajax({ url: featuresApiUrl, type: 'post', contentType: 'application/json', data: writer.writeFeature(feature), headers: {'X-Members-API-Key': apiKey} }); } function remove(id) { $.ajax({ url: `${membersApiUrl}/${id}`, type: 'delete', 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 discord = new LoginWithDiscord({ clientID: discordClientId, scopes: [ Scope.Identify, Scope.Guilds ], cache: true }); discord.onlogin = async () => { var user = await discord.fetchUser(); var guilds = await discord.fetchGuilds(); var horde = guilds.find((guild) => guild.id == cheeseHordeGuildId); if (horde !== undefined) { discordUser = user; var button = discordControl.element.firstElementChild; button.innerHTML = `${discordUser.username}: Log out`; button.style.display = null; } } discord.onlogout = async () => { discordUser = undefined; var button = discordControl.element.firstElementChild; button.innerHTML = 'Log in with Discord'; button.style.display = null; }; async function discordLogin() { await discord.login(); } async function discordLogout() { await discord.logout(); } var DiscordControl = (function(Control) { function DiscordControl(opt_options) { var options = opt_options || {}; var button = document.createElement('button'); button.style.display = 'none'; var element = document.createElement('div'); element.className = 'discord-control ol-unselectable ol-control'; element.appendChild(button); Control.call(this, { element: element, target: options.target }); button.addEventListener('click', this.handleClick.bind(this), false); } if (Control) DiscordControl.__proto__ = Control; DiscordControl.prototype = Object.create(Control && Control.prototype); DiscordControl.prototype.constructor = DiscordControl; DiscordControl.prototype.handleClick = function() { if (!discordUser) discordLogin(); else discordLogout(); }; return DiscordControl; }(ol.control.Control)); var discordControl = new DiscordControl(); var source = new ol.source.Vector({ url: featuresApiUrl, 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([ discordControl ]) }); var snap = new ol.interaction.Snap({ source: source }); map.addInteraction(snap); var modify = new ol.interaction.Modify({ source: source, condition: (evt) => { if (!discordUser) return false; var feature = source.getClosestFeatureToCoordinate(evt.coordinate); if (!feature) return false; return feature.get('id') == discordUser.id; } }); 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 (!discordUser) return false; var feature = source.forEachFeature((feature) => { if (feature.get('id') == discordUser.id) return feature; }); if (feature === undefined) return true; var features = []; // FIXME: allows duplication map.forEachFeatureAtPixel(evt.pixel, (feature) => { features.push(feature); }); return features.length > 1; } }); draw.on('drawend', (evt) => { evt.feature.set('id', discordUser.id); evt.feature.set('nick', discordUser.username); store(evt.feature); }); map.addInteraction(draw); document.addEventListener('keydown', (evt) => { if (evt.code != 'Delete') return; if (!discordUser) return; var feature = source.forEachFeature((feature) => { if (feature.get('id') == discordUser.id) return feature; }); if (feature === undefined) return; if (confirm('Are you sure you want to remove yourself?')) { source.removeFeature(feature); remove(discordUser.id); } });