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 %} + + + +View Map + + + + + + + + + + + + + + + + + + +{% endblock %} +{% block content %} +
+
+
+ +
+ {% if entries is None %} + + {% endif %} +
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+

Copy Link

+
+
+ + +
+ +
+
+

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.entry_name}}

+
{{c.drive.name}}
+

{% trans "Community Information" %}

+ {% if c.comm_activities %} +
+ +
+

+ {{c.comm_activities}} +

+
+
+ {% endif %} + {% if c.cultural_interests %} +
+ +
+

+ {{c.cultural_interests}} +

+
+
+ {% endif %} + {% if c.economic_interests %} +
+ +
+

+ {{c.economic_interests}} +

+
+
+ {% endif %} + {% if c.other_considerations %} +
+ +
+

+ {{c.other_considerations}} +

+
+
+ {% endif %} + {% if c.drive and c.drive.custom_question %} +
+ +
+

+ {{c.custom_response}} +

+
+
+ {% endif %} +
+
+
+
+
{% trans "Download this draft" %}
+

+ {% blocktrans trimmed %} + Map downloads include the community information above. + {% endblocktrans %} +

+
+
+ + + + + + +
+ {% if has_state %} + +
+ +
+ + {% else %} + +
+ +
+ {% endif %} +
+ +
+
+ +
+
+
+
+ + +
+
+
+
+
+
+

{{c.entry_name}}

+
{{c.drive.name}}
+
+
+
+
+
+
+
{% trans "Data Layers" %}
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
{% trans "Election Data" %}
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
{% trans "Demographics" %}
+
+ {% if c.population > 0 %} + Population: {{ c.population }} +
+ {% else %} + {% trans "Demographic information will become available with the release of 2020 Census data by the US Census Bureau." %} + {% endif %} +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+ + {% if is_owner %} + + {% else %} + + {% endif%} +
+
+
+
+
+
+
+ + +
+ {% blocktrans trimmed %} This map was created at Representable.org {% endblocktrans %} +
+
+ {% if c.economic_interests %} +

+
+ {% trans "Economic or Environmental Interests" %} +

+ + {{c.economic_interests}} + + {% endif %} + {% if c.comm_activities %} +

+
+ {% trans "Community Activities and Services" %} +

+ + {{c.comm_activities}} + + {% endif %} + {% if c.cultural_interests %} +

+
+ {% trans "Cultural or Historical Interests" %} +

+ + {{c.cultural_interests}} + + {% endif %} + {% if c.other_considerations %} +

+
+ {% trans "Community Needs and Concerns" %} +

+ + {{c.other_considerations}} + + {% endif %} + {% if c.custom_response %} +
+
+ + {{ c.drive.custom_question }} +
+ + {{c.custom_response}} + + {% endif %} +
+ {% if c.drive %} +
{{c.organization}}
+
{{c.drive}}
+ {% endif %} +{% endblock %} + + {% block script %} + + + + + + + + {% endblock %} diff --git a/main/templates/main/entry.html b/main/templates/main/entry.html index 44ca056e..64127d50 100644 --- a/main/templates/main/entry.html +++ b/main/templates/main/entry.html @@ -41,6 +41,9 @@ var address_required = "{{address_required}}"; var census_key = "{{ census_key }}"; var language = "{{LANGUAGE_CODE}}"; + var census_blocks = "{{ census_blocks }}"; + var block_groups = "{{ block_groups }}"; + var polygon = "{{ polygon }}"; mixpanel.track("Entry Page Loaded", { "drive_id": drive_id, @@ -49,6 +52,7 @@ "organization_name": organization_name, } ); + @@ -56,6 +60,47 @@ {% block content %} +
+ +
+{% if edit_hash == None %} + +{% endif %} + +
+ {% if edit_hash == None %} +
+
+
+
+ +
+
+ Collaborate +
+
+ +
+
+
+
+ {% endif %} +
+ +
{% csrf_token %} @@ -207,4 +252,18 @@
+ {% endblock %} diff --git a/main/templates/main/entry_address.html b/main/templates/main/entry_address.html index 19171cba..ee5f9a81 100644 --- a/main/templates/main/entry_address.html +++ b/main/templates/main/entry_address.html @@ -175,7 +175,7 @@ {{ addr_form.zipcode|append_attr:"class:form-control addr-field" }} -
+
diff --git a/main/templates/main/entry_privacy.html b/main/templates/main/entry_privacy.html index d31b0455..f15af4c4 100644 --- a/main/templates/main/entry_privacy.html +++ b/main/templates/main/entry_privacy.html @@ -6,8 +6,8 @@ Step 4
-
-
+
+