diff --git a/main/forms.py b/main/forms.py index 8548dd8d..97cd21f9 100644 --- a/main/forms.py +++ b/main/forms.py @@ -141,6 +141,7 @@ class Meta: ), "user_name": forms.TextInput(attrs={"maxlength": 500}), "user_polygon": forms.HiddenInput(), + "is_final": forms.TextInput() } label = { "user_name": "Input your full name: ", diff --git a/main/migrations/0087_signature_edit_hash.py b/main/migrations/0087_signature_edit_hash.py new file mode 100644 index 00000000..77e81bb5 --- /dev/null +++ b/main/migrations/0087_signature_edit_hash.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2021-07-13 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0086_merge_20210617_1455"), + ] + + operations = [ + migrations.AddField( + model_name="signature", + name="edit_hash", + field=models.CharField(blank=True, max_length=64), + ), + ] diff --git a/main/migrations/0088_auto_20210713_1540.py b/main/migrations/0088_auto_20210713_1540.py new file mode 100644 index 00000000..92ea66b9 --- /dev/null +++ b/main/migrations/0088_auto_20210713_1540.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2021-07-13 15:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0087_signature_edit_hash"), + ] + + operations = [ + migrations.RenameField( + model_name="signature", + old_name="edit_hash", + new_name="edit_Hash", + ), + ] diff --git a/main/migrations/0089_auto_20210713_1541.py b/main/migrations/0089_auto_20210713_1541.py new file mode 100644 index 00000000..99bdb78d --- /dev/null +++ b/main/migrations/0089_auto_20210713_1541.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2021-07-13 15:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0088_auto_20210713_1540"), + ] + + operations = [ + migrations.RenameField( + model_name="signature", + old_name="edit_Hash", + new_name="edit_hash", + ), + ] diff --git a/main/migrations/0090_communityentry_is_final.py b/main/migrations/0090_communityentry_is_final.py new file mode 100644 index 00000000..79e97fdd --- /dev/null +++ b/main/migrations/0090_communityentry_is_final.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-07-27 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0089_auto_20210713_1541'), + ] + + operations = [ + migrations.AddField( + model_name='communityentry', + name='is_final', + field=models.BooleanField(default=False), + ), + ] diff --git a/main/migrations/0091_remove_communityentry_is_final.py b/main/migrations/0091_remove_communityentry_is_final.py new file mode 100644 index 00000000..cc3054f8 --- /dev/null +++ b/main/migrations/0091_remove_communityentry_is_final.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.24 on 2021-07-27 14:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0090_communityentry_is_final'), + ] + + operations = [ + migrations.RemoveField( + model_name='communityentry', + name='is_final', + ), + ] diff --git a/main/migrations/0092_communityentry_is_final.py b/main/migrations/0092_communityentry_is_final.py new file mode 100644 index 00000000..6e743ebf --- /dev/null +++ b/main/migrations/0092_communityentry_is_final.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-07-27 14:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0091_remove_communityentry_is_final'), + ] + + operations = [ + migrations.AddField( + model_name='communityentry', + name='is_final', + field=models.BooleanField(default=True), + ), + ] diff --git a/main/migrations/0093_auto_20210727_1455.py b/main/migrations/0093_auto_20210727_1455.py new file mode 100644 index 00000000..e02c255b --- /dev/null +++ b/main/migrations/0093_auto_20210727_1455.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-07-27 14:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0092_communityentry_is_final'), + ] + + operations = [ + migrations.AlterField( + model_name='communityentry', + name='is_final', + field=models.BooleanField(default=False), + ), + ] diff --git a/main/migrations/0094_auto_20210727_1455.py b/main/migrations/0094_auto_20210727_1455.py new file mode 100644 index 00000000..98daae70 --- /dev/null +++ b/main/migrations/0094_auto_20210727_1455.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-07-27 14:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0093_auto_20210727_1455'), + ] + + operations = [ + migrations.AlterField( + model_name='communityentry', + name='is_final', + field=models.BooleanField(default=True), + ), + ] diff --git a/main/models.py b/main/models.py index 3e100023..bdbbd597 100644 --- a/main/models.py +++ b/main/models.py @@ -393,12 +393,13 @@ class CommunityEntry(models.Model): ) # signature = models.CharField(max_length=64, blank=True) created_at = models.DateTimeField(auto_now_add=True) + is_final = models.BooleanField(default=True) admin_approved = models.BooleanField(default=True) private = models.BooleanField(default=False, null=True) population = models.IntegerField(blank=True, null=True, default=0) def human_readable_name(self): - return self.entry_name.replace(' ', '_') + return self.entry_name.replace(" ", "_") def __str__(self): return str(self.entry_ID) @@ -439,6 +440,7 @@ class Signature(models.Model): CommunityEntry, on_delete=models.CASCADE, default="" ) hash = models.CharField(max_length=64, blank=True) + edit_hash = models.CharField(max_length=64, blank=True) # ******************************************************************************# @@ -495,4 +497,4 @@ def unapprove(self): self.community.save() -# ******************************************************************************# +# ******************************************************************************# \ No newline at end of file diff --git a/main/static/img/collaborate-active.svg b/main/static/img/collaborate-active.svg new file mode 100644 index 00000000..1f5894e7 --- /dev/null +++ b/main/static/img/collaborate-active.svg @@ -0,0 +1,4 @@ + diff --git a/main/static/img/collaborate-icon.svg b/main/static/img/collaborate-icon.svg new file mode 100644 index 00000000..e4386b91 --- /dev/null +++ b/main/static/img/collaborate-icon.svg @@ -0,0 +1,3 @@ + diff --git a/main/static/img/collaborate-inactive.svg b/main/static/img/collaborate-inactive.svg new file mode 100644 index 00000000..e217748f --- /dev/null +++ b/main/static/img/collaborate-inactive.svg @@ -0,0 +1,4 @@ + diff --git a/main/static/img/collaborate-link.svg b/main/static/img/collaborate-link.svg new file mode 100644 index 00000000..1c7daec3 --- /dev/null +++ b/main/static/img/collaborate-link.svg @@ -0,0 +1,5 @@ + diff --git a/main/static/img/collaborate-plus.svg b/main/static/img/collaborate-plus.svg new file mode 100644 index 00000000..2f2aec71 --- /dev/null +++ b/main/static/img/collaborate-plus.svg @@ -0,0 +1,5 @@ + diff --git a/main/static/main/css/style.css b/main/static/main/css/style.css index 914862e0..30b5164c 100644 --- a/main/static/main/css/style.css +++ b/main/static/main/css/style.css @@ -2,8 +2,8 @@ Name: style.css Project: Representable Version: 1.0 -Modified: 5/30/2019 -Authors: S. Arora, K. Barnes, P.Iyer, L. Johnston, T. Marcu +Modified: 7/30/2021 +Authors: S. Arora, K. Barnes, P.Iyer, L. Johnston, T. Marcu, N. Shapiro ----------------------------------------------------------------------*/ /* IMPORTANT - READ BELOW BEFORE ADDING - IMPORTANT*/ @@ -33,6 +33,21 @@ Bootstrap is better in that case. https://getbootstrap.com/docs/4.5/getting-star justify-content: center; } +.collaboration-notification { + position: fixed; + bottom: 0; + left: auto; + right: 0; + z-index: 100; + width: 33%; + background-color: #DAEFFF; +} + +.collaboration-notification p.small { + font-size: 75%; + line-height: 95%; +} + /* Entry Form CSS */ form:invalid { border-color: red; @@ -343,6 +358,13 @@ Entry Preview Page margin-top: 20px; margin-left: 0px !important; } + #collaboration-notification { + width: 60%; + } + #collab-switch { + top: 25%; + left: 25%; + } } @media only screen and (max-width: 540px) { @@ -378,6 +400,13 @@ Entry Preview Page #entry_preview_button { padding-top: 20px; } + + #collaborationButton { + width: 100%; + } + #collaboration-notification { + width: 100%; + } } input[type="range"]::-webkit-slider-thumb { @@ -770,6 +799,10 @@ color scheme. See readme.md*/ color: #fff !important; } +.bg-primary { + background-color: #2a94f4 !important; +} + .btn-primary:active { background-color: #2176c2; border-color: #2176c2; @@ -1286,6 +1319,10 @@ Entry Mapping Page #mapping-image-div { width: 30% !important; } + #collab-switch { + left: 0% !important; + top: 25% !important; + } } .loader { @@ -1742,6 +1779,18 @@ input[type="checkbox"].switch_1:checked { background: #2a94f4; } +input[type="checkbox"]#collab-switch { + background: #fff; +} + +input[type="checkbox"]#collab-switch:checked { + background: #fff; +} + +input[type="checkbox"]#collab-switch:after { + background: #2a94f4; +} + input[type="checkbox"].switch_1:after { position: absolute; content: ""; @@ -2210,6 +2259,10 @@ height:25vh!important; .vh-lg-25 { height:25vh!important; } + #collab-switch { + left: 15%; + top: 20%; + } } /* Extra large devices (large desktops, 1200px and up)*/ @@ -2263,6 +2316,9 @@ height:25vh!important; .vh-xl-25 { height:25vh!important; } + #collab-switch { + left: 0% !important; + } } /* Fallback for Edge diff --git a/main/static/main/js/geo.js b/main/static/main/js/geo.js index c228d710..bc1b796e 100644 --- a/main/static/main/js/geo.js +++ b/main/static/main/js/geo.js @@ -21,6 +21,7 @@ // GEO Js file for handling map drawing. /* https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-draw/ */ + var bg_id = "GEOID"; // census FIPS code for block group var block_id = "GEOID20"; // census FIPS code for block var unit_id = bg_id; // abstracted - current unit @@ -375,6 +376,7 @@ function surveyP2ToMap() { animateStepForward(2, 3, 4); $("#2to3").removeClass("h-75"); automaticScrollToTop(); + document.getElementById('collaborationBar').style = "display: none;"; } function mapToSurveyP2() { @@ -386,6 +388,7 @@ function mapToSurveyP2() { $("#2to3").addClass("h-75"); }, 600); automaticScrollToTop(); + document.getElementById('collaborationBar').style = "visibility: visible"; } function mapToPrivacy() { @@ -395,6 +398,7 @@ function mapToPrivacy() { $("#entry_survey").addClass("d-none"); animateStepForward(3, 4, 5); automaticScrollToTop(); + document.getElementById('collaborationBar').style = "visibility: visible"; } function privacyToMap() { @@ -692,7 +696,12 @@ function backupFormValidation() { function createCommPolygon() { // start by checking size -- 800 is an arbitrary number // it means a community with a population between 480,000 & 2,400,000 - var polyFilter = JSON.parse(sessionStorage.getItem("bgFilter")); + if(JSON.parse(sessionStorage.getItem("bgFilter")) === null && !isEmptyFilter(map.getFilter(state + "-highlighted-" + layer_suffix))){ + var polyFilter = map.getFilter(state + "-highlighted-" + layer_suffix); + } + else if (JSON.parse(sessionStorage.getItem("bgFilter")) !== null){ + var polyFilter = JSON.parse(sessionStorage.getItem("bgFilter")); + } if (polyFilter === null) { triggerMissingPolygonError(); @@ -787,6 +796,34 @@ document.addEventListener( $("#entrySubmissionButton").on("click", function (e) { e.preventDefault(); + document.getElementById('id_is_final').value = "True" + var form = $("#entryForm"); + zoomToCommunity(); + // delay so that zoom can occur + var polySuccess = true, + formSuccess = true; + // loading icon + // $("#loading-entry").css("display", "block"); + // $("#loading-entry").delay(1000).fadeOut(1000); + //todo: switch this to a promise ? + setTimeout(function () { + backupSuccess = backupFormValidation(); + privacySuccess = privacyCheckValidation(); + animateStepForward(4, 5, null); + }, 500); + setTimeout(function () { + if (backupSuccess && privacySuccess) { + form.submit(); + } else { + animateStepBackward(5, 4, null); + } + }, 850); + return false; + }); + + $("#entrySaveButton").on("click", function (e) { + e.preventDefault(); + document.getElementById('id_is_final').value = "False" var form = $("#entryForm"); zoomToCommunity(); // delay so that zoom can occur @@ -1341,6 +1378,63 @@ map.on("style.load", function () { } sessionStorage.setItem("prev_state", state); + if (census_blocks.includes("'") || block_groups.includes("'")){ + census_blocks = census_blocks.split("'"); + block_groups = block_groups.split("'"); + } else if (census_blocks.includes("x27") || block_groups.includes("x27")){ + census_blocks = census_blocks.split("'"); + block_groups = block_groups.split("'"); + } + toDisplay = ["in", "GEOID"]; + if(typeof block_groups !== "string" && (census_blocks.length > 1 || block_groups.length > 1)){ + if (census_blocks.length > 1){ // if there are census blocks + let i = 1; + while (i < census_blocks.length){ + toDisplay[(i+3)/2] = parseInt(census_blocks[i]); + i+=2; + } + } + else { // (block_groups.length > 1) if there are block groups + let i = 1; + while (i < block_groups.length){ + toDisplay[(i+3)/2] = block_groups[i]; + i+=2; + } + } + + map.setFilter(state + "-highlighted-" + layer_suffix, toDisplay); + + polygon = polygon.slice(20).slice(0,-2).split(", "); + for(let i = 0; i < polygon.length; i++){ + polygon[i] = polygon[i].split(" "); + } + + var north = polygon[0][1]; + var south = polygon[0][1]; + var east = polygon[0][0]; + var west = polygon[0][0]; + + for(let i = 0; i < polygon.length; i++){ + if (north < parseFloat(polygon[i][1])){ + north = parseFloat(polygon[i][1]); + } + if (south > parseFloat(polygon[i][1])){ + south = parseFloat(polygon[i][1]); + } + if (east < parseFloat(polygon[i][0])){ + east = parseFloat(polygon[i][0]); + } + if (west > parseFloat(polygon[i][0])){ + west = parseFloat(polygon[i][0]); + } + } + // console.log("("+east+", "+north+"), ("+west+", "+south+")"); + map.fitBounds([ + [west, south], // southwestern corner of the bounds + [east, north] // northeastern corner of the bounds + ]); + } + // When the user moves their mouse over the census shading layer, we'll update the // feature state for the feature under the mouse. var bgID = null; diff --git a/main/static/main/js/submission.js b/main/static/main/js/submission.js index 75b93b9f..36f277e3 100644 --- a/main/static/main/js/submission.js +++ b/main/static/main/js/submission.js @@ -17,6 +17,34 @@ function toggleAngle(e) { } } +function toggleGrayScale(){ + if (document.getElementById("collaborationButton").style.filter == "grayscale(100%)"){ + document.getElementById("collaborationButton").style.filter = "grayscale(0%)"; + } + else{ + document.getElementById("collaborationButton").style.filter = "grayscale(100%)"; + } +} + +function copyEditLink() { + var message; + var link = document.getElementById("edit-link").innerText; + console.log(link); + navigator.clipboard.writeText(link).then(function() { + message = "Copied to clipboard!"; + copyText.innerHTML = message; + document.getElementById('copy-link-text').style = "cursor: text"; + }, function(err) { + message = "There was an error, please try again later"; + copyText.innerHTML = message; + }); + + // set text to say "copied!" for feedback mechanism that the copying worked + var copyText = document.getElementById("copy-link-text"); + + setTimeout(function () { copyText.innerHTML = '🔗 Copy Link'; document.getElementById('copy-link-text').style = "cursor: pointer"; }, 2000); +} + /*------------------------------------------------------------------------*/ /* JS file from mapbox site -- display a polygon */ /* https://docs.mapbox.com/mapbox-gl-js/example/geojson-polygon/ */ @@ -237,7 +265,7 @@ map.on("load", function () { for (var key in BOUNDARIES_LAYERS) { newBoundariesLayer(key); } - + var outputstr = a.replace(/'/g, '"'); a = JSON.parse(outputstr); var dest = []; @@ -542,7 +570,7 @@ for (var id in toggleableLayerIds) { function updateFeatureState(source, sourceLayer, hoveredStateId, hover) { map.setFeatureState( { source: source, - sourceLayer: + sourceLayer: sourceLayer, id: hoveredStateId }, { hover: hover } @@ -591,7 +619,7 @@ function addToggleableLayer(id, appendElement) { map.setLayoutProperty(txt + "-fills", "visibility", "visible"); visible = txt; } - + } if (visible != null && visible != "sta5") { @@ -609,7 +637,7 @@ function addToggleableLayer(id, appendElement) { } } }); - + map.on('mouseleave', visible + '-fills', function(e) { popup.remove(); if (hoveredStateId !== null) { diff --git a/main/static/main/php/send-email.php b/main/static/main/php/send-email.php new file mode 100644 index 00000000..a712e344 --- /dev/null +++ b/main/static/main/php/send-email.php @@ -0,0 +1,37 @@ +special = $special; + $this->message = $message; + $this->response = $response; + $this->data = $data; + } + + function spit() { + echo(json_encode($this)); + } +} + +$op = ''; +if (isset($_GET['op'])) { + $op = $_GET['op']; +} +else if (isset($_POST['op'])) { + $op = $_POST['op']; +} + + +$headers = 'From:'. $from . "\r\n"; // Set from headers + + +mail($op, "Make a map on Reprsentable!", "test", $headers); + +$rval = new AjaxResponse(false, $op, 'email sent!', array()); +$rval->spit(); + +?> \ No newline at end of file diff --git a/main/templates/main/draft.html b/main/templates/main/draft.html new file mode 100644 index 00000000..66b17ee8 --- /dev/null +++ b/main/templates/main/draft.html @@ -0,0 +1,605 @@ +{% extends 'main/base.html' %} +{% load leaflet_tags %} +{% load static %} +{% load representable_extras %} +{% load i18n %} + + +{% block head %} +{% leaflet_js %} +{% leaflet_css %} + + + +
Anyone with the link can edit this map, however only the creator may officially submit the map. Changes to the map will be reflected here when you open your map on Representable.
++ {{c.comm_activities}} +
++ {{c.cultural_interests}} +
++ {{c.economic_interests}} +
++ {{c.other_considerations}} +
++ {{c.custom_response}} +
++ {% blocktrans trimmed %} + Map downloads include the community information above. + {% endblocktrans %} +
++ {% blocktrans trimmed %} + You will be able to export your map in more formats after submitting. + {% endblocktrans %} +
+