Handling multiple enterprises at the same location

Changes:
* Introduced a cluster marker to denote multiple points of interest at
the same location
* Seperated out a plain enterprise modal into 2 parts
  * A modal called EnterpriseModal for showing a list of enterprises at
  the same location
  * A box called EnterpriseBox(which by the way is also a technically a
  modal) that shows the details of that particular enterprise selected
* If at a location there exists only a single enterprise then only the
box is shown
This commit is contained in:
Manvil George
2020-05-06 12:33:49 +10:00
parent 8a107bee98
commit 19b5f6a562
11 changed files with 240 additions and 25 deletions

View File

@@ -39,6 +39,7 @@ Setup the database and seed it with sample data:
```sh
$ docker-compose run web bundle exec rake db:reset
$ docker-compose run web bundle exec rake db:test:prepare
$ docker-compose run web bundle exec rake db:seed
$ docker-compose run web bundle exec rake ofn:sample_data
```

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:ns="&amp;#38;ns_sfw;"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4f, 2020-05-01)"
sodipodi:docname="map_009-cluster.svg"
xml:space="preserve"
enable-background="new 0 0 28 33"
viewBox="0 0 28 33"
height="33px"
width="28px"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><defs
id="defs23" /><sodipodi:namedview
inkscape:document-rotation="0"
inkscape:current-layer="g14"
inkscape:window-maximized="0"
inkscape:window-y="23"
inkscape:window-x="0"
inkscape:cy="7.8178019"
inkscape:cx="14"
inkscape:zoom="10.11377"
showgrid="false"
id="namedview21"
inkscape:window-height="794"
inkscape:window-width="1440"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<metadata
id="metadata2">
<ns:sfw>
<ns:slices />
<ns:sliceSourceBounds
bottomLeftOrigin="true"
x="-8112"
y="-85.5"
width="16383"
height="96" />
</ns:sfw>
<rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata>
<g
id="g18">
<path
id="path4"
d="M14,25c-6.059,0-10.988,1.679-10.988,3.333c0,2.485,10.307,4.542,10.746,4.643 C13.828,32.992,13.914,33,14,33c0.084,0,0.17-0.008,0.239-0.023c0.439-0.099,10.749-2.114,10.749-4.643 C24.988,26.679,20.059,25,14,25z"
fill="#282828"
opacity="0.25" />
<g
id="g16">
<path
id="path6"
d="M14,0C6.28,0,0,6.717,0,13.332c0,9.941,13.132,18.169,13.691,18.572C13.78,31.968,13.891,32,14,32 c0.107,0,0.217-0.031,0.305-0.094C14.864,31.511,28,23.45,28,13.332C28,6.717,21.72,0,14,0z"
fill="#FFFFFF" />
<g
id="g14">
<path
style="fill:#0b8c61;fill-opacity:1"
id="path12"
d="M14,0C6.28,0,0,6.717,0,13.333c0,9.941,13.132,18.169,13.691,18.571C13.78,31.968,13.891,32,14,32 c0.107,0,0.217-0.031,0.305-0.094C14.864,31.511,28,23.45,28,13.333C28,6.717,21.72,0,14,0z M23.5,12.057 c0,0.863-0.567,1.661-1.325,1.942l-1.025,5.889c-0.015,0.976-0.889,1.831-1.94,1.831H8.744c-1.052,0-1.925-0.855-1.947-1.906 l-1.024-5.828C5.036,13.691,4.5,12.91,4.5,12.057V11.23c0-1.074,0.874-1.948,1.948-1.948H8.75l1.396-2.247 c0.4-0.698,1.417-0.978,2.145-0.555c0.755,0.435,1.015,1.403,0.58,2.159l-0.41,0.642h2.662l-0.39-0.61 c-0.227-0.391-0.284-0.823-0.174-1.235c0.109-0.406,0.37-0.745,0.734-0.955c0.716-0.417,1.739-0.148,2.159,0.579l1.381,2.223 h2.718c1.074,0,1.948,0.874,1.948,1.948V12.057z"
fill="#C1122B" />
<rect
y="6.9915252"
x="3.6006355"
height="15.521187"
width="20.415255"
id="rect146"
style="fill:#0b8c61;fill-opacity:1" /><rect
y="5.6631355"
x="8.9841099"
height="3.6355932"
width="10.487288"
id="rect148"
style="fill:#0b8c61;fill-opacity:1" /><text
id="text152"
y="20.243376"
x="5.0698848"
style="font-style:normal;font-weight:normal;font-size:16px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16px;font-family:Arial;-inkscape-font-specification:'Arial Bold';fill:#ffffff"
y="20.243376"
x="5.0698848"
id="tspan150"
sodipodi:role="line">1+</tspan></text></g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,12 @@
Darkswarm.factory "EnterpriseBox", ($modal, $rootScope, $http)->
# Build a modal popup for an enterprise.
new class EnterpriseBox
open: (enterprise)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
$http.get("/api/shops/" + enterprise.id).success (data) ->
scope.enterprise = data
$modal.open(templateUrl: "enterprise_box.html", scope: scope)
.error (data) ->
console.error(data)

View File

@@ -1,12 +1,13 @@
Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http)->
Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http, EnterpriseBox)->
# Build a modal popup for an enterprise.
new class EnterpriseModal
open: (enterprise)->
open: (enterprises)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
$http.get("/api/shops/" + enterprise.id).success (data) ->
scope.enterprise = data
scope.enterprises = enterprises
scope.EnterpriseBox = EnterpriseBox
len = Object.keys(enterprises).length
if len > 1
$modal.open(templateUrl: "enterprise_modal.html", scope: scope)
.error (data) ->
console.error(data)
else
EnterpriseBox.open enterprises[Object.keys(enterprises)[0]]

View File

@@ -1,21 +1,42 @@
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal) ->
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, MapConfiguration) ->
new class OfnMap
constructor: ->
@enterprises = @enterprise_markers(Enterprises.enterprises)
@coordinates = {}
@enterprises = Enterprises.enterprises.filter (enterprise) ->
# Remove enterprises w/o lat or long
enterprise.latitude != null || enterprise.longitude != null
@enterprises = @enterprise_markers(@enterprises)
self = this
@enterprises = @enterprises.filter (enterprise) ->
enterprise.latitude != null || enterprise.longitude != null # Remove enterprises w/o lat or long
# Remove enterprises w/o lat or long
enterprise.latitude != null || enterprise.longitude != null
enterprise_markers: (enterprises) ->
@extend(enterprise) for enterprise in enterprises
enterprise_hash: (hash, enterprise) ->
hash[enterprise.id] = { id: enterprise.id, name: enterprise.name, icon: enterprise.icon_font }
hash
# Adding methods to each enterprise
extend: (enterprise) ->
new class MapMarker
# We cherry-pick attributes because GMaps tries to crawl
# our data, and our data is cyclic, so it breaks
latitude: enterprise.latitude
longitude: enterprise.longitude
icon: enterprise.icon
id: enterprise.id
reveal: =>
EnterpriseModal.open enterprise
marker = @coordinates[[enterprise.latitude, enterprise.longitude]]
self = this
if !marker
marker = new class MapMarker
# We cherry-pick attributes because GMaps tries to crawl
# our data, and our data is cyclic, so it breaks
latitude: enterprise.latitude
longitude: enterprise.longitude
icon: enterprise.icon
id: [enterprise.id]
enterprises: self.enterprise_hash({}, enterprise)
reveal: =>
EnterpriseModal.open this.enterprises
@coordinates[[enterprise.latitude, enterprise.longitude]] = marker
else
marker.icon = MapConfiguration.options.cluster_icon
self.enterprise_hash(marker.enterprises, enterprise)
marker.id.push(enterprise.id)
marker

View File

@@ -4,6 +4,7 @@ Darkswarm.factory "MapConfiguration", ->
center:
latitude: -37.4713077
longitude: 144.7851531
cluster_icon: 'assets/map_009-cluster.svg'
zoom: 12
additional_options:
# mapTypeId: 'satellite'

View File

@@ -0,0 +1,5 @@
%ng-include{src: "'partials/enterprise_header.html'"}
%ng-include{src: "'partials/enterprise_details.html'"}
%ng-include{src: "'partials/hub_details.html'"}
%ng-include{src: "'partials/producer_details.html'"}
%ng-include{src: "'partials/close.html'"}

View File

@@ -1,5 +1,2 @@
%ng-include{src: "'partials/enterprise_header.html'"}
%ng-include{src: "'partials/enterprise_details.html'"}
%ng-include{src: "'partials/hub_details.html'"}
%ng-include{src: "'partials/producer_details.html'"}
%ng-include{src: "'partials/enterprise_listing.html'"}
%ng-include{src: "'partials/close.html'"}

View File

@@ -0,0 +1,10 @@
.modal-list
.row{"ng-repeat" => "(id, enterprise) in ::enterprises"}
.highlight
.highlight-top.row.enterprise
.small-12.medium-12.large-12.columns
%h4
%a.heading{"ng-click" => "::EnterpriseBox.open(enterprise)"}
%i{"ng-class" => "enterprise.icon"}
%span{"ng-bind" => "enterprise.name"}
%img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"}

View File

@@ -1,5 +1,6 @@
@import "branding";
@import "mixins";
@import "admin/globals/variables";
// Generic styles for use
@@ -22,6 +23,24 @@
margin-bottom: 0.5rem;
}
.modal-list {
text-align: center;
font-size: 1rem;
font-weight: 400;
border-bottom: 1px solid $light-grey;
margin-top: 0.75rem;
margin-bottom: 0.5rem;
a.heading {
color: $color-link;
&:hover {
color: $color-link-hover;
text-decoration: underline;
}
}
}
// Enterprise promo image and text
.highlight {
@@ -54,6 +73,12 @@
color: $clr-brick;
}
&.enterprise {
margin: auto;
text-align: center;
width: 100%;
}
p {
line-height: 2.4;

View File

@@ -6,6 +6,8 @@ describe "Hubs service", ->
{
id: 2
active: false
icon_font: 'abc'
name: 'BugSpray'
orders_close_at: new Date()
type: "hub"
visible: true
@@ -15,12 +17,36 @@ describe "Hubs service", ->
{
id: 3
active: false
icon_font: 'def'
name: 'Toothbrush'
orders_close_at: new Date()
type: "hub"
visible: true
latitude: 100
longitude: 200
}
{
id: 4
active: false
icon_font: 'ghi'
name: 'Covidness'
orders_close_at: new Date()
type: "hub"
visible: true
latitude: null
longitude: null
}
{
id: 5
active: false
icon_font: 'jkl'
name: 'Toothbrush for kids'
orders_close_at: new Date()
type: "hub"
visible: true
latitude: 100
longitude: 200
}
]
beforeEach ->
@@ -34,7 +60,19 @@ describe "Hubs service", ->
OfnMap = $injector.get("OfnMap")
it "builds MapMarkers from enterprises", ->
expect(OfnMap.enterprises[0].id).toBe enterprises[0].id
expect(OfnMap.enterprises[0].id[0]).toBe enterprises[0].id
it "excludes enterprises without latitude or longitude", ->
expect(OfnMap.enterprises.map (e) -> e.id).not.toContain enterprises[1].id
expect(OfnMap.enterprises.map (e) -> e.id).not.toContain [enterprises[2].id]
it "the MapMarkers will a field for enterprises", ->
enterprise = enterprises[0]
expect(OfnMap.enterprises[0].enterprises[enterprise.id]).toEqual { id: enterprise.id, name: enterprise.name, icon: enterprise.icon_font }
it "the MapMarkers will bunch up enterprises with the same coordinates", ->
enterprise1 = enterprises[1]
enterprise2 = enterprises[3]
hash = {}
hash[enterprise1.id] = { id: enterprise1.id, name: enterprise1.name, icon: enterprise1.icon_font }
hash[enterprise2.id] = { id: enterprise2.id, name: enterprise2.name, icon: enterprise2.icon_font }
expect(OfnMap.enterprises[2].enterprises).toEqual hash