Merge branch 'master' into checkout

This commit is contained in:
Rafael Schouten
2014-10-25 15:12:24 +11:00
165 changed files with 1441 additions and 3051 deletions

View File

@@ -22,6 +22,7 @@
//= require ./payment_methods/payment_methods
//= require ./products/products
//= require ./shipping_methods/shipping_methods
//= require ./utils/utils
//= require ./users/users
//= require textAngular.min.js
//= require textAngular-sanitize.min.js

View File

@@ -1,10 +1,18 @@
angular.module("admin.enterprises")
.controller "enterpriseCtrl", ($scope, Enterprise, longDescription, PaymentMethods, ShippingMethods) ->
.controller "enterpriseCtrl", ($scope, longDescription, NavigationCheck, Enterprise, PaymentMethods, ShippingMethods) ->
$scope.Enterprise = Enterprise.enterprise
$scope.PaymentMethods = PaymentMethods.paymentMethods
$scope.ShippingMethods = ShippingMethods.shippingMethods
$scope.navClear = NavigationCheck.clear
# htmlVariable is used by textAngular wysiwyg for the long descrtiption.
$scope.htmlVariable = longDescription
# Provide a callback for generating warning messages displayed before leaving the page. This is passed in
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
# and on all new uses of this contoller, and we might not want that .
$scope.enterpriseNavCallback = ->
"You are editing an enterprise!"
for payment_method in $scope.PaymentMethods
payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids

View File

@@ -1 +1 @@
angular.module("admin.enterprises", ["admin.payment_methods", "admin.shipping_methods", "admin.users", "textAngular"])
angular.module("admin.enterprises", ["admin.payment_methods", "admin.utils", "admin.shipping_methods", "admin.users", "textAngular"])

View File

@@ -1,4 +1,5 @@
angular.module("admin.enterprises")
# Populate Enterprise.enterprise with enterprise json array from the page.
.factory 'Enterprise', (enterprise) ->
new class Enterprise
enterprise: enterprise
enterprise: enterprise

View File

@@ -0,0 +1,9 @@
angular.module("admin.utils").directive "navCheck", (NavigationCheck)->
restrict: 'A'
scope:
navCallback: '&'
link: (scope,element,attributes) ->
# Define navigationCallback on a controller in scope, otherwise this default will be used:
scope.navCallback ||= ->
"You will lose any unsaved work!"
NavigationCheck.register(scope.navCallback)

View File

@@ -0,0 +1,46 @@
angular.module("admin.utils")
.factory "NavigationCheck", ($window, $rootScope) ->
new class NavigationCheck
callbacks = []
constructor: ->
if $window.addEventListener
$window.addEventListener "beforeunload", @onBeforeUnloadHandler
else
$window.onbeforeunload = @onBeforeUnloadHandler
$rootScope.$on "$locationChangeStart", @locationChangeStartHandler
# Action for regular browser navigation.
onBeforeUnloadHandler: ($event) =>
message = @getMessage()
if message
($event or $window.event).preventDefault()
message
# Action for angular navigation.
locationChangeStartHandler: ($event) =>
message = @getMessage()
if message and not $window.confirm(message)
$event.stopPropagation() if $event.stopPropagation
$event.preventDefault() if $event.preventDefault
$event.cancelBubble = true
$event.returnValue = false
# Runs callback functions to retreive most recently added non-empty message.
getMessage: ->
message = null
message = callback() ? message for callback in callbacks
message
register: (callback) =>
callbacks.push callback
clear: =>
if $window.addEventListener
$window.removeEventListener "beforeunload", @onBeforeUnloadHandler
else
$window.onbeforeunload = null
$rootScope.$on "$locationChangeStart", null

View File

@@ -0,0 +1 @@
angular.module("admin.utils", [])

View File

@@ -1,13 +1,14 @@
Darkswarm.controller "HubsCtrl", ($scope, Hubs, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, MapModal) ->
$scope.Hubs = Hubs
$scope.hubs = Hubs.visible
Darkswarm.controller "EnterprisesCtrl", ($scope, Enterprises, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, EnterpriseModal) ->
$scope.Enterprises = Enterprises
$scope.totalActive = FilterSelectorsService.totalActive
$scope.clearAll = FilterSelectorsService.clearAll
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.query = Search.search()
$scope.openModal = EnterpriseModal.open
$scope.activeTaxons = []
$scope.show_profiles = false
$scope.openModal = MapModal.open
$scope.filtersActive = false
$scope.$watch "query", (query)->
Search.search query

View File

@@ -1,14 +0,0 @@
Darkswarm.controller "ProducersCtrl", ($scope, Producers, $filter, FilterSelectorsService, Search, MapModal) ->
$scope.Producers = Producers
$scope.totalActive = FilterSelectorsService.totalActive
$scope.clearAll = FilterSelectorsService.clearAll
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.filtersActive = false
$scope.activeTaxons = []
$scope.query = Search.search()
$scope.show_profiles = false
$scope.openModal = MapModal.open
$scope.$watch "query", (query)->
Search.search query

View File

@@ -3,7 +3,7 @@ Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, Enterpris
$scope.enterprise = EnterpriseRegistrationService.enterprise
$scope.select = RegistrationService.select
$scope.steps = ['details','address','contact','about','images','social']
$scope.steps = ['details','contact','type','about','images','social']
$scope.countries = availableCountries

View File

@@ -1,3 +1,4 @@
Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises) ->
# Injecting Enterprises so CurrentHub.producers is dereferenced
Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises, EnterpriseModal) ->
# Injecting Enterprises so CurrentHub.producers is dereferenced.
# We should probably dereference here instead and separate out CurrentHub dereferencing from the Enterprise factory.
$scope.CurrentHub = CurrentHub

View File

@@ -1,18 +1,11 @@
Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location, OrderCycle) ->
# Return active if supplied path matches url hash path.
$scope.active = (path)->
if !OrderCycle.selected() and $location.hash() == "" and path == "about"
true
else
$location.hash() == path
$location.hash() == path
$scope.tabs = ["contact", "about", "groups", "producers"]
for tab in $scope.tabs
$scope[tab] =
path: tab
$scope.select = (tab)->
if $scope.active(tab.path)
# Toggle tab selected status by setting the url hash path.
$scope.select = (path)->
if $scope.active(path)
$location.hash ""
else
$location.hash tab.path
$location.hash path

View File

@@ -1,4 +1,4 @@
Darkswarm.directive "producerModal", ($modal)->
Darkswarm.directive "enterpriseModal", ($modal)->
restrict: 'E'
replace: true
template: "<a ng-transclude></a>"
@@ -6,5 +6,4 @@ Darkswarm.directive "producerModal", ($modal)->
link: (scope, elem, attrs, ctrl)->
elem.on "click", (ev)=>
ev.stopPropagation()
scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'producer_modal.html', scope: scope)
scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'enterprise_modal.html', scope: scope)

View File

@@ -1,8 +0,0 @@
Darkswarm.directive "hubModal", ($modal)->
restrict: 'E'
replace: true
template: "<a>{{enterprise.name}}</a>"
link: (scope, elem, attrs, ctrl)->
elem.on "click", (ev)=>
ev.stopPropagation()
scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'hub_modal.html', scope: scope)

View File

@@ -0,0 +1,7 @@
Darkswarm.directive "ofnInlineAlert", ->
restrict: 'A'
scope: true
link: (scope, elem, attrs) ->
scope.visible = true
scope.close = ->
scope.visible = false

View File

@@ -1,6 +0,0 @@
Darkswarm.directive "ofnInlineFlash", ->
restrict: 'E'
controller: ($scope) ->
$scope.visible = true
$scope.closeFlash = ->
$scope.visible = false

View File

@@ -3,7 +3,7 @@ Darkswarm.directive "ofnRegistrationLimitModal", (Navigation, $modal, Loading) -
link: (scope, elem, attr)->
scope.modalInstance = $modal.open
templateUrl: 'registration/limit_reached.html'
windowClass: "login-modal large"
windowClass: "login-modal register-modal xlarge"
backdrop: 'static'
scope.modalInstance.result.then scope.close, scope.close

View File

@@ -1,4 +1,5 @@
Darkswarm.filter "capitalize", ->
# Convert to basic sentence case.
(input, scope) ->
input = input.toLowerCase() if input?
input = input.toLowerCase() if input?
input.substring(0, 1).toUpperCase() + input.substring(1)

View File

@@ -1,9 +0,0 @@
Darkswarm.filter 'hubs', (Matcher)->
(hubs, text) ->
hubs ||= []
text ?= ""
hubs.filter (hub)=>
Matcher.match [
hub.name, hub.address.zipcode, hub.address.city, hub.address.state
], text

View File

@@ -1,6 +0,0 @@
Darkswarm.filter 'filterProducers', (hubsFilter)->
(producers, text) ->
producers ||= []
text ?= ""
hubsFilter(producers, text)

View File

@@ -1,15 +1,14 @@
# Convert number to string currency using injected currency configuration.
#
# @requires currencyConfig json - /app/serializers/api/currency_config_serializer.rb
# @return: string
Darkswarm.filter "localizeCurrency", (currencyConfig)->
# Convert number to string currency using injected currency configuration.
(amount) ->
# Set country code (eg. "US").
currency_code = if currencyConfig.display_currency then " " + currencyConfig.currency else ""
# Set decimal points, 2 or 0 if hide_cents.
decimals = if currencyConfig.hide_cents == "true" then 0 else 2
# We need to use parseFloat before toFixed as the amount should be a passed in as a string.
# We need to use parseFloat before toFixed as the amount should come in as a string.
amount_fixed = parseFloat(amount).toFixed(decimals)
# Build the final price string.
# Build the final price string. TODO use spree decimal point and spacer character settings.
if currencyConfig.symbol_position == 'before'
currencyConfig.symbol + amount_fixed + currency_code
else

View File

@@ -0,0 +1,10 @@
Darkswarm.filter 'searchEnterprises', (Matcher)->
# Search multiple fields of enterprises for matching text fragment.
(enterprises, text) ->
enterprises ||= []
text ?= ""
enterprises.filter (enterprise)=>
Matcher.match [
enterprise.name, enterprise.address.zipcode, enterprise.address.city, enterprise.address.state
], text

View File

@@ -0,0 +1,8 @@
Darkswarm.filter 'showHubProfiles', ()->
# Filter hub_profile enterprises in or out.
(enterprises, show_profiles) ->
enterprises ||= []
show_profiles ?= false
enterprises.filter (enterprise)=>
show_profiles or enterprise.is_distributor

View File

@@ -1,7 +0,0 @@
Darkswarm.filter 'showProfiles', ()->
(enterprises, show_profiles) ->
enterprises ||= []
show_profiles ?= true
enterprises.filter (enterprise)=>
show_profiles or enterprise.has_shopfront

View File

@@ -1,13 +1,16 @@
Darkswarm.filter 'taxons', (Matcher)->
# Filter anything that responds to object.taxons, and/or object.primary_taxon
Darkswarm.filter 'taxons', ()->
# Filter anything that responds to object.taxons, object.supplied_taxon or object.primary_taxon.
(objects, ids) ->
objects ||= []
ids ?= []
if ids.length == 0
# No taxons selected, pass all objects through.
objects
else
objects.filter (obj)->
taxons = obj.taxons
taxons.concat obj.supplied_taxons if obj.supplied_taxons
# Combine object taxons with supplied taxons, if they exist.
taxons = taxons.concat obj.supplied_taxons if obj.supplied_taxons
# Match primary taxon if it exists, then taxon array.
obj.primary_taxon?.id in ids || taxons.some (taxon)->
taxon.id in ids

View File

@@ -1,4 +1,4 @@
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections)->
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections, Loading)->
new class AuthenticationService
selectedPath: "/login"
@@ -25,4 +25,9 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
active: Navigation.active
close: ->
Navigation.navigate "/"
if location.pathname == "/"
Navigation.navigate "/"
else
Loading.message = "Taking you back to the home page"
location.hash = ""
location.pathname = "/"

View File

@@ -1,3 +1,4 @@
Darkswarm.factory 'CurrentHub', ($location, $filter, currentHub) ->
Darkswarm.factory 'CurrentHub', (currentHub) ->
# Populate CurrentHub.hub from json in page. This is probably redundant now.
new class CurrentHub
hub: currentHub

View File

@@ -1,3 +1,4 @@
Darkswarm.factory 'CurrentOrder', (currentOrder) ->
# Populate Currentorder.order from json in page. This is probably redundant now.
new class CurrentOrder
order: currentOrder

View File

@@ -0,0 +1,8 @@
Darkswarm.factory "EnterpriseModal", ($modal, $rootScope)->
# Build a modal popup for an enterprise.
new class EnterpriseModal
open: (enterprise)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.enterprise = enterprise
$modal.open(templateUrl: "enterprise_modal.html", scope: scope)

View File

@@ -1,13 +1,21 @@
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer)->
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter)->
new class Enterprises
enterprises_by_id: {} # id/object pairs for lookup
enterprises_by_id: {}
constructor: ->
# Populate Enterprises.enterprises from json in page.
@enterprises = enterprises
# Map enterprises to id/object pairs for lookup.
for enterprise in enterprises
@enterprises_by_id[enterprise.id] = enterprise
# Replace enterprise and taxons ids with actual objects.
@dereferenceEnterprises()
@dereferenceTaxons()
@visible_enterprises = visibleFilter @enterprises
@producers = @visible_enterprises.filter (enterprise)->
enterprise.category in ["producer_hub", "producer_shop", "producer"]
@hubs = @visible_enterprises.filter (enterprise)->
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
dereferenceEnterprises: ->
if CurrentHub.hub?.id
CurrentHub.hub = @enterprises_by_id[CurrentHub.hub.id]
@@ -16,6 +24,7 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer)
Dereferencer.dereference enterprise.producers, @enterprises_by_id
dereferenceTaxons: ->
for enterprise in @enterprises
for enterprise in @enterprises
Dereferencer.dereference enterprise.taxons, Taxons.taxons_by_id
Dereferencer.dereference enterprise.supplied_taxons, Taxons.taxons_by_id

View File

@@ -1,9 +0,0 @@
Darkswarm.factory 'Hubs', ($filter, Enterprises, visibleFilter) ->
new class Hubs
constructor: ->
@hubs = @order Enterprises.enterprises.filter (hub)->
hub.has_hub_listing
@visible = visibleFilter @hubs
order: (hubs)->
$filter('orderBy')(hubs, ['-active', '+orders_close_at'])

View File

@@ -1,7 +1,7 @@
Darkswarm.factory "OfnMap", (Enterprises, MapModal, visibleFilter)->
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter)->
new class OfnMap
constructor: ->
@enterprises = (@extend(enterprise) for enterprise in visibleFilter(Enterprises.enterprises))
@enterprises = (@extend(enterprise) for enterprise in visibleFilter(Enterprises.enterprises))
# Adding methods to each enterprise
@@ -14,4 +14,4 @@ Darkswarm.factory "OfnMap", (Enterprises, MapModal, visibleFilter)->
icon: enterprise.icon
id: enterprise.id
reveal: =>
MapModal.open enterprise
EnterpriseModal.open enterprise

View File

@@ -1,12 +0,0 @@
Darkswarm.factory "MapModal", ($modal, $rootScope)->
new class MapModal
open: (enterprise)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.enterprise = enterprise
if enterprise.is_distributor
scope.hub = enterprise
$modal.open(templateUrl: "hub_modal.html", scope: scope)
else
scope.producer = enterprise
$modal.open(templateUrl: "map_modal_producer.html", scope: scope)

View File

@@ -1,6 +1,7 @@
Darkswarm.factory "Matcher", ->
new class Matcher
match: (properties, text)->
properties.some (prop)->
prop ||= ""
prop.toLowerCase().indexOf(text.toLowerCase()) != -1
# Match text fragment in an array of strings.
new class Matcher
match: (properties, text)->
properties.some (prop)->
prop ||= ""
prop.toLowerCase().indexOf(text.toLowerCase()) != -1

View File

@@ -1,7 +0,0 @@
Darkswarm.factory 'Producers', (Enterprises, visibleFilter) ->
new class Producers
constructor: ->
@producers = Enterprises.enterprises.filter (enterprise)->
enterprise.is_primary_producer
@visible = visibleFilter @producers

View File

@@ -1,8 +1,10 @@
Darkswarm.factory "Taxons", (taxons)->
new class Taxons
taxons: taxons
# Populate Taxons.taxons from json in page.
taxons: taxons
taxons_by_id: {}
constructor: ->
# Map taxons to id/object pairs for lookup.
for taxon in @taxons
@taxons_by_id[taxon.id] = taxon

View File

@@ -5,6 +5,7 @@ Darkswarm.factory 'Variants', ->
@variants[variant.id] ||= @extend variant
extend: (variant)->
# Add totalPrice method to calculate line item total. This should be on a line item!
variant.totalPrice = ->
variant.price_with_fees * variant.line_item.quantity
variant.basePricePercentage = Math.round(variant.price / variant.price_with_fees * 100)

View File

@@ -1,18 +0,0 @@
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery
//= require jquery_ujs
//= require jquery-ui
//= require spin
//= require foundation
//= require_tree .
//
// Hacky fix for issue - http://foundation.zurb.com/forum/posts/2112-foundation-5100-syntax-error-in-js
Foundation.set_namespace = function() {};
$(function(){ $(document).foundation(); });

View File

@@ -1,444 +0,0 @@
Gmaps = {}
Gmaps.triggerOldOnload = ->
Gmaps.oldOnload() if typeof(Gmaps.oldOnload) == 'function'
Gmaps.loadMaps = ->
#loop through all variable names.
#there should only be maps inside so it trigger their load function
for key, value of Gmaps
searchLoadIncluded = key.search(/load/)
if searchLoadIncluded == -1
load_function_name = "load_" + key
Gmaps[load_function_name]()
window.Gmaps = Gmaps
class @Gmaps4Rails
constructor: ->
#map config
@map = null #DEPRECATED: will still contain a copy of serviceObject below as transition
@serviceObject = null #contains the map we're working on
@visibleInfoWindow = null #contains the current opened infowindow
@userLocation = null #contains user's location if geolocalization was performed and successful
#empty slots
@geolocationSuccess = -> false #triggered when geolocation succeeds. Can be customized.
@geolocationFailure = -> false #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean
@callback = -> false #to let user set a custom callback function
@customClusterer = -> false #to let user set custom clusterer pictures
@infobox = -> false #to let user use custom infoboxes
@jsTemplate = false #to let user create infowindows client side
@default_map_options =
id: 'map'
draggable: true
detect_location: false # should the browser attempt to use geolocation detection features of HTML5?
center_on_user: false # centers map on the location detected through the browser
center_latitude: 0
center_longitude: 0
zoom: 7
maxZoom: null
minZoom: null
auto_adjust : true # adjust the map to the markers if set to true
auto_zoom: true # zoom given by auto-adjust
bounds: [] # adjust map to these limits. Should be [{"lat": , "lng": }]
raw: {} # raw json to pass additional options
@default_markers_conf =
#Marker config
title: ""
#MarkerImage config
picture : ""
width: 22
length: 32
draggable: false # how to modify: <%= gmaps( "markers" => { "data" => @object.to_gmaps4rails, "options" => { "draggable" => true }}) %>
#clustering config
do_clustering: false # do clustering if set to true
randomize: false # Google maps can't display two markers which have the same coordinates. This randomizer enables to prevent this situation from happening.
max_random_distance: 100 # in meters. Each marker coordinate could be altered by this distance in a random direction
list_container: null # id of the ul that will host links to all markers
offset: 0 # used when adding_markers to an existing map. Because new markers are concated with previous one, offset is here to prevent the existing from being re-created.
raw: {} # raw json to pass additional options
#Stored variables
@markers = [] # contains all markers. A marker contains the following: {"description": , "longitude": , "title":, "latitude":, "picture": "", "width": "", "length": "", "sidebar": "", "serviceObject": google_marker}
@boundsObject = null # contains current bounds from markers, polylines etc...
@polygons = [] # contains raw data, array of arrays (first element could be a hash containing options)
@polylines = [] # contains raw data, array of arrays (first element could be a hash containing options)
@circles = [] # contains raw data, array of hash
@markerClusterer = null # contains all marker clusterers
@markerImages = []
#Polyline Styling
@polylines_conf = #default style for polylines
strokeColor: "#FF0000"
strokeOpacity: 1
strokeWeight: 2
clickable: false
zIndex: null
#tnitializes the map
initialize : ->
@serviceObject = @createMap()
@map = @serviceObject #beware, soon deprecated
if (@map_options.detect_location == true or @map_options.center_on_user == true)
@findUserLocation(this)
#resets sidebar if needed
@resetSidebarContent()
findUserLocation : (map_object) ->
if (navigator.geolocation)
#try to retrieve user's position
positionSuccessful = (position) ->
map_object.userLocation = map_object.createLatLng(position.coords.latitude, position.coords.longitude)
#change map's center to focus on user's geoloc if asked
if(map_object.map_options.center_on_user == true)
map_object.centerMapOnUser()
map_object.geolocationSuccess()
positionFailure = ->
map_object.geolocationFailure(true)
navigator.geolocation.getCurrentPosition( positionSuccessful, positionFailure)
else
#failure but the navigator doesn't handle geolocation
map_object.geolocationFailure(false)
#////////////////////////////////////////////////////
#//////////////////// DIRECTIONS ////////////////////
#////////////////////////////////////////////////////
create_direction : ->
directionsDisplay = new google.maps.DirectionsRenderer()
directionsService = new google.maps.DirectionsService()
directionsDisplay.setMap(@serviceObject)
#display panel only if required
if @direction_conf.display_panel
directionsDisplay.setPanel(document.getElementById(@direction_conf.panel_id))
directionsDisplay.setOptions
suppressMarkers: false
suppressInfoWindows: false
suppressPolylines: false
request =
origin: @direction_conf.origin
destination: @direction_conf.destination
waypoints: @direction_conf.waypoints
optimizeWaypoints: @direction_conf.optimizeWaypoints
unitSystem: google.maps.DirectionsUnitSystem[@direction_conf.unitSystem]
avoidHighways: @direction_conf.avoidHighways
avoidTolls: @direction_conf.avoidTolls
region: @direction_conf.region
travelMode: google.maps.DirectionsTravelMode[@direction_conf.travelMode]
language: "en"
directionsService.route request, (response, status) ->
if (status == google.maps.DirectionsStatus.OK)
directionsDisplay.setDirections(response)
#////////////////////////////////////////////////////
#///////////////////// CIRCLES //////////////////////
#////////////////////////////////////////////////////
#Loops through all circles
#Loops through all circles and draws them
create_circles : ->
for circle in @circles
@create_circle circle
create_circle : (circle) ->
#by convention, default style configuration could be integrated in the first element
if circle == @circles[0]
@circles_conf.strokeColor = circle.strokeColor if circle.strokeColor?
@circles_conf.strokeOpacity = circle.strokeOpacity if circle.strokeOpacity?
@circles_conf.strokeWeight = circle.strokeWeight if circle.strokeWeight?
@circles_conf.fillColor = circle.fillColor if circle.fillColor?
@circles_conf.fillOpacity = circle.fillOpacity if circle.fillOpacity?
if circle.lat? and circle.lng?
# always check if a config is given, if not, use defaults
# NOTE: is there a cleaner way to do this? Maybe a hash merge of some sort?
newCircle = new google.maps.Circle
center: @createLatLng(circle.lat, circle.lng)
strokeColor: circle.strokeColor || @circles_conf.strokeColor
strokeOpacity: circle.strokeOpacity || @circles_conf.strokeOpacity
strokeWeight: circle.strokeWeight || @circles_conf.strokeWeight
fillOpacity: circle.fillOpacity || @circles_conf.fillOpacity
fillColor: circle.fillColor || @circles_conf.fillColor
clickable: circle.clickable || @circles_conf.clickable
zIndex: circle.zIndex || @circles_conf.zIndex
radius: circle.radius
circle.serviceObject = newCircle
newCircle.setMap(@serviceObject)
# clear circles
clear_circles : ->
for circle in @circles
@clear_circle circle
clear_circle : (circle) ->
circle.serviceObject.setMap(null)
hide_circles : ->
for circle in @circles
@hide_circle circle
hide_circle : (circle) ->
circle.serviceObject.setMap(null)
show_circles : ->
for circle in @circles
@show_circle @circle
show_circle : (circle) ->
circle.serviceObject.setMap(@serviceObject)
#////////////////////////////////////////////////////
#///////////////////// POLYGONS /////////////////////
#////////////////////////////////////////////////////
#polygons is an array of arrays. It loops.
create_polygons : ->
for polygon in @polygons
@create_polygon(polygon)
#creates a single polygon, triggered by create_polygons
create_polygon : (polygon) ->
polygon_coordinates = []
#Polygon points are in an Array, that's why looping is necessary
for point in polygon
latlng = @createLatLng(point.lat, point.lng)
polygon_coordinates.push(latlng)
#first element of an Array could contain specific configuration for this particular polygon. If no config given, use default
if point == polygon[0]
strokeColor = point.strokeColor || @polygons_conf.strokeColor
strokeOpacity = point.strokeOpacity || @polygons_conf.strokeOpacity
strokeWeight = point.strokeWeight || @polygons_conf.strokeWeight
fillColor = point.fillColor || @polygons_conf.fillColor
fillOpacity = point.fillOpacity || @polygons_conf.fillOpacity
clickable = point.clickable || @polygons_conf.clickable
#Construct the polygon
new_poly = new google.maps.Polygon
paths: polygon_coordinates
strokeColor: strokeColor
strokeOpacity: strokeOpacity
strokeWeight: strokeWeight
fillColor: fillColor
fillOpacity: fillOpacity
clickable: clickable
map: @serviceObject
#save polygon in list
polygon.serviceObject = new_poly
#////////////////////////////////////////////////////
#///////////////////// MARKERS //////////////////////
#////////////////////////////////////////////////////
#creates, clusterizes and adjusts map
create_markers : ->
@createServiceMarkersFromMarkers()
@clusterize()
#create google.maps Markers from data provided by user
createServiceMarkersFromMarkers : ->
for marker, index in @markers
if not @markers[index].serviceObject?
#extract options, test if value passed or use default
Lat = @markers[index].lat
Lng = @markers[index].lng
#alter coordinates if randomize is true
if @markers_conf.randomize
LatLng = @randomize(Lat, Lng)
#retrieve coordinates from the array
Lat = LatLng[0]
Lng = LatLng[1]
#save object
@markers[index].serviceObject = @createMarker
"marker_picture": if @markers[index].picture then @markers[index].picture else @markers_conf.picture
"marker_width": if @markers[index].width then @markers[index].width else @markers_conf.width
"marker_height": if @markers[index].height then @markers[index].height else @markers_conf.length
"marker_title": if @markers[index].title then @markers[index].title else null
"marker_anchor": if @markers[index].marker_anchor then @markers[index].marker_anchor else null
"shadow_anchor": if @markers[index].shadow_anchor then @markers[index].shadow_anchor else null
"shadow_picture": if @markers[index].shadow_picture then @markers[index].shadow_picture else null
"shadow_width": if @markers[index].shadow_width then @markers[index].shadow_width else null
"shadow_height": if @markers[index].shadow_height then @markers[index].shadow_height else null
"marker_draggable": if @markers[index].draggable then @markers[index].draggable else @markers_conf.draggable
"rich_marker": if @markers[index].rich_marker then @markers[index].rich_marker else null
"zindex": if @markers[index].zindex then @markers[index].zindex else null
"Lat": Lat
"Lng": Lng
"index": index
#add infowindowstuff if enabled
@createInfoWindow(@markers[index])
#create sidebar if enabled
@createSidebar(@markers[index])
@markers_conf.offset = @markers.length
#creates Image Anchor Position or return null if nothing passed
createImageAnchorPosition : (anchorLocation) ->
if (anchorLocation == null)
return null
else
return @createPoint(anchorLocation[0], anchorLocation[1])
#replace old markers with new markers on an existing map
replaceMarkers : (new_markers, adjustBounds = true) ->
@clearMarkers()
#reset previous markers
@markers = new Array
#reset current bounds
@boundsObject = @createLatLngBounds() if adjustBounds
#reset sidebar content if exists
@resetSidebarContent()
#add new markers
@markers_conf.offset = 0
@addMarkers(new_markers, adjustBounds)
#add new markers to on an existing map
addMarkers : (new_markers, adjustBounds = true) ->
#update the list of markers to take into account
@markers = @markers.concat(new_markers)
#put markers on the map
@create_markers()
@adjustMapToBounds() if adjustBounds
#////////////////////////////////////////////////////
#///////////////////// SIDEBAR //////////////////////
#////////////////////////////////////////////////////
#//creates sidebar
createSidebar : (marker_container) ->
if (@markers_conf.list_container)
ul = document.getElementById(@markers_conf.list_container)
li = document.createElement('li')
aSel = document.createElement('a')
aSel.href = 'javascript:void(0);'
html = if marker_container.sidebar? then marker_container.sidebar else "Marker"
aSel.innerHTML = html
currentMap = this
aSel.onclick = @sidebar_element_handler(currentMap, marker_container.serviceObject, 'click')
li.appendChild(aSel)
ul.appendChild(li)
#moves map to marker clicked + open infowindow
sidebar_element_handler : (currentMap, marker, eventType) ->
return () ->
currentMap.map.panTo(marker.position)
google.maps.event.trigger(marker, eventType)
resetSidebarContent : ->
if @markers_conf.list_container isnt null
ul = document.getElementById(@markers_conf.list_container)
ul.innerHTML = ""
#////////////////////////////////////////////////////
#////////////////// MISCELLANEOUS ///////////////////
#////////////////////////////////////////////////////
#to make the map fit the different LatLng points
adjustMapToBounds : ->
#FIRST_STEP: retrieve all bounds
#create the bounds object only if necessary
if @map_options.auto_adjust or @map_options.bounds isnt null
@boundsObject = @createLatLngBounds()
#if autodjust is true, must get bounds from markers polylines etc...
if @map_options.auto_adjust
#from markers
@extendBoundsWithMarkers()
#from polylines:
@updateBoundsWithPolylines()
#from polygons:
@updateBoundsWithPolygons()
#from circles
@updateBoundsWithCircles()
#in every case, I've to take into account the bounds set up by the user
@extendMapBounds()
#SECOND_STEP: ajust the map to the bounds
@adaptMapToBounds()
#////////////////////////////////////////////////////
#/////////////////// POLYLINES //////////////////////
#////////////////////////////////////////////////////
#replace old markers with new markers on an existing map
replacePolylines : (new_polylines) ->
#reset previous polylines and kill them from map
@destroy_polylines()
#set new polylines
@polylines = new_polylines
#create
@create_polylines()
#.... and adjust map boundaries
@adjustMapToBounds()
destroy_polylines : ->
for polyline in @polylines
#delete polylines from map
polyline.serviceObject.setMap(null)
#empty array
@polylines = []
#polylines is an array of arrays. It loops.
create_polylines : ->
for polyline in @polylines
@create_polyline polyline
#////////////////////////////////////////////////////
#///////////////// Basic functions //////////////////
#///////////////////tests coded//////////////////////
#//basic function to check existence of a variable
exists : (var_name) ->
return (var_name != "" and typeof var_name != "undefined")
#randomize
randomize : (Lat0, Lng0) ->
#distance in meters between 0 and max_random_distance (positive or negative)
dx = @markers_conf.max_random_distance * @random()
dy = @markers_conf.max_random_distance * @random()
Lat = parseFloat(Lat0) + (180/Math.PI)*(dy/6378137)
Lng = parseFloat(Lng0) + ( 90/Math.PI)*(dx/6378137)/Math.cos(Lat0)
return [Lat, Lng]
mergeObjectWithDefault : (object1, object2) ->
copy_object1 = {}
for key, value of object1
copy_object1[key] = value
for key, value of object2
unless copy_object1[key]?
copy_object1[key] = value
return copy_object1
mergeWithDefault : (objectName) ->
default_object = @["default_" + objectName]
object = @[objectName]
@[objectName] = @mergeObjectWithDefault(object, default_object)
return true
#gives a value between -1 and 1
random : -> return(Math.random() * 2 -1)

View File

@@ -1,174 +0,0 @@
######################################################################################################
############################################## Bing Maps ##########################################
######################################################################################################
#// http://wiki.openstreetmap.org/wiki/OpenLayers
#// http://openlayers.org/dev/examples
#//http://docs.openlayers.org/contents.html
class @Gmaps4RailsBing extends Gmaps4Rails
constructor: ->
super
@map_options =
type: "road" # aerial, auto, birdseye, collinsBart, mercator, ordnanceSurvey, road
@markers_conf =
infobox: "description" #description or htmlContent
@mergeWithDefault("map_options")
@mergeWithDefault("markers_conf")
#////////////////////////////////////////////////////
#/////////////// Basic Objects //////////////
#////////////////////////////////////////////////////
getMapType: ->
switch @map_options.type
when "road" then return Microsoft.Maps.MapTypeId.road
when "aerial" then return Microsoft.Maps.MapTypeId.aerial
when "auto" then return Microsoft.Maps.MapTypeId.auto
when "birdseye" then return Microsoft.Maps.MapTypeId.birdseye
when "collinsBart" then return Microsoft.Maps.MapTypeId.collinsBart
when "mercator" then return Microsoft.Maps.MapTypeId.mercator
when "ordnanceSurvey" then return Microsoft.Maps.MapTypeId.ordnanceSurvey
else return Microsoft.Maps.MapTypeId.auto
createPoint: (lat, lng) ->
return new Microsoft.Maps.Point(lat, lng)
createLatLng:(lat, lng) ->
return new Microsoft.Maps.Location(lat, lng)
createLatLngBounds: ->
createMap: ->
return new Microsoft.Maps.Map(document.getElementById(@map_options.id), {
credentials: @map_options.provider_key,
mapTypeId: @getMapType(),
center: @createLatLng(@map_options.center_latitude, @map_options.center_longitude),
zoom: @map_options.zoom
})
createSize: (width, height) ->
return new google.maps.Size(width, height)
#////////////////////////////////////////////////////
#////////////////////// Markers /////////////////////
#////////////////////////////////////////////////////
createMarker: (args) ->
markerLatLng = @createLatLng(args.Lat, args.Lng)
marker
#// Marker sizes are expressed as a Size of X,Y
if args.marker_picture == ""
marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), {
draggable: args.marker_draggable,
anchor: @createImageAnchorPosition(args.Lat, args.Lng),
text: args.marker_title
}
);
else
marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), {
draggable: args.marker_draggable,
anchor: @createImageAnchorPosition(args.Lat, args.Lng),
icon: args.marker_picture,
height: args.marker_height,
text: args.marker_title,
width: args.marker_width
}
);
@addToMap(marker)
return marker
#// clear markers
clearMarkers: ->
for marker in @markers
@clearMarker marker
clearMarker: (marker) ->
@removeFromMap(marker.serviceObject)
#//show and hide markers
showMarkers: ->
for marker in @markers
@showMarker marker
showMarker: (marker) ->
marker.serviceObject.setOptions({ visible: true })
hideMarkers: ->
for marker in @markers
@hideMarker marker
hideMarker: (marker) ->
marker.serviceObject.setOptions({ visible: false })
extendBoundsWithMarkers: ->
locationsArray = []
for marker in @markers
locationsArray.push(marker.serviceObject.getLocation())
@boundsObject = Microsoft.Maps.LocationRect.fromLocations(locationsArray)
#////////////////////////////////////////////////////
#/////////////////// Clusterer //////////////////////
#////////////////////////////////////////////////////
createClusterer: (markers_array) ->
clearClusterer: ->
#//creates clusters
clusterize: ->
#////////////////////////////////////////////////////
#/////////////////// INFO WINDOW ////////////////////
#////////////////////////////////////////////////////
#// creates infowindows
createInfoWindow: (marker_container) ->
if marker_container.description?
#//create the infowindow
if @markers_conf.infobox == "description"
marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { description: marker_container.description, visible: false, showCloseButton: true})
else
marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { htmlContent: marker_container.description, visible: false})
#//add the listener associated
currentMap = this
Microsoft.Maps.Events.addHandler(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.info_window))
@addToMap(marker_container.info_window)
openInfoWindow: (currentMap, infoWindow) ->
return ->
# Close the latest selected marker before opening the current one.
if currentMap.visibleInfoWindow
currentMap.visibleInfoWindow.setOptions({ visible: false })
infoWindow.setOptions({ visible:true })
currentMap.visibleInfoWindow = infoWindow
#////////////////////////////////////////////////////
#/////////////////// Other methods //////////////////
#////////////////////////////////////////////////////
fitBounds: ->
@serviceObject.setView({bounds: @boundsObject})
addToMap: (object)->
@serviceObject.entities.push(object)
removeFromMap: (object)->
@serviceObject.entities.remove(object)
centerMapOnUser: ->
@serviceObject.setView({ center: @userLocation})
updateBoundsWithPolylines: ()->
updateBoundsWithPolygons: ()->
updateBoundsWithCircles: ()->
extendMapBounds :->
adaptMapToBounds: ->
@fitBounds()

View File

@@ -1,339 +0,0 @@
#######################################################################################################
############################################## Google maps ##########################################
#######################################################################################################
class @Gmaps4RailsGoogle extends Gmaps4Rails
constructor: ->
super
#Map settings
@map_options =
disableDefaultUI: false
disableDoubleClickZoom: false
type: "ROADMAP" # HYBRID, ROADMAP, SATELLITE, TERRAIN
#markers + info styling
@markers_conf =
clusterer_gridSize: 50
clusterer_maxZoom: 5
custom_cluster_pictures: null
custom_infowindow_class: null
@mergeWithDefault("map_options")
@mergeWithDefault("markers_conf")
@kml_options =
clickable: true
preserveViewport: false
suppressInfoWindows: false
#Polygon Styling
@polygons_conf = # default style for polygons
strokeColor: "#FFAA00"
strokeOpacity: 0.8
strokeWeight: 2
fillColor: "#000000"
fillOpacity: 0.35
clickable: false
#Circle Styling
@circles_conf = #default style for circles
fillColor: "#00AAFF"
fillOpacity: 0.35
strokeColor: "#FFAA00"
strokeOpacity: 0.8
strokeWeight: 2
clickable: false
zIndex: null
#Direction Settings
@direction_conf =
panel_id: null
display_panel: false
origin: null
destination: null
waypoints: [] #[{location: "toulouse,fr", stopover: true}, {location: "Clermont-Ferrand, fr", stopover: true}]
optimizeWaypoints: false
unitSystem: "METRIC" #IMPERIAL
avoidHighways: false
avoidTolls: false
region: null
travelMode: "DRIVING" #WALKING, BICYCLING
#////////////////////////////////////////////////////
#/////////////// Basic Objects //////////////
#////////////////////////////////////////////////////
createPoint : (lat, lng) ->
return new google.maps.Point(lat, lng)
createLatLng : (lat, lng) ->
return new google.maps.LatLng(lat, lng)
createLatLngBounds : ->
return new google.maps.LatLngBounds()
createMap : ->
defaultOptions =
maxZoom: @map_options.maxZoom
minZoom: @map_options.minZoom
zoom: @map_options.zoom
center: @createLatLng(@map_options.center_latitude, @map_options.center_longitude)
mapTypeId: google.maps.MapTypeId[@map_options.type]
mapTypeControl: @map_options.mapTypeControl
disableDefaultUI: @map_options.disableDefaultUI
disableDoubleClickZoom: @map_options.disableDoubleClickZoom
draggable: @map_options.draggable
mergedOptions = @mergeObjectWithDefault @map_options.raw, defaultOptions
return new google.maps.Map document.getElementById(@map_options.id), mergedOptions
createMarkerImage : (markerPicture, markerSize, origin, anchor, scaledSize) ->
return new google.maps.MarkerImage(markerPicture, markerSize, origin, anchor, scaledSize)
createSize : (width, height) ->
return new google.maps.Size(width, height)
#////////////////////////////////////////////////////
#////////////////////// Markers /////////////////////
#////////////////////////////////////////////////////
createMarker : (args) ->
markerLatLng = @createLatLng(args.Lat, args.Lng)
#Marker sizes are expressed as a Size of X,Y
if args.marker_picture == "" and args.rich_marker == null
defaultOptions = {position: markerLatLng, map: @serviceObject, title: args.marker_title, draggable: args.marker_draggable, zIndex: args.zindex}
mergedOptions = @mergeObjectWithDefault @markers_conf.raw, defaultOptions
return new google.maps.Marker mergedOptions
if (args.rich_marker != null)
return new RichMarker({
position: markerLatLng
map: @serviceObject
draggable: args.marker_draggable
content: args.rich_marker
flat: if args.marker_anchor == null then false else args.marker_anchor[1]
anchor: if args.marker_anchor == null then 0 else args.marker_anchor[0]
zIndex: args.zindex
})
#default behavior
#calculate MarkerImage anchor location
imageAnchorPosition = @createImageAnchorPosition args.marker_anchor
shadowAnchorPosition = @createImageAnchorPosition args.shadow_anchor
#create or retrieve existing MarkerImages
markerImage = @createOrRetrieveImage(args.marker_picture, args.marker_width, args.marker_height, imageAnchorPosition)
shadowImage = @createOrRetrieveImage(args.shadow_picture, args.shadow_width, args.shadow_height, shadowAnchorPosition)
defaultOptions = {position: markerLatLng, map: @serviceObject, icon: markerImage, title: args.marker_title, draggable: args.marker_draggable, shadow: shadowImage, zIndex: args.zindex}
mergedOptions = @mergeObjectWithDefault @markers_conf.raw, defaultOptions
return new google.maps.Marker mergedOptions
#checks if obj is included in arr Array and returns the position or false
includeMarkerImage : (arr, obj) ->
for object, index in arr
return index if object.url == obj
return false
#checks if MarkerImage exists before creating a new one
#returns a MarkerImage or false if ever something wrong is passed as argument
createOrRetrieveImage : (currentMarkerPicture, markerWidth, markerHeight, imageAnchorPosition) ->
return null if (currentMarkerPicture == "" or currentMarkerPicture == null )
test_image_index = @includeMarkerImage(@markerImages, currentMarkerPicture)
switch test_image_index
when false
markerImage = @createMarkerImage(currentMarkerPicture, @createSize(markerWidth, markerHeight), null, imageAnchorPosition, null )
@markerImages.push(markerImage)
return markerImage
break
else
return @markerImages[test_image_index] if typeof test_image_index == 'number'
return false
#clear markers
clearMarkers : ->
for marker in @markers
@clearMarker marker
#show and hide markers
showMarkers : ->
for marker in @markers
@showMarker marker
hideMarkers : ->
for marker in @markers
@hideMarker marker
clearMarker : (marker) ->
marker.serviceObject.setMap(null)
showMarker : (marker) ->
marker.serviceObject.setVisible(true)
hideMarker : (marker) ->
marker.serviceObject.setVisible(false)
extendBoundsWithMarkers : ->
for marker in @markers
@boundsObject.extend(marker.serviceObject.position)
#////////////////////////////////////////////////////
#/////////////////// Clusterer //////////////////////
#////////////////////////////////////////////////////
createClusterer : (markers_array) ->
return new MarkerClusterer( @serviceObject, markers_array, { maxZoom: @markers_conf.clusterer_maxZoom, gridSize: @markers_conf.clusterer_gridSize, styles: @customClusterer() })
clearClusterer : ->
@markerClusterer.clearMarkers()
#creates clusters
clusterize : ->
if @markers_conf.do_clustering == true
#first clear the existing clusterer if any
@clearClusterer() if @markerClusterer != null
markers_array = new Array
for marker in @markers
markers_array.push(marker.serviceObject)
@markerClusterer = @createClusterer(markers_array)
#////////////////////////////////////////////////////
#/////////////////// INFO WINDOW ////////////////////
#////////////////////////////////////////////////////
#// creates infowindows
createInfoWindow : (marker_container) ->
if typeof(@jsTemplate) == "function" or marker_container.description?
marker_container.description = @jsTemplate(marker_container) if typeof(@jsTemplate) == "function"
if @markers_conf.custom_infowindow_class != null
#creating custom infowindow
boxText = document.createElement("div")
boxText.setAttribute("class", @markers_conf.custom_infowindow_class) #to customize
boxText.innerHTML = marker_container.description
marker_container.infowindow = new InfoBox(@infobox(boxText))
currentMap = this
google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject))
else
#create default infowindow
marker_container.infowindow = new google.maps.InfoWindow({content: marker_container.description })
#add the listener associated
currentMap = this
google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject))
openInfoWindow : (currentMap, infoWindow, marker) ->
return ->
# Close the latest selected marker before opening the current one.
currentMap.visibleInfoWindow.close() if currentMap.visibleInfoWindow != null
infoWindow.open(currentMap.serviceObject, marker)
currentMap.visibleInfoWindow = infoWindow
#////////////////////////////////////////////////////
#///////////////// KML //////////////////
#////////////////////////////////////////////////////
createKmlLayer : (kml) ->
kml_options = kml.options || {}
kml_options = @mergeObjectWithDefault(kml_options, @kml_options)
kml = new google.maps.KmlLayer( kml.url, kml_options)
kml.setMap(@serviceObject)
return kml
#////////////////////////////////////////////////////
#/////////////////// POLYLINES //////////////////////
#////////////////////////////////////////////////////
#creates a single polyline, triggered by create_polylines
create_polyline : (polyline) ->
polyline_coordinates = []
#2 cases here, either we have a coded array of LatLng or we have an Array of LatLng
for element in polyline
#if we have a coded array
if element.coded_array?
decoded_array = new google.maps.geometry.encoding.decodePath(element.coded_array)
#loop through every point in the array
for point in decoded_array
polyline_coordinates.push(point)
#or we have an array of latlng
else
#by convention, a single polyline could be customized in the first array or it uses default values
if element == polyline[0]
strokeColor = element.strokeColor || @polylines_conf.strokeColor
strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity
strokeWeight = element.strokeWeight || @polylines_conf.strokeWeight
clickable = element.clickable || @polylines_conf.clickable
zIndex = element.zIndex || @polylines_conf.zIndex
#add latlng if positions provided
if element.lat? && element.lng?
latlng = @createLatLng(element.lat, element.lng)
polyline_coordinates.push(latlng)
# Construct the polyline
new_poly = new google.maps.Polyline
path: polyline_coordinates
strokeColor: strokeColor
strokeOpacity: strokeOpacity
strokeWeight: strokeWeight
clickable: clickable
zIndex: zIndex
#save polyline
polyline.serviceObject = new_poly
new_poly.setMap(@serviceObject)
updateBoundsWithPolylines: ()->
for polyline in @polylines
polyline_points = polyline.serviceObject.latLngs.getArray()[0].getArray()
for point in polyline_points
@boundsObject.extend point
#////////////////////////////////////////////////////
#///////////////// KML //////////////////
#////////////////////////////////////////////////////
create_kml : ->
for kml in @kml
kml.serviceObject = @createKmlLayer kml
#////////////////////////////////////////////////////
#/////////////////// Other methods //////////////////
#////////////////////////////////////////////////////
fitBounds : ->
@serviceObject.fitBounds(@boundsObject) unless @boundsObject.isEmpty()
centerMapOnUser : ->
@serviceObject.setCenter(@userLocation)
updateBoundsWithPolygons: ()->
for polygon in @polygons
polygon_points = polygon.serviceObject.latLngs.getArray()[0].getArray()
for point in polygon_points
@boundsObject.extend point
updateBoundsWithCircles: ()->
for circle in @circles
@boundsObject.extend(circle.serviceObject.getBounds().getNorthEast())
@boundsObject.extend(circle.serviceObject.getBounds().getSouthWest())
extendMapBounds: ()->
for bound in @map_options.bounds
#create points from bounds provided
@boundsObject.extend @createLatLng(bound.lat, bound.lng)
adaptMapToBounds:()->
#if autozoom is false, take user info into account
if !@map_options.auto_zoom
map_center = @boundsObject.getCenter()
@map_options.center_latitude = map_center.lat()
@map_options.center_longitude = map_center.lng()
@serviceObject.setCenter(map_center)
else
@fitBounds()

View File

@@ -1,145 +0,0 @@
#######################################################################################################
############################################## Map Quest #############################################
#######################################################################################################
# http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/api/MQA.Poi.html
class @Gmaps4RailsMapquest extends Gmaps4Rails
constructor: ->
super
#Map settings
@map_options = {type: "map"} #map type (map, sat, hyb)
@markers_conf = {}
@mergeWithDefault "markers_conf"
@mergeWithDefault "map_options"
#////////////////////////////////////////////////////
#/////////////// Basic Objects //////////////
#////////////////////////////////////////////////////
createPoint: (lat, lng) ->
return new MQA.Poi({lat: lat, lng: lng})
createLatLng: (lat, lng) ->
return {lat: lat, lng: lng}
createLatLngBounds: ->
createMap: ->
map = new MQA.TileMap( #// Constructs an instance of MQA.TileMap
document.getElementById(@map_options.id), #//the id of the element on the page you want the map to be added into
@map_options.zoom, #//intial zoom level of the map
{lat: @map_options.center_latitude, lng: @map_options.center_longitude},
@map_options.type) #//map type (map, sat, hyb)
MQA.withModule('zoomcontrol3', (->
map.addControl(
new MQA.LargeZoomControl3(),
new MQA.MapCornerPlacement(MQA.MapCorner.TOP_LEFT)
)
))
return map
createMarkerImage: (markerPicture, markerSize, origin, anchor, scaledSize) ->
#////////////////////////////////////////////////////
#////////////////////// Markers /////////////////////
#////////////////////////////////////////////////////
createMarker: (args)->
marker = new MQA.Poi( {lat: args.Lat, lng: args.Lng} )
if args.marker_picture != ""
icon = new MQA.Icon(args.marker_picture, args.marker_height, args.marker_width)
marker.setIcon(icon)
if args.marker_anchor != null
marker.setBias({x: args.marker_anchor[0], y: args.marker_anchor[1]})
if args.shadow_picture != ""
icon = new MQA.Icon(args.shadow_picture, args.shadow_height, args.shadow_width)
marker.setShadow(icon)
if args.shadow_anchor != null
marker.setShadowOffset({x: args.shadow_anchor[0], y: args.shadow_anchor[1]})
@addToMap marker
return marker
#// clear markers
clearMarkers: ->
for marker in markers
@clearMarker marker
#//show and hide markers
showMarkers: ->
for marker in markers
@showMarker marker
hideMarkers: ->
for marker in markers
@hideMarker marker
clearMarker: (marker) ->
@removeFromMap(marker.serviceObject)
showMarker: (marker) ->
#// marker.serviceObject
hideMarker: (marker) ->
#// marker.serviceObject
extendBoundsWithMarkers: ->
if @markers.length >=2
@boundsObject = new MQA.RectLL(@markers[0].serviceObject.latLng, @markers[1].serviceObject.latLng)
for marker in @markers
@boundsObject.extend marker.serviceObject.latLng
#////////////////////////////////////////////////////
#/////////////////// Clusterer //////////////////////
#////////////////////////////////////////////////////
createClusterer: (markers_array) ->
clearClusterer: ->
#//creates clusters
clusterize: ->
#////////////////////////////////////////////////////
#/////////////////// INFO WINDOW ////////////////////
#////////////////////////////////////////////////////
#// creates infowindows
createInfoWindow: (marker_container) ->
marker_container.serviceObject.setInfoTitleHTML(marker_container.description)
#//TODO: how to disable the mouseover display when using setInfoContentHTML?
#//marker_container.serviceObject.setInfoContentHTML(marker_container.description);
#////////////////////////////////////////////////////
#/////////////////// Other methods //////////////////
#////////////////////////////////////////////////////
fitBounds: ->
@serviceObject.zoomToRect @boundsObject if @markers.length >=2
@serviceObject.setCenter @markers[0].serviceObject.latLng if @markers.length == 1
centerMapOnUser: ->
@serviceObject.setCenter @userLocation
addToMap: (object) ->
@serviceObject.addShape object
removeFromMap: (object)->
@serviceObject.removeShape object
updateBoundsWithPolylines: ()->
updateBoundsWithPolygons: ()->
updateBoundsWithCircles: ()->
extendMapBounds :->
adaptMapToBounds: ->
@fitBounds()

View File

@@ -1,261 +0,0 @@
#######################################################################################################
############################################## Open Layers ##########################################
#######################################################################################################
#// http://wiki.openstreetmap.org/wiki/OpenLayers
#// http://openlayers.org/dev/examples
#//http://docs.openlayers.org/contents.html
class @Gmaps4RailsOpenlayers extends Gmaps4Rails
constructor: ->
super
@map_options = {}
@mergeWithDefault "map_options"
@markers_conf = {}
@mergeWithDefault "markers_conf"
@openMarkers = null
@markersLayer = null
@markersControl = null
@polylinesLayer = null
#////////////////////////////////////////////////////
#/////////////// Basic Objects ////////////////////
#////////////////////////////////////////////////////
createPoint: (lat, lng)->
createLatLng: (lat, lng)->
return new OpenLayers.LonLat(lng, lat).transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")) # transform from WGS 1984 to Spherical Mercator Projection
createAnchor: (offset)->
return null if offset == null
return new OpenLayers.Pixel(offset[0], offset[1])
createSize: (width, height)->
return new OpenLayers.Size(width, height)
createLatLngBounds: ->
return new OpenLayers.Bounds()
createMap: ->
#//todo add customization: kind of map and other map options
map = new OpenLayers.Map(@map_options.id)
map.addLayer(new OpenLayers.Layer.OSM())
map.setCenter(@createLatLng(@map_options.center_latitude, @map_options.center_longitude), #// Center of the map
@map_options.zoom) #// Zoom level
return map
#////////////////////////////////////////////////////
#////////////////////// Markers /////////////////////
#////////////////////////////////////////////////////
#//http://openlayers.org/dev/examples/marker-shadow.html
createMarker: (args) ->
style_mark = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default'])
style_mark.fillOpacity = 1
#//creating markers' dedicated layer
if (@markersLayer == null)
@markersLayer = new OpenLayers.Layer.Vector("Markers", null)
@serviceObject.addLayer(@markersLayer)
#//TODO move?
@markersLayer.events.register("featureselected", @markersLayer, @onFeatureSelect)
@markersLayer.events.register("featureunselected", @markersLayer, @onFeatureUnselect)
@markersControl = new OpenLayers.Control.SelectFeature(@markersLayer)
@serviceObject.addControl(@markersControl)
@markersControl.activate()
#//showing default pic if none available
if args.marker_picture == ""
#style_mark.graphicWidth = 24
style_mark.graphicHeight = 30
style_mark.externalGraphic = "http://openlayers.org/dev/img/marker-blue.png"
#//creating custom pic
else
style_mark.graphicWidth = args.marker_width
style_mark.graphicHeight = args.marker_height
style_mark.externalGraphic = args.marker_picture
#//adding anchor if any
if args.marker_anchor != null
style_mark.graphicXOffset = args.marker_anchor[0]
style_mark.graphicYOffset = args.marker_anchor[1]
#//adding shadow if any
if args.shadow_picture != ""
style_mark.backgroundGraphic = args.shadow_picture
style_mark.backgroundWidth = args.shadow_width
style_mark.backgroundHeight = args.shadow_height
#//adding shadow's anchor if any
if args.shadow_anchor != null
style_mark.backgroundXOffset = args.shadow_anchor[0]
style_mark.backgroundYOffset = args.shadow_anchor[1]
style_mark.graphicTitle = args.marker_title
marker = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(args.Lng, args.Lat),
null,
style_mark)
#//changing coordinates so that it actually appears on the map!
marker.geometry.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"))
#//adding layer to the map
@markersLayer.addFeatures([marker])
return marker
#//clear markers
clearMarkers: ->
@clearMarkersLayerIfExists()
@markersLayer = null
@boundsObject = new OpenLayers.Bounds()
clearMarkersLayerIfExists: ->
@serviceObject.removeLayer(@markersLayer) if @markersLayer != null and @serviceObject.getLayer(@markersLayer.id) != null
extendBoundsWithMarkers: ->
console.log "here"
for marker in @markers
@boundsObject.extend(@createLatLng(marker.lat,marker.lng))
#////////////////////////////////////////////////////
#/////////////////// Clusterer //////////////////////
#////////////////////////////////////////////////////
#//too ugly to be considered valid :(
createClusterer: (markers_array)->
options =
pointRadius: "${radius}"
fillColor: "#ffcc66"
fillOpacity: 0.8
strokeColor: "#cc6633"
strokeWidth: "${width}"
strokeOpacity: 0.8
funcs =
context:
width: (feature) ->
return (feature.cluster) ? 2 : 1
radius: (feature) ->
pix = 2
pix = Math.min(feature.attributes.count, 7) + 2 if feature.cluster
return pix
style = new OpenLayers.Style options, funcs
strategy = new OpenLayers.Strategy.Cluster()
clusters = new OpenLayers.Layer.Vector "Clusters",
strategies: [strategy]
styleMap: new OpenLayers.StyleMap
"default": style
"select":
fillColor: "#8aeeef"
strokeColor: "#32a8a9"
@clearMarkersLayerIfExists()
@serviceObject.addLayer(clusters)
clusters.addFeatures(markers_array)
return clusters
clusterize: ->
if @markers_conf.do_clustering == true
#//first clear the existing clusterer if any
if @markerClusterer != null
@clearClusterer()
markers_array = new Array
for marker in @markers
markers_array.push(marker.serviceObject)
@markerClusterer = @createClusterer markers_array
clearClusterer: ->
@serviceObject.removeLayer @markerClusterer
#////////////////////////////////////////////////////
#/////////////////// INFO WINDOW ////////////////////
#////////////////////////////////////////////////////
#// creates infowindows
createInfoWindow: (marker_container) ->
marker_container.serviceObject.infoWindow = marker_container.description if marker_container.description?
onPopupClose: (evt) ->
#// 'this' is the popup.
@markersControl.unselect @feature
onFeatureSelect: (evt) ->
feature = evt.feature
popup = new OpenLayers.Popup.FramedCloud("featurePopup",
feature.geometry.getBounds().getCenterLonLat(),
new OpenLayers.Size(300,200),
feature.infoWindow,
null, true, @onPopupClose)
feature.popup = popup
popup.feature = feature
@map.addPopup popup
onFeatureUnselect: (evt) ->
feature = evt.feature
if feature.popup
#//popup.feature = null;
@map.removePopup feature.popup
feature.popup.destroy()
feature.popup = null
#////////////////////////////////////////////////////
#/////////////////// POLYLINES //////////////////////
#////////////////////////////////////////////////////
create_polyline : (polyline) ->
if(@polylinesLayer == null)
@polylinesLayer = new OpenLayers.Layer.Vector("Polylines", null)
@serviceObject.addLayer(@polylinesLayer)
@polylinesLayer.events.register("featureselected", @polylinesLayer, @onFeatureSelect)
@polylinesLayer.events.register("featureunselected", @polylinesLayer, @onFeatureUnselect)
@polylinesControl = new OpenLayers.Control.DrawFeature(@polylinesLayer, OpenLayers.Handler.Path)
@serviceObject.addControl(@polylinesControl)
polyline_coordinates = []
for element in polyline
#by convention, a single polyline could be customized in the first array or it uses default values
if element == polyline[0]
strokeColor = element.strokeColor || @polylines_conf.strokeColor
strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity
strokeWeight = element.strokeWeight || @polylines_conf.strokeWeight
clickable = element.clickable || @polylines_conf.clickable
zIndex = element.zIndex || @polylines_conf.zIndex
#add latlng if positions provided
if element.lat? && element.lng?
latlng = new OpenLayers.Geometry.Point(element.lng, element.lat)
polyline_coordinates.push(latlng)
line_points = new OpenLayers.Geometry.LineString(polyline_coordinates);
line_style = { strokeColor: strokeColor, strokeOpacity: strokeOpacity, strokeWidth: strokeWeight };
polyline = new OpenLayers.Feature.Vector(line_points, null, line_style);
polyline.geometry.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"))
@polylinesLayer.addFeatures([polyline])
return polyline
updateBoundsWithPolylines: ()->
updateBoundsWithPolygons: ()->
updateBoundsWithCircles: ()->
# #////////////////////////////////////////////////////
# #/////////////////// Other methods //////////////////
# #////////////////////////////////////////////////////
fitBounds: ->
@serviceObject.zoomToExtent(@boundsObject, true)
centerMapOnUser: ->
@serviceObject.setCenter @userLocation
extendMapBounds :->
adaptMapToBounds: ->
@fitBounds()

View File

@@ -1,4 +0,0 @@
/*! Backstretch - v2.0.4 - 2013-06-19
* http://srobbin.com/jquery-plugins/backstretch/
* Copyright (c) 2013 Scott Robbin; Licensed MIT */
(function(a,d,p){a.fn.backstretch=function(c,b){(c===p||0===c.length)&&a.error("No images were supplied for Backstretch");0===a(d).scrollTop()&&d.scrollTo(0,0);return this.each(function(){var d=a(this),g=d.data("backstretch");if(g){if("string"==typeof c&&"function"==typeof g[c]){g[c](b);return}b=a.extend(g.options,b);g.destroy(!0)}g=new q(this,c,b);d.data("backstretch",g)})};a.backstretch=function(c,b){return a("body").backstretch(c,b).data("backstretch")};a.expr[":"].backstretch=function(c){return a(c).data("backstretch")!==p};a.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5E3,fade:0};var r={left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},s={position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxHeight:"none",maxWidth:"none",zIndex:-999999},q=function(c,b,e){this.options=a.extend({},a.fn.backstretch.defaults,e||{});this.images=a.isArray(b)?b:[b];a.each(this.images,function(){a("<img />")[0].src=this});this.isBody=c===document.body;this.$container=a(c);this.$root=this.isBody?l?a(d):a(document):this.$container;c=this.$container.children(".backstretch").first();this.$wrap=c.length?c:a('<div class="backstretch"></div>').css(r).appendTo(this.$container);this.isBody||(c=this.$container.css("position"),b=this.$container.css("zIndex"),this.$container.css({position:"static"===c?"relative":c,zIndex:"auto"===b?0:b,background:"none"}),this.$wrap.css({zIndex:-999998}));this.$wrap.css({position:this.isBody&&l?"fixed":"absolute"});this.index=0;this.show(this.index);a(d).on("resize.backstretch",a.proxy(this.resize,this)).on("orientationchange.backstretch",a.proxy(function(){this.isBody&&0===d.pageYOffset&&(d.scrollTo(0,1),this.resize())},this))};q.prototype={resize:function(){try{var a={left:0,top:0},b=this.isBody?this.$root.width():this.$root.innerWidth(),e=b,g=this.isBody?d.innerHeight?d.innerHeight:this.$root.height():this.$root.innerHeight(),j=e/this.$img.data("ratio"),f;j>=g?(f=(j-g)/2,this.options.centeredY&&(a.top="-"+f+"px")):(j=g,e=j*this.$img.data("ratio"),f=(e-b)/2,this.options.centeredX&&(a.left="-"+f+"px"));this.$wrap.css({width:b,height:g}).find("img:not(.deleteable)").css({width:e,height:j}).css(a)}catch(h){}return this},show:function(c){if(!(Math.abs(c)>this.images.length-1)){var b=this,e=b.$wrap.find("img").addClass("deleteable"),d={relatedTarget:b.$container[0]};b.$container.trigger(a.Event("backstretch.before",d),[b,c]);this.index=c;clearInterval(b.interval);b.$img=a("<img />").css(s).bind("load",function(f){var h=this.width||a(f.target).width();f=this.height||a(f.target).height();a(this).data("ratio",h/f);a(this).fadeIn(b.options.speed||b.options.fade,function(){e.remove();b.paused||b.cycle();a(["after","show"]).each(function(){b.$container.trigger(a.Event("backstretch."+this,d),[b,c])})});b.resize()}).appendTo(b.$wrap);b.$img.attr("src",b.images[c]);return b}},next:function(){return this.show(this.index<this.images.length-1?this.index+1:0)},prev:function(){return this.show(0===this.index?this.images.length-1:this.index-1)},pause:function(){this.paused=!0;return this},resume:function(){this.paused=!1;this.next();return this},cycle:function(){1<this.images.length&&(clearInterval(this.interval),this.interval=setInterval(a.proxy(function(){this.paused||this.next()},this),this.options.duration));return this},destroy:function(c){a(d).off("resize.backstretch orientationchange.backstretch");clearInterval(this.interval);c||this.$wrap.remove();this.$container.removeData("backstretch")}};var l,f=navigator.userAgent,m=navigator.platform,e=f.match(/AppleWebKit\/([0-9]+)/),e=!!e&&e[1],h=f.match(/Fennec\/([0-9]+)/),h=!!h&&h[1],n=f.match(/Opera Mobi\/([0-9]+)/),t=!!n&&n[1],k=f.match(/MSIE ([0-9]+)/),k=!!k&&k[1];l=!((-1<m.indexOf("iPhone")||-1<m.indexOf("iPad")||-1<m.indexOf("iPod"))&&e&&534>e||d.operamini&&"[object OperaMini]"==={}.toString.call(d.operamini)||n&&7458>t||-1<f.indexOf("Android")&&e&&533>e||h&&6>h||"palmGetResource"in d&&e&&534>e||-1<f.indexOf("MeeGo")&&-1<f.indexOf("NokiaBrowser/8.5.0")||k&&6>=k)})(jQuery,window);

View File

@@ -1,62 +0,0 @@
//;(function (window, document, $) {
// alert($("#sidebarButton").html());
//}(this, document, jQuery));
//;(function (window, document, $) {
// // Set the negative margin on the top menu for slide-menu pages
// var $selector1 = $('#topMenu'),
// events = 'click.fndtn';
// if ($selector1.length > 0) $selector1.css("margin-top", $selector1.height() * -1);
//
// // Watch for clicks to show the sidebar
// var $selector2 = $('#sidebarButton');
// if ($selector2.length > 0) {
// $('#sidebarButton').on(events, function (e) {
// console.log("testing one two three");
// e.preventDefault();
// $('body').toggleClass('active');
// });
// }
// else {
// console.log("not supposed to be there");
// }
//
// // Watch for clicks to show the menu for slide-menu pages
// var $selector3 = $('#menuButton');
// if ($selector3.length > 0) {
// $('#menuButton').on(events, function (e) {
// e.preventDefault();
// $('body').toggleClass('active-menu');
// });
// }
//
// // // Adjust sidebars and sizes when resized
// // $(window).resize(function() {
// // // if (!navigator.userAgent.match(/Android/i)) $('body').removeClass('active');
// // var $selector4 = $('#topMenu');
// // if ($selector4.length > 0) $selector4.css("margin-top", $selector4.height() * -1);
// // });
//
// // Switch panels for the paneled nav on mobile
// var $selector5 = $('#switchPanels');
// if ($selector5.length > 0) {
// $('#switchPanels dd').on(events, function (e) {
// e.preventDefault();
// var switchToPanel = $(this).children('a').attr('href'),
// switchToIndex = $(switchToPanel).index();
// $(this).toggleClass('active').siblings().removeClass('active');
// $(switchToPanel).parent().css("left", (switchToIndex * (-100) + '%'));
// });
// }
//
// $('#nav li a').on(events, function (e) {
// alert("test");
// e.preventDefault();
// var href = $(this).attr('href'),
// $target = $(href);
// $('html, body').animate({scrollTop : $target.offset().top}, 300);
// });
//}(this, document, jQuery));

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -9,7 +9,5 @@
//= require store/spree_core
//= require store/spree_auth
//= require store/spree_promo
//= require angular
//= require angular-resource
//= require_tree .

View File

@@ -1,8 +0,0 @@
$(document).ready ->
$('#cart_adjustments').hide()
$('th.cart-adjustment-header').html('<a href="#">Distribution...</a>')
$('th.cart-adjustment-header a').click ->
$('#cart_adjustments').toggle()
$('th.cart-adjustment-header a').html('Distribution')
false

View File

@@ -1,20 +0,0 @@
'use strict'
angular.module('store', ['ngResource']).
controller('CartCtrl', ['$scope', '$window', 'CartFactory', ($scope, $window, CartFactory) ->
$scope.state = 'Empty'
$scope.loadCart = (cart_id) ->
if cart_id?
CartFactory.load cart_id, (cart) ->
$scope.cart = cart
if $scope.cart?.orders?.length > 0
$scope.state = "There's something there...."
$scope.addVariant = (variant, quantity) ->
])
.config(['$httpProvider', ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
])

View File

@@ -1,11 +0,0 @@
'use strict'
angular.module('store').
factory('CartFactory', ['$resource', '$window', '$http', ($resource, $window, $http) ->
Cart = $resource '/open_food_network/cart/:cart_id.json', {},
{ 'show': { method: 'GET'} }
load: (id, callback) ->
Cart.show {cart_id: id}, (cart) ->
callback(cart)
])

View File

@@ -1,47 +0,0 @@
/**
* Update the price on the product details page in real time when the variant or the quantity are changed.
**/
$(document).ready(function() {
// Product page with variant choice
$("#product-variants input[type='radio']").change(products_update_price_with_variant);
$("#quantity").change(products_update_price_with_variant);
$("#quantity").change();
// Product page with master price only
$(".add-to-cart input.title:not(#quantity):not(.max_quantity)").change(products_update_price_without_variant).change();
// Product page other
$("#distributor_id").change(function() {
var distributor_html = distributors[$(this).val()];
if(!distributor_html) {
distributor_html = 'When you select a distributor for your order, their address and pickup times will be displayed here.';
}
$("#product-distributor-details .distributor-details").html(distributor_html);
});
});
function products_update_price_with_variant() {
var variant_price = $("#product-variants input[type='radio']:checked").parent().find("span.price").html().trim();
variant_price = variant_price.substr(2, variant_price.length-3);
var quantity = $("#quantity").val();
$("#product-price span.price").html("$"+(parseFloat(variant_price) * parseInt(quantity)).toFixed(2));
}
function products_update_price_without_variant() {
var master_price = $("#product-price span.price").data('master-price');
if(master_price == null) {
// Store off the master price
master_price = $("#product-price span.price").html();
master_price = master_price.substring(1);
$("#product-price span.price").data('master-price', master_price);
}
var quantity = $(this).val();
$("#product-price span.price").html("$"+(parseFloat(master_price)*parseInt(quantity)).toFixed(2));
}

View File

@@ -1,4 +0,0 @@
$(document).ready ->
$("#order_order_cycle_id").change -> $("#order_cycle_select").submit()
$("#reset_order_cycle").click -> return false unless confirm "Changing your collection date will clear your cart."
$(".shop-distributor.empties-cart").click -> return false unless confirm "Changing your location will clear your cart."

View File

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

View File

@@ -1,13 +1,13 @@
.highlight{"ng-class" => "{'has_shopfront' : enterprise.has_shopfront}"}
.highlight{"ng-class" => "{'is_distributor' : enterprise.is_distributor}"}
.highlight-top.row
.small-12.medium-7.large-8.columns
%h3{"ng-if" => "enterprise.has_shopfront"}
%h3{"ng-if" => "enterprise.is_distributor"}
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true}
%i{"ng-class" => "enterprise.icon_font"}
%span {{ enterprise.name }}
%h3{"ng-if" => "!enterprise.has_shopfront", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
%h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "enterprise.icon_font"}
%span {{ enterprise.name }}
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
%p {{ [enterprise.address.city, enterprise.address.state_name] | printArray}}
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}

View File

@@ -1,4 +1,4 @@
.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.has_shopfront"}
.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.is_distributor"}
.cta-container.small-12.columns
%label
Shop for

View File

@@ -1,4 +1,4 @@
.row.pad-top{bindonce: true, ng: { if: 'enterprise.has_shopfront' } }
.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } }
.cta-container.small-12.columns
.row
.small-4.columns

View File

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

View File

@@ -1,44 +1,47 @@
.container#registration-about
.header
%h2 Nice one!
%h5
Now let's flesh out the details about
%span.brick{"ng-show" => "enterprise.is_distributor"}
{{ enterprise.name }}
%span.turquoise{"ng-show" => "!enterprise.is_distributor" }
{{ enterprise.name }}
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 Nice one!
%h5
Now let's flesh out the details about
%span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } }
{{ enterprise.name }}
%form{ name: 'about', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('images',about)" } }
.row
.small-12.columns
.alert-box.alert{"data-alert" => ""}
{{ enterprise.name }} won't be visible on the Open Food Network until you enter a long and short description.
%a.close{:href => "#"} &times;
.alert-box.info{"data-alert" => ""}
{{ enterprise.name }} has been created on the Open Food Network. If you leave at any point from here onwards, your enterprise will be saved, and you can always login to the admin section to update or continue filling out your enterprise details.
%a.close{:href => "#"} &times;
.alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } }
%h6 Success! {{ enterprise.name }} added to the Open Food Network
%span If you exit the wizard at any stage, login and go to admin to edit or update your enterprise details.
%a.close{ ng: { click: "close()" } } &times;
.small-12.large-8.columns
.row
.small-12.columns
%label{ for: 'enterprise_description' } Short Description:
%input.chunky.small-12.columns{ id: 'enterprise_description', placeholder: "A short sentence describing your enterprise", ng: { model: 'enterprise.description' } }
.field
%label{ for: 'enterprise_description' } Short Description:
%input.chunky{ id: 'enterprise_description', placeholder: "A short sentence describing your enterprise", ng: { model: 'enterprise.description' } }
.row
.small-12.columns
%label{ for: 'enterprise_long_desc' } Long Description:
%textarea.chunky.small-12.columns{ id: 'enterprise_long_desc', rows: 6, placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words.", ng: { model: 'enterprise.long_description' } }
%small {{ enterprise.long_description.length }} characters / up to 600 recommended
.field
%label{ for: 'enterprise_long_desc' } Long Description:
%textarea.chunky{ id: 'enterprise_long_desc', rows: 6, placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words.", ng: { model: 'enterprise.long_description' } }
%small {{ enterprise.long_description.length }} characters / up to 600 recommended
.small-12.large-4.columns
.row
.small-12.columns
%label{ for: 'enterprise_abn' } ABN:
%input.chunky.small-12.columns{ id: 'enterprise_abn', placeholder: "eg. 99 123 456 789", ng: { model: 'enterprise.abn' } }
.field
%label{ for: 'enterprise_abn' } ABN:
%input.chunky{ id: 'enterprise_abn', placeholder: "eg. 99 123 456 789", ng: { model: 'enterprise.abn' } }
.row
.small-12.columns
%label{ for: 'enterprise_acn' } ACN:
%input.chunky.small-12.columns{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } }
.field
%label{ for: 'enterprise_acn' } ACN:
%input.chunky{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } }
.row.buttons.pad-top
.small-12.columns
%input.button.primary{ type: "submit", value: "Continue" }
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -1,60 +0,0 @@
.container#registration-address
.header
%h2
Greetings
%span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } }
{{ enterprise.name }}
%h5 Now we need to know where you are
%ng-include{ src: "'registration/steps.html'" }
%form{ name: 'address', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',address)" } }
.row.content
.small-12.medium-12.large-7.columns
.row
.small-12.columns.field
%label{ for: 'enterprise_address' } Address:
%input.chunky.small-12.columns{ id: 'enterprise_address', name: 'address1', required: true, placeholder: "eg. 123 Cranberry Drive", required: true, ng: { model: 'enterprise.address.address1' } }
%span.error.small-12.columns{ ng: { show: "address.address1.$error.required && submitted" } }
You need to enter an address.
.row
.small-12.large-8.columns.field
%label{ for: 'enterprise_city' } Suburb:
%input.chunky.small-12.columns{ id: 'enterprise_city', name: 'city', required: true, placeholder: "eg. Northcote", ng: { model: 'enterprise.address.city' } }
%span.error.small-12.columns{ ng: { show: "address.city.$error.required && submitted" } }
You need to enter a suburb.
.small-12.large-4.columns.field
%label{ for: 'enterprise_zipcode' } Postcode:
%input.chunky.small-12.columns{ id: 'enterprise_zipcode', name: 'zipcode', required: true, placeholder: "eg. 3070", ng: { model: 'enterprise.address.zipcode' } }
%span.error.small-12.columns{ ng: { show: "address.zipcode.$error.required && submitted" } }
You need to enter a postcode.
.row
.small-12.large-8.columns.field
%label{ for: 'enterprise_country' } Country:
%select.chunky.small-12.columns{ id: 'enterprise_country', name: 'country', required: true, ng: { model: 'enterprise.country', options: 'c as c.name for c in countries' } }
%span.error.small-12.columns{ ng: { show: "address.country.$error.required && submitted" } }
You need to enter a country.
.small-12.large-4.columns.field
%label{ for: 'enterprise_state' } State:
%select.chunky.small-12.columns{ id: 'enterprise_state', name: 'state', ng: { model: 'enterprise.address.state_id', options: 's.id as s.abbr for s in enterprise.country.states', show: 'countryHasStates()', required: 'countryHasStates()' } }
%span.error.small-12.columns{ ng: { show: "address.state.$error.required && submitted" } }
You need to enter a state.
.small-12.medium-12.large-5.hide-for-small-only
// This is the location area
/ %h6
/ Location display
/ %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your enterprise's address on the Open Food Network. By default, full location is shown everywhere including street name and number."}
/ .row
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'enterpise_suburb_only', ng: { model: 'enterprise.suburb_only' } }
/ Hide my street name and street number from the public (ie. only show the suburb)
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'enterprise_on_map', ng: { model: 'enterprise.on_map' } }
/ Blur my location on the map (show an approximate, not exact pin)
.row.buttons
.small-12.columns
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('details')" } }
&nbsp;
%input.button.primary{ type: "submit", value: "Continue" }

View File

@@ -1,12 +1,13 @@
.container#registration-contact
.header
%h2 Last step to create your enterprise!
%h5
Who is responsible for managing
%span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } }
{{ enterprise.name }}?
%ng-include{ src: "'registration/steps.html'" }
%form{ name: 'contact', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(contact)" } }
.row
.small-12.columns
%header
%h2 Greetings!
%h5
Who is responsible for managing {{ enterprise.name }}?
%form{ name: 'contact', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('type',contact)" } }
.row.content
.small-12.medium-12.large-7.columns
.row
@@ -39,8 +40,8 @@
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'contact_phone_profile', ng: { model: 'enterprise.phone_in_profile' } } &nbsp; Display phone in profile
.row.buttons
.small-12.columns
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('address')" } }
&nbsp;
%input.button.primary{ type: "submit", value: "Continue" }
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('details')" } }
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -1,42 +1,77 @@
.container#registration-details{bindonce: true}
.header
%h2 Let's Get Started
%h5{ bo: { if: "enterprise.type != 'single'" } } Woot! First we need to know what sort of enterprise you are:
%h5{ bo: { if: "enterprise.type == 'single'" } } Woot! First we need to know the name of your farm:
%ng-include{ src: "'registration/steps.html'" }
%form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('address',details)" } }
.row
.small-12.columns.field
%label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'single'" } } Enterprise Name:
%label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'single'" } } Farm Name:
%input.chunky.small-12.columns{ id: 'enterprise_name', name: 'name', placeholder: "eg. Charlie's Awesome Farm", required: true, ng: { model: 'enterprise.name' } }
%span.error.small-12.columns{ ng: { show: "details.name.$error.required && submitted" } }
You need to enter a name for your enterprise!
.row
.small-12.columns
%header
%h2 Let's Get Started
%h5{ bo: { if: "enterprise.type != 'own'" } } Woot! First we need to know a little bit about your enterprise:
%h5{ bo: { if: "enterprise.type == 'own'" } } Woot! First we need to know a little bit about your farm:
.row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'single'" } }
.small-12.columns.field
%form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } }
.row
.small-12.medium-9.large-12.columns.end
.field
%label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } Enterprise Name:
%label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } Farm Name:
%input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "e.g. Charlie's Awesome Farm", required: true, ng: { model: 'enterprise.name' } }
%span.error{ ng: { show: "details.name.$error.required && submitted" } }
Please choose a unique name for your enterprise
.row
.small-12.medium-9.large-6.columns
.field
%label{ for: 'enterprise_address' } Address line 1:
%input.chunky{ id: 'enterprise_address', name: 'address1', required: true, placeholder: "e.g. 123 Cranberry Drive", required: true, ng: { model: 'enterprise.address.address1' } }
%span.error{ ng: { show: "details.address1.$error.required && submitted" } }
Please enter an address
.field
%label{ for: 'enterprise_address2' } Address line 2:
%input.chunky{ id: 'enterprise_address2', name: 'address2', required: false, placeholder: "", required: false, ng: { model: 'enterprise.address.address2' } }
.small-12.medium-9.large-6.columns.end
.row
.small-12.columns
%label Choose one:
.small-12.medium-8.large-8.columns
.field
%label{ for: 'enterprise_city' } Suburb:
%input.chunky{ id: 'enterprise_city', name: 'city', required: true, placeholder: "e.g. Northcote", ng: { model: 'enterprise.address.city' } }
%span.error{ ng: { show: "details.city.$error.required && submitted" } }
Please enter a suburb
.small-12.medium-4.large-4.columns
.field
%label{ for: 'enterprise_zipcode' } Postcode:
%input.chunky{ id: 'enterprise_zipcode', name: 'zipcode', required: true, placeholder: "e.g. 3070", ng: { model: 'enterprise.address.zipcode' } }
%span.error{ ng: { show: "details.zipcode.$error.required && submitted" } }
Postcode required
.row
.small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true }
%a.panel#producer-panel{ href: "#", ng: { click: "enterprise.is_distributor = false; enterprise.is_primary_producer = true", class: "{selected: (!enterprise.is_distributor && enterprise.is_primary_producer)}" } }
.left
/ %render-svg{ path: "/assets/map-icon-producer.svg" }
%h4 I'm A Producer
%p Producers make yummy things to eat &amp;/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it.
.small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true }
%a.panel#hub-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = false", class: "{selected: (enterprise.is_distributor && !enterprise.is_primary_producer)}" } }
.left
/ %render-svg{ path: "/assets/map-icon-hub.svg" }
%h4 I'm A Hub
%p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc.
.small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true }
%a.panel#both-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = true", class: "{selected: (enterprise.is_distributor && enterprise.is_primary_producer)}" } }
.left
/ %render-svg{path: "/assets/map-icon-both.svg"}
%h4 I'm Both
%p Hey there, Jack-of-all-trades! Not only do you produce things to eat &amp;/or drink, you also want to sell your yummies through an Open Food Network shopfront.
.small-12.medium-4.large-4.columns
.field
%label{ for: 'enterprise_state' } State:
%select.chunky{ id: 'enterprise_state', name: 'state', ng: { model: 'enterprise.address.state_id', options: 's.id as s.abbr for s in enterprise.country.states', show: 'countryHasStates()', required: 'countryHasStates()' } }
%span.error{ ng: { show: "details.state.$error.required && submitted" } }
State required
.small-12.medium-8.large-8.columns
.field
%label{ for: 'enterprise_country' } Country:
%select.chunky{ id: 'enterprise_country', name: 'country', required: true, ng: { model: 'enterprise.country', options: 'c as c.name for c in countries' } }
%span.error{ ng: { show: "details.country.$error.required && submitted" } }
Please select a country
/ .small-12.medium-12.large-5.hide-for-small-only
/ %h6
/ Location display
/ %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your enterprise's address on the Open Food Network. By default, full location is shown everywhere including street name and number."}
/ .row
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'enterpise_suburb_only', ng: { model: 'enterprise.suburb_only' } }
/ Hide my street name and street number from the public (ie. only show the suburb)
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'enterprise_on_map', ng: { model: 'enterprise.on_map' } }
/ Blur my location on the map (show an approximate, not exact pin)
.row.buttons
.small-12.columns
%hr
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -1,18 +1,24 @@
.container#registration-finished
.header
%h2 Well done!
%h5
You have successfully completed the profile for
%span.brick{"ng-show" => "enterprise.is_distributor"}
{{ enterprise.name }}
%span.turquoise{"ng-show" => "!enterprise.is_distributor" }
{{ enterprise.name }}
.content{ style: 'text-align: center'}
%h3 Why not check it out on the Open Food Network?
%a.button.primary{ type: "button", href: "/map" } Go to Map Page &gt;
.row
.small-12.columns.pad-top
%header
%h2 Finished!
.panel.callout
%p
Thanks for filling out the details for
%span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } }
{{ enterprise.name }}
%p You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin.
.row
.small-12.columns.text-center
%h4
Activate
%span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } }
{{ enterprise.name }}
%br
%br
%p
We've sent a confirmation email to
%strong {{ enterprise.email }}.
%br Please follow the instructions there to make your enterprise visible on the Open Food Network.
%h3 Next step - add some products:
%a.button.primary{ type: "button", href: "/admin/products/new" } Add a Product &gt;
%a.button.primary{ type: "button", href: "/" } Open Food Network home &gt;

View File

@@ -1,20 +1,22 @@
.container#registration-images{ 'nv-file-drop' => true, uploader: "imageUploader", options:"{ alias: imageStep }", ng: { controller: "EnterpriseImageCtrl" } }
.header
%h2 Thanks!
%h5 Let's upload some pretty pictures so your profile looks great! :)
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 Thanks!
%h5 Let's upload some pretty pictures so your profile looks great! :)
%form{ name: 'images', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "select('social')" } }
.row{ ng: { repeat: 'image_step in imageSteps', show: "imageStep == image_step" } }
%ng-include{ src: "'registration/images/'+ image_step + '.html'" }
.row.buttons.pad-top{ ng: { if: "imageStep == 'logo'" } }
.small-12.columns
%input.button.primary{ type: "button", value: "Back", ng: { click: "select('about')" } }
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('about')" } }
&nbsp;
%input.button.primary{ type: "button", value: "Continue", ng: { click: "imageSelect('promo')" } }
%input.button.primary.right{ type: "button", value: "Continue", ng: { click: "imageSelect('promo')" } }
.row.buttons.pad-top{ ng: { if: "imageStep == 'promo'" } }
.small-12.columns
%input.button.primary{ type: "button", value: "Back", ng: { click: "imageSelect('logo')" } }
&nbsp;
%input.button.primary{ type: "submit", value: "Continue" }
%input.button.secondary{ type: "button", value: "Back", ng: { click: "imageSelect('logo')" } }
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -36,6 +36,10 @@
.row.pad-top
.small-12.columns.center
#image-placeholder.logo
%img{ ng: { show: "imageSrc()", src: '{{ imageSrc() }}' } }
.message{ ng: { hide: "imageSrc()" } }
%img{ ng: { show: "imageSrc() && !imageUploader.isUploading", src: '{{ imageSrc() }}' } }
.message{ ng: { hide: "imageSrc() || imageUploader.isUploading" } }
Your logo will appear here for review once uploaded
.loading{ ng: { hide: "!imageUploader.isUploading" } }
%img.spinner{ src: "/assets/loading.gif" }
%br/
Uploading...

View File

@@ -34,6 +34,10 @@
.row.pad-top
.small-12.columns.center
#image-placeholder.promo
%img{ ng: { show: "imageSrc()", src: '{{ imageSrc() }}' } }
.message{ ng: { hide: "imageSrc()" } }
%img{ ng: { show: "imageSrc() && !imageUploader.isUploading", src: '{{ imageSrc() }}' } }
.message{ ng: { hide: "imageSrc() || imageUploader.isUploading" } }
Your logo will appear here for review once uploaded
.loading{ ng: { hide: "!imageUploader.isUploading" } }
%img.spinner{ src: "/assets/loading.gif" }
%br/
Uploading...

View File

@@ -1,39 +1,45 @@
%div
.header
%h2 Hi there!
%h4 This wizard will step you through creating a profile
.row
.small-12.medium-3.large-2.columns.text-right.hide-for-small-only
%img{:src => "/assets/potatoes.png"}
.small-12.medium-9.large-10.columns
%p
Your profile gives you an online presence on the
%strong Open Food Network,
allowing you to easily connect with potential customers or partners. You can always choose to update your info later, as well as choose to upgrade your Profile to and Online Store, where you can sell products, track orders and receive payments. Creating a profile takes about 5-10 minutes.
.row{ 'data-equalizer' => true }
.small-12.medium-6.large-6.columns.pad-top{ 'data-equalizer-watch' => true }
%h5 You'll need the following:
%ul.check-list
%li
Your enterprise address and contact details
%li
Your logo image
%li
A pretty picture for your profile header
%li
Some 'About Us' text
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true}
.highlight-box
%h5 Your profile entitles you to:
%ul.small-block-grid-1
%li
%i.ofn-i_020-search
A searchable listing
%li
%i.ofn-i_040-hub
A pin on the OFN map
.row
.small-12.columns
.row
.small-12.columns
%header
%h2 Hi there!
%h4
%small
%i.ofn-i_040-hub
Create your enterprise profile
.hide-for-large-up
%hr
%input.button.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } }
%input.button.small.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } }
%hr
.row{ 'data-equalizer' => true }
.small-12.medium-12.large-6.columns.pad-top{ 'data-equalizer-watch' => true }
%h5 You'll need:
%ul.check-list
%li
5-10 minutes
%li
Enterprise address
%li
Primary contact details
%li
Your logo image
%li
Landscape image for your profile
%li
'About Us' text
.small-9.medium-8.large-5.columns.pad-top.end{ 'data-equalizer-watch' => true}
%h5
What do I get?
%p
Your profile helps people
%strong find
and
%strong contact
you on the Open Food Network.
%p Use this space to tell the story of your enterprise, to help drive connections to your social and online presence.
.row.show-for-large-up
.small-12.columns
%hr
%input.button.primary.right{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } }

View File

@@ -1,15 +1,16 @@
%div
.header.center
%h2 Oh no!
%h4 You have reached the limit!
.row
.small-12.medium-3.large-2.columns.text-right.hide-for-small-only
%img{:src => "/assets/potatoes.png"}
.small-12.medium-9.large-10.columns
%p
You have reached the limit for the number of enterprises you are allowed to own on the
%strong Open Food Network.
.row
.small-12.columns
%hr
%input.button.primary{ type: "button", value: "Return to the homepage", ng: { click: "close()" } }
.row
.small-12.columns
%header
%h2 Oh no!
%h4 You have reached the limit!
.row
.small-12.medium-3.large-2.columns.text-right.hide-for-small-only
%img{:src => "/assets/potatoes.png"}
.small-12.medium-9.large-10.columns
%p
You have reached the limit for the number of enterprises you are allowed to own on the
%strong Open Food Network.
.row
.small-12.columns
%hr
%input.button.primary{ type: "button", value: "Return to the homepage", ng: { click: "close()" } }

View File

@@ -1,35 +1,49 @@
.container#registration-social
.header
%h2 Last step!
%h5 How can people find {{ enterprise.name }} online?
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 Final step!
%h5
How can people find
%span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } }
{{ enterprise.name }}
online?
%form{ name: 'social', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('finished',social)" } }
.row.content
.small-12.large-7.columns
.row
.small-12.columns
%label{ for: 'enterprise_website' } Website:
%input.chunky.small-12.columns{ id: 'enterprise_website', placeholder: "eg. openfoodnetwork.org.au", ng: { model: 'enterprise.website' } }
.field
%label{ for: 'enterprise_website' } Website:
%input.chunky{ id: 'enterprise_website', placeholder: "eg. openfoodnetwork.org.au", ng: { model: 'enterprise.website' } }
.row
.small-12.columns
%label{ for: 'enterprise_facebook' } Facebook:
%input.chunky.small-12.columns{ id: 'enterprise_facebook', placeholder: "eg. www.facebook.com/PageNameHere", ng: { model: 'enterprise.facebook' } }
.field
%label{ for: 'enterprise_facebook' } Facebook:
%input.chunky{ id: 'enterprise_facebook', placeholder: "eg. www.facebook.com/PageNameHere", ng: { model: 'enterprise.facebook' } }
.row
.small-12.columns
%label{ for: 'enterprise_linkedin' } LinkedIn:
%input.chunky.small-12.columns{ id: 'enterprise_linkedin', placeholder: "eg. www.linkedin.com/YourNameHere", ng: { model: 'enterprise.linkedin' } }
.field
%label{ for: 'enterprise_linkedin' } LinkedIn:
%input.chunky{ id: 'enterprise_linkedin', placeholder: "eg. www.linkedin.com/YourNameHere", ng: { model: 'enterprise.linkedin' } }
.small-12.large-5.columns
.row
.small-12.columns
%label{ for: 'enterprise_twitter' } Twitter:
%input.chunky.small-12.columns{ id: 'enterprise_twitter', placeholder: "eg. @twitter_handle", ng: { model: 'enterprise.twitter' } }
.field
%label{ for: 'enterprise_twitter' } Twitter:
%input.chunky{ id: 'enterprise_twitter', placeholder: "eg. @twitter_handle", ng: { model: 'enterprise.twitter' } }
.row
.small-12.columns
%label{ for: 'enterprise_instagram' } Instagram:
%input.chunky.small-12.columns{ id: 'enterprise_instagram', placeholder: "eg. @instagram_handle", ng: { model: 'enterprise.instagram' } }
.field
%label{ for: 'enterprise_instagram' } Instagram:
%input.chunky{ id: 'enterprise_instagram', placeholder: "eg. @instagram_handle", ng: { model: 'enterprise.instagram' } }
.row.buttons
.small-12.columns
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('images')" } }
&nbsp;
%input.button.primary{ type: "submit", value: "Continue" }
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -1,5 +1,3 @@
.row#progress-bar
.small-12.medium-2.columns.item{ ng: { repeat: 'step in steps', class: "{active: (currentStep() == step),'show-for-medium-up': (currentStep() != step)}" } }
{{ $index+1 + ". " + step }}

View File

@@ -0,0 +1,46 @@
.container#registration-type{bindonce: true}
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2
Last step to add
%span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } }
{{ enterprise.name }}!
%h4
Are you a producer?
%form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } }
.row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } }
.small-12.columns.field
.row
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true }
%a.btnpanel#producer-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = true", class: "{selected: enterprise.is_primary_producer}" } }
%i.ofn-i_059-producer
%h4 Yes, I'm a producer
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true }
%a.btnpanel#hub-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = false", class: "{selected: enterprise.is_primary_producer == false}" } }
%i.ofn-i_063-hub
%h4 No, I'm not a producer
.row
.small-12.columns
%input.chunky{ id: 'enterprise_is_primary_producer', name: 'is_primary_producer', hidden: true, required: true, ng: { model: 'enterprise.is_primary_producer' } }
%span.error{ ng: { show: "type.is_primary_producer.$error.required && submitted" } }
Please choose one. Are you are producer?
.row
.small-12.columns
.panel.callout
.left
%i.ofn-i_013-help
&nbsp;
%p Producers make yummy things to eat &amp;/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it.
/ %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc.
.row.buttons
.small-12.columns
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('contact')" } }
%input.button.primary.right{ type: "submit", value: "Continue" }

View File

@@ -0,0 +1,15 @@
.alert
border: 3px solid #919191
border-radius: 6px
margin-bottom: 20px
color: #919191
padding: 5px 10px
h6
color: #919191
.message
font-weight: bold
&:hover
border-color: #DA5354
color: #DA5354
h6
color: #DA5354

View File

@@ -32,7 +32,7 @@
span
text-decoration: underline
&.has_shopfront, &.has_shopfront i.ofn-i_059-producer, &.has_shopfront i.ofn-i_060-producer-reversed
&.is_distributor, &.is_distributor i.ofn-i_059-producer, &.is_distributor i.ofn-i_060-producer-reversed
color: $clr-brick
&:hover, &:active, &:focus
color: $clr-brick-bright

View File

@@ -2,44 +2,25 @@
@import mixins
#registration-modal
.header
header
text-align: center
background-color: #efefef
padding-bottom: 1rem
// background-color: #efefef
@media all and (max-width: 64em)
text-align: left
.container
background-color: #ffffff
.content
// margin-bottom: 15px
i
font-size: 150%
.buttons
ofn-inline-flash
display: block
padding: 15px
position: relative
margin-bottom: 10px
&.brick
background-color: $clr-brick-light
border: 2px solid $clr-brick
color: $clr-brick
&.turquoise
background-color: $clr-turquoise-light
border: 2px solid $clr-turquoise
color: $clr-turquoise
.close-button
position: absolute
top: 0px
right: 0px
.field
margin-bottom: 15px
margin-bottom: 1em
input.chunky
.chunky
padding: 8px
font-size: 105%
font-size: 1rem
margin: 0
width: 100%
label.indent-checkbox
display: block
@@ -51,9 +32,9 @@
label
margin-bottom: 3px
ol, ul
// font-size: 80%
ol, ul, p
font-size: 0.875rem
ol, ul
padding: 0
margin: 0
ol
@@ -62,22 +43,24 @@
.highlight-box
background: white
padding: 1rem 1.2rem
@media all and (max-width: 640px)
@media all and (max-width: 64em)
margin-top: 1rem
#progress-bar
margin-bottom: 15px
.item
padding: 12px 0px
font-size: 0.75rem
padding: 10px 0px
text-transform: uppercase
text-align: center
background-color: #333
border: 2px solid #333
background-color: $clr-blue
border: 2px solid $clr-blue
color: #fff
.item.active
background-color: #cccccc
border: 2px solid #333
color: #333
background-color: $disabled-light
border: 2px solid $clr-blue
color: $clr-blue
font-weight: 700
@include box-shadow(inset 0 0 1px 0 #fff)
@@ -110,39 +93,57 @@
font-size: 18px
font-weight: bold
color: #373737
background-color: #e1e1e1
background-color: #f1f1f1
text-align: center
border: 3px dashed #494949
margin-left: auto
margin-right: auto
.spinner
width: 100px
&.logo
.message
padding-top: 6em
.loading
padding-top: 4em
width: 306px
height: 306px
&.promo
.message
padding-top: 4em
.loading
padding-top: 1em
width: 726px
height: 166px
#registration-details
#registration-type
#enterprise-types
a.panel
a.btnpanel
display: block
padding: 1rem
margin-bottom: 1rem
background-color: #efefef
color: black
@media all and (min-width: 768px)
min-height: 200px
text-align: center
border: 1px solid transparent
i
font-size: 3rem
h4
margin-top: 1rem
&:hover
background-color: #fff
&#producer-panel:hover
border: 1px solid $clr-turquoise
&, & *
color: $clr-turquoise
&#hub-panel:hover, &#both-panel:hover
border: 1px solid $clr-brick
&, & *
color: $clr-brick
&.selected
&, & *
color: #fff

View File

@@ -74,8 +74,10 @@ button.success, .button.success
.profile-checkbox
display: inline-block
input[type="checkbox"] + label
label
margin: 0 0.2rem
float: right
// Responsive
@media screen and (min-width: 768px)

View File

@@ -1,3 +0,0 @@
// Place all the styles related to the distributors controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@@ -1,3 +0,0 @@
// Place all the styles related to the groups controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@@ -1,3 +0,0 @@
// Place all the styles related to the Shop::Checkout controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@@ -1,381 +0,0 @@
@import "screen";
@import "compass/css3/border-radius";
/* General purpose styles */
a:hover {
color: lighten($link_text_color, 20) !important;
}
/* Cleared div for clearing previous floating elements */
div.cleared {
clear: both;
}
p.hint {
margin: 0 0 0.5em 0;
padding: 0;
color: #6a6a6a;
}
table {
tbody, tfoot {
tr {
&.alt, &.odd {
background-color: lighten($link_text_color, 75) !important;
}
}
}
}
/* Style current distributor in main nav bar */
#header {
margin-bottom: 40px;
#logo {
position: relative;
margin-top: 40px;
padding-top: 10px;
h1 {
position: absolute;
top: 10px;
left: 0px;
}
.change-location {
position: absolute;
top: 22px;
right: -104px;
}
}
}
nav #main-nav-bar {
position: absolute;
top: 0px;
left: 10px;
width: 100%;
#link-to-cart {
position: relative;
top: 40px;
right: 16px;
}
li {
&#current-distribution {
float: right;
clear: right;
margin: 0.5em 5px 0 0;
a {
font-size: 12px;
padding: 0;
}
}
}
}
nav#top-nav-bar {
position: relative;
z-index: 999;
margin-top: 20px;
}
/* Based on Spree's nav#taxonomies style. Copied instead of
* extended with SASS because SASS does not allow extending
* nested selectors.
*/
nav#filters {
.filter_name {
text-transform: uppercase;
border-bottom: 1px solid lighten($body_text_color, 60);
margin-bottom: 5px;
font-size: $main_navigation_header_font_size;
}
.filter_choices {
padding-left: 20px;
margin-bottom: 5px;
list-style: disc outside;
li {
a {
font-size: $main_navigation_font_size;
}
span.inactive {
color: #999;
}
}
}
input[type=submit] {
margin-bottom: 15px;
}
}
/* Distributor and order cycle selection and display */
#distribution-choice {
margin-bottom: 2em;
padding: 5px;
border: 2px solid #ccc;
@include border-radius(10px);
}
#distribution-selection {
overflow: auto;
margin-bottom: 2em;
padding: 20px;
background-color: #f3f3f3;
.distributors {
float: left;
margin-right: 4em;
option.local {
background-color: #cfc;
}
option.remote {
background-color: #fcc;
}
}
.order-cycles {
select {
padding: 10px;
margin-top: 15px;
border: 2px solid white;
width: 100%;
}
float: left;
tr.local {
background-color: #cfc;
}
tr.remote {
background-color: #fcc;
}
}
}
.countdown-panel {
background: url("../countdown.png") no-repeat left;
min-height: 60px;
min-width: 70px;
padding-left: 77px;
}
/* Style the product source on the product details page in the
* same manner as the product properties table above it.
*/
#product-source {
@extend #product-properties;
}
#product-properties td, #product-source td {
width: 50%;
}
/* Apply Spree's ul#products style to ul.product-listing. When viewing products
* split by distributor, a separate product listing is displayed for local and
* remote products, so the #products id is removed to avoid its duplication.
*/
ul.product-listing {
&:after {
content: " ";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0;
}
li {
text-align: center;
font-weight: bold;
margin-bottom: 20px;
a {
display: block;
&.info {
height: 35px;
margin-top: 5px;
font-size: $product_list_name_font_size;
color: $product_link_text_color;
border-bottom: 1px solid lighten($body_text_color, 60);
overflow: hidden;
}
}
.product-image {
border: 1px solid lighten($body_text_color, 60);
padding: 5px;
min-height: 110px;
background-color: $product_background_color;
&:hover {
border-color: $link_text_color;
}
}
.price {
color: $link_text_color;
font-size: $product_list_price_font_size;
padding-top: 5px;
display: block;
}
}
}
/* Enterprise description */
.enterprise-description {
margin-bottom: 2em;
}
/* Supplier page distributor listing */
#supplier-distributors li a.inactive {
color: #999;
}
/* Highlight local products in distributor-split product listings */
#products-local ul {
margin-bottom: 1em;
padding: 10px;
@include border-radius(10px);
background-color: #def;
}
/* Distributor details on product details page */
fieldset#product-distributor-details {
float: right;
margin-top: 0;
width: 250px;
@extend #shipping;
}
#product-variants {
float: none;
ul {
list-style-type: none;
}
}
/* Add to cart form on product details page */
#cart-form {
.error-distributor {
font-size: 120%;
font-weight: bold;
color: #f00;
a {
color: #f00;
text-decoration: underline;
}
}
.distributor-fixed {
}
}
/* View cart form */
#subtotal {
width: 100%;
}
.links {
float: right;
text-align: right;
}
#empty-cart {
float: left;
margin-top: 15px !important;
p {
padding: 0;
}
}
/* Checkout address page */
#checkout .alternative-available-distributors {
padding-top: 30px;
}
/* Delivery fees table on checkout page */
#delivery-fees {
clear: both;
padding-top: 6em;
table#delivery {
width: 100%;
tbody {
tr {
td.item-shipping-cost, td.item-shipping-method {
text-align: center;
}
td.item-shipping-cost {
@extend span.price;
@extend span.price.selling;
}
}
}
}
.subtotal {
width: 100%;
text-align: right;
text-transform: uppercase;
margin-top: 15px;
span.order-total {
@extend span.price;
}
}
}
/* Alert for EFT Payment during checkout process */
div#eft-payment-alert {
border: 2px solid red;
padding-left: 5px;
}
/* Large 'Save and Continue/Process My Order' button under Order Summary on the checkout pages */
#add_new_save_checkout_button {
display: none;
margin-top: 30px;
padding-top: 10px;
border-top: 1px solid #d9d9db;
}
.secondary {
color: #6A6A6A;
}
.hide {
display: none;
}
/* Distributor details */
.distributor-details .next-collection-at {
font-size: 20px;
font-weight: bold;
color: #de790c;
}

View File

@@ -1,64 +0,0 @@
/*--------------------------------------*/
/* Colors
/*--------------------------------------*/
$c_green: #8dba53 !default; /* Spree green */
$c_red: #e45353 !default; /* Error red */
$layout_background_color: #FFFFFF !default;
$title_text_color: #404042 !default;
$body_text_color: #404042 !default;
$link_text_color: #00ADEE !default;
$product_background_color: #FFFFFF !default;
$product_title_text_color: #404042 !default;
$product_body_text_color: #404042 !default;
$product_link_text_color: #BBBBBB !default;
/*--------------------------------------*/
/* Fonts import from remote
/*--------------------------------------*/
@import url(//fonts.googleapis.com/css?family=Ubuntu:400,700,400italic,700italic|&subset=latin,cyrillic,greek,greek-ext,latin-ext,cyrillic-ext);
/*--------------------------------------*/
/* Font families
/*--------------------------------------*/
$ff_base: 'Ubuntu', sans-serif !default;
/*--------------------------------------
| Font sizes
|--------------------------------------
|- Navigation
| */
$header_navigation_font_size: 14px !default;
$horizontal_navigation_font_size: 16px !default;
$main_navigation_header_font_size: 14px !default;
$main_navigation_font_size: 12px !default;
/*|------------------------------------
|- Product Listing
| */
$product_list_name_font_size: 12px !default;
$product_list_price_font_size: 16px !default;
$product_list_header_font_size: 20px !default;
$product_list_search_font_size: 14px !default;
/*|------------------------------------
|- Product Details
| */
$product_detail_name_font_size: 24px !default;
$product_detail_description_font_size: 12px !default;
$product_detail_price_font_size: 20px !default;
$product_detail_title_font_size: 14px !default;
/*|------------------------------------
|- Basic
| */
$heading_font_size: 24px !default;
$sub_heading_font_size: 14px !default;
$button_font_size: 12px !default;
$input_box_font_size: 13px !default;
$base_font_size: 12px !default;
$border_color: lighten($body_text_color, 60) !default;
$default_border: 1px solid $border_color !default;
$button_border_color: rgba(0, 138, 189, .75) !default;
$table_head_color: lighten($body_text_color, 60) !default;
@import "./store/variables_changes.css.scss";

View File

@@ -1 +0,0 @@
$link_text_color: #006066;

View File

@@ -3,11 +3,11 @@ module Admin
before_filter :load_enterprise_set, :only => :index
before_filter :load_countries, :except => :index
before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create]
before_filter :check_type, only: :update
before_filter :check_bulk_type, only: :bulk_update
before_filter :check_can_change_sells, only: :update
before_filter :check_can_change_bulk_sells, only: :bulk_update
before_filter :override_owner, only: :create
before_filter :check_owner, only: :update
before_filter :check_bulk_owner, only: :bulk_update
before_filter :check_can_change_owner, only: :update
before_filter :check_can_change_bulk_owner, only: :bulk_update
helper 'spree/products'
include OrderCyclesHelper
@@ -50,7 +50,8 @@ module Admin
end
def collection
Enterprise.managed_by(spree_current_user).order('is_distributor DESC, is_primary_producer ASC, name')
# TODO was ordered with is_distributor DESC as well, not sure why or how we want ot sort this now
Enterprise.managed_by(spree_current_user).order('is_primary_producer ASC, name')
end
def collection_actions
@@ -63,29 +64,29 @@ module Admin
@enterprise_fees = EnterpriseFee.managed_by(spree_current_user).for_enterprise(@enterprise).order(:fee_type, :name).all
end
def check_bulk_type
def check_can_change_bulk_sells
unless spree_current_user.admin?
params[:enterprise_set][:collection_attributes].each do |i, enterprise_params|
enterprise_params.delete :type
enterprise_params.delete :sells
end
end
end
def check_type
params[:enterprise].delete :type unless spree_current_user.admin?
def check_can_change_sells
params[:enterprise].delete :sells unless spree_current_user.admin?
end
def override_owner
params[:enterprise][:owner_id] = spree_current_user.id unless spree_current_user.admin?
end
def check_owner
def check_can_change_owner
unless ( spree_current_user == @enterprise.owner ) || spree_current_user.admin?
params[:enterprise].delete :owner_id
end
end
def check_bulk_owner
def check_can_change_bulk_owner
unless spree_current_user.admin?
params[:enterprise_set][:collection_attributes].each do |i, enterprise_params|
enterprise_params.delete :owner_id

View File

@@ -0,0 +1,7 @@
Devise::ConfirmationsController.class_eval do
protected
# Override of devise method in Devise::ConfirmationsController
def after_confirmation_path_for(resource_name, resource)
spree.admin_path
end
end

View File

@@ -2,17 +2,12 @@ require 'open_food_network/spree_api_key_loader'
class RegistrationController < BaseController
include OpenFoodNetwork::SpreeApiKeyLoader
before_filter :load_spree_api_key, only: [:index, :store]
before_filter :load_spree_api_key, only: [:index]
before_filter :check_user, except: :authenticate
layout 'registration'
def index
@enterprise_attributes = { type: 'profile' }
end
def store
@enterprise_attributes = { is_distributor: true, is_primary_producer: true, type: 'single' }
render :index
@enterprise_attributes = { sells: 'none' }
end
private

View File

@@ -0,0 +1,11 @@
Spree::Admin::ImageSettingsController.class_eval do
# Spree stores attachent definitions in JSON. This converts the style name and format to
# strings. However, when paperclip encounters these, it doesn't recognise the format.
# Here we solve that problem by converting format and style name to symbols.
def update_paperclip_settings_with_format_styles
update_paperclip_settings_without_format_styles
Spree::Image.reformat_styles
end
alias_method_chain :update_paperclip_settings, :format_styles
end

View File

@@ -1,6 +1,7 @@
Spree::Admin::OverviewController.class_eval do
def index
@enterprises = Enterprise.managed_by(spree_current_user).order('is_distributor DESC, is_primary_producer ASC, name')
# TODO was sorted with is_distributor DESC as well, not sure why or how we want ot sort this now
@enterprises = Enterprise.managed_by(spree_current_user).order('is_primary_producer ASC, name')
@product_count = Spree::Product.active.managed_by(spree_current_user).count
@order_cycle_count = OrderCycle.active.managed_by(spree_current_user).count
end

View File

@@ -6,24 +6,6 @@ require 'open_food_network/order_grouper'
require 'open_food_network/customers_report'
Spree::Admin::ReportsController.class_eval do
# Fetches user's distributors, suppliers and order_cycles
before_filter :load_data, only: [:customers, :products_and_inventory]
# Render a partial for orders and fulfillment description
respond_override :index => { :html => { :success => lambda {
@reports[:orders_and_fulfillment][:description] =
render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe
@reports[:products_and_inventory][:description] =
render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe
@reports[:customers][:description] =
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
} } }
# OVERRIDING THIS so we use a method not a constant for available reports
def index
@reports = available_reports
respond_with(@reports)
end
REPORT_TYPES = {
orders_and_fulfillment: [
@@ -42,6 +24,26 @@ Spree::Admin::ReportsController.class_eval do
]
}
# Fetches user's distributors, suppliers and order_cycles
before_filter :load_data, only: [:customers, :products_and_inventory]
# Render a partial for orders and fulfillment description
respond_override :index => { :html => { :success => lambda {
@reports[:orders_and_fulfillment][:description] =
render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe
@reports[:products_and_inventory][:description] =
render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe
@reports[:customers][:description] =
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
} } }
# Overide spree reports list.
def index
@reports = authorized_reports
respond_with(@reports)
end
# This action is short because we refactored it like bosses
def customers
@report_types = REPORT_TYPES[:customers]
@@ -592,28 +594,29 @@ Spree::Admin::ReportsController.class_eval do
private
def load_data
# Load distributors either owned by the user or selling their enterprises products.
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
@distributors = my_distributors | distributors_of_my_products
# Load suppliers either owned by the user or supplying products their enterprises distribute.
suppliers_of_products_I_distribute = my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
@suppliers = my_suppliers | suppliers_of_products_I_distribute
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
end
def available_reports
def authorized_reports
reports = {
:orders_and_distributors => {:name => "Orders And Distributors", :description => "Orders with distributor details"},
:bulk_coop => {:name => "Bulk Co-Op", :description => "Reports for Bulk Co-Op orders"},
:payments => {:name => "Payment Reports", :description => "Reports for Payments"},
:orders_and_fulfillment => {:name => "Orders & Fulfillment Reports", :description => ''},
:customers => {:name => "Customers", :description => 'Customer details'},
:products_and_inventory => {:name => "Products & Inventory", :description => ''}
:products_and_inventory => {:name => "Products & Inventory", :description => ''},
:sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" }
}
if spree_current_user.has_spree_role? 'admin'
reports[:sales_total] = { :name => "Sales Total", :description => "Sales Total For All Orders" }
end
reports
# Return only reports the user is authorized to view.
reports.select { |action| can? action, :report }
end
def total_units(line_items)

View File

@@ -0,0 +1,21 @@
module Admin
module ImageSettingsHelper
def admin_image_settings_format_options
[['Unchanged', ''], ['PNG', 'png'], ['JPEG', 'jpg']]
end
def admin_image_settings_geometry_from_style(style)
geometry, format = admin_image_settings_split_style style
geometry
end
def admin_image_settings_format_from_style(style)
geometry, format = admin_image_settings_split_style style
format
end
def admin_image_settings_split_style(style)
[style, nil].flatten[0..1]
end
end
end

View File

@@ -1,8 +1,16 @@
require 'devise/mailers/helpers'
class EnterpriseMailer < Spree::BaseMailer
def creation_confirmation(enterprise)
find_enterprise(enterprise)
subject = "#{@enterprise.name} is now on #{Spree::Config[:site_name]}"
mail(:to => @enterprise.owner.email, :from => from_address, :subject => subject)
include Devise::Mailers::Helpers
def confirmation_instructions(record, token, opts={})
@token = token
find_enterprise(record)
opts = {
subject: "Please confirm your email for #{@enterprise.name}",
to: [ @enterprise.owner.email, @enterprise.email ].uniq,
from: from_address,
}
devise_mail(record, :confirmation_instructions, opts)
end
private

View File

@@ -1,5 +1,5 @@
Spree::UserMailer.class_eval do
def signup_confirmation(user)
@user = user
mail(:to => user.email, :from => from_address,

View File

@@ -1,12 +1,14 @@
class Enterprise < ActiveRecord::Base
TYPES = %w(full single profile)
SELLS = %w(none own any)
ENTERPRISE_SEARCH_RADIUS = 100
devise :confirmable, reconfirmable: true
self.inheritance_column = nil
acts_as_gmappable :process_geocoding => false
after_create :send_creation_email
before_create :check_email
has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup'
has_many :producer_properties, foreign_key: 'producer_id'
@@ -34,7 +36,7 @@ class Enterprise < ActiveRecord::Base
path: 'public/images/enterprises/logos/:id/:style/:basename.:extension'
has_attached_file :promo_image,
styles: { large: "1200x260#", medium: "720x156#", thumb: "100x100>" },
styles: { large: ["1200x260#", :jpg], medium: ["720x156#", :jpg], thumb: ["100x100>", :jpg] },
url: '/images/enterprises/promo_images/:id/:style/:basename.:extension',
path: 'public/images/enterprises/promo_images/:id/:style/:basename.:extension'
@@ -47,11 +49,12 @@ class Enterprise < ActiveRecord::Base
validates :name, presence: true
validates :type, presence: true, inclusion: {in: TYPES}
validates :sells, presence: true, inclusion: {in: SELLS}
validates :address, presence: true, associated: true
validates :email, presence: true
validates_presence_of :owner
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
validates_length_of :description, :maximum => 255
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
before_validation :set_unused_address_fields
@@ -59,8 +62,10 @@ class Enterprise < ActiveRecord::Base
scope :by_name, order('name')
scope :visible, where(:visible => true)
scope :confirmed, where('confirmed_at IS NOT NULL')
scope :unconfirmed, where('confirmed_at IS NULL')
scope :is_primary_producer, where(:is_primary_producer => true)
scope :is_distributor, where(:is_distributor => true)
scope :is_distributor, where('sells != ?', 'none')
scope :supplying_variant_in, lambda { |variants| joins(:supplied_products => :variants_including_master).where('spree_variants.id IN (?)', variants).select('DISTINCT enterprises.*') }
scope :with_supplied_active_products_on_hand, lambda {
joins(:supplied_products)
@@ -211,36 +216,30 @@ class Enterprise < ActiveRecord::Base
Spree::Variant.joins(:product => :product_distributions).where('product_distributions.distributor_id=?', self.id)
end
# Replaces currententerprse type field.
def sells
# Type: full - single - profile becomes Sells: all - own - none
# Remove this return later.
return "none" if !is_distributor || type == "profile"
return "own" if type == "single" || suppliers == [self]
"all"
def is_distributor
self.sells != "none"
end
# Simplify enterprise categories for frontend logic and icons, and maybe other things.
def enterprise_category
def category
# Make this crazy logic human readable so we can argue about it sanely.
# This can be simplified later, it's like this for readablitlty during changes.
category = is_primary_producer ? "producer_" : "non_producer_"
category << "sell_" + sells
cat = self.is_primary_producer ? "producer_" : "non_producer_"
cat << "sells_" + self.sells
# Map backend cases to front end cases.
case category
when "producer_sell_all"
"producer_hub" # Producer hub who sells own and others produce and supplies other hubs.
when "producer_sell_own"
"producer_shop" # Producer with shopfront and supplies other hubs.
when "producer_sell_none"
"producer" # Producer only supplies through others.
when "non_producer_sell_all"
"hub" # Hub selling others products in order cycles.
when "non_producer_sell_own"
"hub" # Wholesaler selling through own shopfront?
when "non_producer_sell_none"
"hub_profile" # Hub selling outside the system.
case cat
when "producer_sells_any"
:producer_hub # Producer hub who sells own and others produce and supplies other hubs.
when "producer_sells_own"
:producer_shop # Producer with shopfront and supplies other hubs.
when "producer_sells_none"
:producer # Producer only supplies through others.
when "non_producer_sells_any"
:hub # Hub selling others products in order cycles.
when "non_producer_sells_own"
:hub # Wholesaler selling through own shopfront? Does this need a separate name? Should it exist?
when "non_producer_sells_none"
:hub_profile # Hub selling outside the system.
end
end
@@ -260,14 +259,20 @@ class Enterprise < ActiveRecord::Base
select('DISTINCT spree_taxons.*')
end
protected
def devise_mailer
EnterpriseMailer
end
private
def send_creation_email
EnterpriseMailer.creation_confirmation(self).deliver
def check_email
skip_confirmation! if owner.enterprises.confirmed.map(&:email).include?(email)
end
def strip_url(url)
url.andand.sub /(https?:\/\/)?/, ''
url.andand.sub(/(https?:\/\/)?/, '')
end
def set_unused_address_fields

View File

@@ -15,7 +15,7 @@ class EnterpriseGroup < ActiveRecord::Base
path: 'public/images/enterprise_groups/logos/:id/:style/:basename.:extension'
has_attached_file :promo_image,
styles: {large: "1200x260#"},
styles: {large: ["1200x260#", :jpg]},
url: '/images/enterprise_groups/promo_images/:id/:style/:basename.:extension',
path: 'public/images/enterprise_groups/promo_images/:id/:style/:basename.:extension'

View File

@@ -1,6 +1,8 @@
class AbilityDecorator
include CanCan::Ability
# All abilites are allocated from this initialiser, currently in 5 chunks.
# Spree also defines other abilities.
def initialize(user)
add_base_abilities user if is_new_user? user
add_enterprise_management_abilities user if can_manage_enterprises? user
@@ -9,27 +11,31 @@ class AbilityDecorator
add_relationship_management_abilities user if can_manage_relationships? user
end
# New users have no enterprises.
def is_new_user?(user)
user.enterprises.blank?
end
# Users can manage an enterprise if they have one.
def can_manage_enterprises?(user)
user.enterprises.present?
end
# Users can manage products if they have an enterprise.
def can_manage_products?(user)
can_manage_enterprises? user
can_manage_enterprises?(user) && user.enterprises.is_primary_producer.present?
end
# Users can manage orders if they have a sells own/any enterprise.
def can_manage_orders?(user)
( user.enterprises.map(&:type) & %w(single full) ).any?
( user.enterprises.map(&:sells) & %w(own any) ).any?
end
def can_manage_relationships?(user)
can_manage_enterprises? user
end
# New users can create an enterprise, and gain other permissions from doing this.
def add_base_abilities(user)
can [:create], Enterprise
end
@@ -47,6 +53,12 @@ class AbilityDecorator
can [:read, :edit, :update, :bulk_update], Enterprise do |enterprise|
user.enterprises.include? enterprise
end
# All enterprises can have fees, though possibly suppliers don't need them?
can [:index, :create], EnterpriseFee
can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee|
user.enterprises.include? enterprise_fee.enterprise
end
end
def add_product_management_abilities(user)
@@ -66,6 +78,9 @@ class AbilityDecorator
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory], :report
end
def add_order_management_abilities(user)
@@ -76,7 +91,7 @@ class AbilityDecorator
# during the order creation process from the admin backend
order.distributor.nil? || user.enterprises.include?(order.distributor)
end
can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor?)
can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor)
can [:admin, :create], Spree::LineItem
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Payment
@@ -90,11 +105,6 @@ class AbilityDecorator
end
can [:for_order_cycle], Enterprise
can [:index, :create], EnterpriseFee
can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee|
user.enterprises.include? enterprise_fee.enterprise
end
can [:admin, :index, :read, :create, :edit, :update], ExchangeVariant
can [:admin, :index, :read, :create, :edit, :update], Exchange
can [:admin, :index, :read, :create, :edit, :update], ExchangeFee
@@ -111,7 +121,7 @@ class AbilityDecorator
end
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory], :report
can [:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], :report
end

View File

@@ -0,0 +1,23 @@
Spree::Image.class_eval do
# Spree stores attachent definitions in JSON. This converts the style name and format to
# strings. However, when paperclip encounters these, it doesn't recognise the format.
# Here we solve that problem by converting format and style name to symbols.
# See also: ImageSettingsController decorator.
#
# eg. {'mini' => ['48x48>', 'png']} is converted to {mini: ['48x48>', :png]}
def self.format_styles(styles)
styles_a = styles.map do |name, style|
style[1] = style[1].to_sym if style.is_a? Array
[name.to_sym, style]
end
Hash[styles_a]
end
def self.reformat_styles
Spree::Image.attachment_definitions[:attachment][:styles] =
format_styles(Spree::Image.attachment_definitions[:attachment][:styles])
end
reformat_styles
end

View File

@@ -0,0 +1,11 @@
/ replace_contents '#styles_list'
- @styles.each_with_index do |(style_name, style_value), index|
.field.three.columns
= label_tag "attachment_styles[#{style_name}]", style_name
%a.destroy_style.with-tip{:alt => t(:destroy), :href => "#", :title => t(:destroy)}
%i.icon-trash
= text_field_tag "attachment_styles[#{style_name}][]", admin_image_settings_geometry_from_style(style_value), :class => 'fullwidth'
%br/
- current_format = admin_image_settings_format_from_style(style_value) || ''
= select_tag "attachment_styles[#{style_name}][]", options_for_select(admin_image_settings_format_options, current_format), :class => 'fullwidth', :id => "attachment_styles_format_#{style_name}"

View File

@@ -1,3 +1,3 @@
class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :name, :id, :is_primary_producer, :is_distributor, :payment_method_ids, :shipping_method_ids
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids
end

View File

@@ -17,9 +17,6 @@ end
class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
attributes :orders_close_at, :active
#TODO: Remove these later
attributes :icon, :icon_font, :producer_icon_font, :has_shopfront, :has_hub_listing, :enterprise_category
def orders_close_at
OrderCycle.first_closing_for(object).andand.orders_close_at
end
@@ -28,60 +25,6 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
@options[:active_distributors].andand.include? object
end
def enterprise_category
object.enterprise_category
end
def has_shopfront
object.is_distributor && object.type != 'profile'
end
# Used to select enterprises for hub listing
def has_hub_listing
has_shopfront || object.enterprise_category == "hub_profile"
end
# Map svg icons.
def icon
icons = {
"hub" => "/assets/map_005-hub.svg",
"hub_profile" => "/assets/map_006-hub-profile.svg",
"producer_hub" => "/assets/map_005-hub.svg",
"producer_shop" => "/assets/map_003-producer-shop.svg",
"producer" => "/assets/map_001-producer-only.svg",
"producer_profile" => "/assets/map_002-producer-only-profile.svg",
}
icons[object.enterprise_category]
end
# Choose regular icon font for enterprises.
def icon_font
icon_fonts = {
"hub" => "ofn-i_063-hub",
"hub_profile" => "ofn-i_064-hub-reversed",
"producer_hub" => "ofn-i_063-hub",
"producer_shop" => "ofn-i_059-producer",
"producer" => "ofn-i_059-producer",
"producer_profile" => "ofn-i_060-producer-reversed",
}
icon_fonts[object.enterprise_category]
end
# Choose producer page icon font - yes, sadly its got to be different.
# This duplicates some code but covers the producer page edge case where
# producer-hub has a producer icon without needing to duplicate the category logic in angular.
def producer_icon_font
icon_fonts = {
"hub" => "",
"hub_profile" => "",
"producer_hub" => "ofn-i_059-producer",
"producer_shop" => "ofn-i_059-producer",
"producer" => "ofn-i_059-producer",
"producer_profile" => "ofn-i_060-producer-reversed",
"empty" => "",
}
icon_fonts[object.enterprise_category]
end
end
@@ -92,8 +35,8 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
attributes :name, :id, :description, :latitude, :longitude,
:long_description, :website, :instagram, :linkedin, :twitter,
:facebook, :is_primary_producer, :is_distributor, :phone, :visible,
:email, :hash, :logo, :promo_image, :path,
:pickup, :delivery
:email, :hash, :logo, :promo_image, :path, :pickup, :delivery,
:icon, :icon_font, :producer_icon_font, :category
has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer
has_many :supplied_taxons, serializer: Api::IdSerializer
@@ -102,6 +45,10 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
has_one :address, serializer: Api::AddressSerializer
def visible
object.visible && object.confirmed?
end
def pickup
object.shipping_methods.where(:require_ship_address => false).present?
end
@@ -131,4 +78,42 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
def path
"/enterprises/#{object.to_param}/shop"
end
# Map svg icons.
def icon
icons = {
:hub => "/assets/map_005-hub.svg",
:hub_profile => "/assets/map_006-hub-profile.svg",
:producer_hub => "/assets/map_005-hub.svg",
:producer_shop => "/assets/map_003-producer-shop.svg",
:producer => "/assets/map_001-producer-only.svg",
}
icons[object.category]
end
# Choose regular icon font for enterprises.
def icon_font
icon_fonts = {
:hub => "ofn-i_063-hub",
:hub_profile => "ofn-i_064-hub-reversed",
:producer_hub => "ofn-i_063-hub",
:producer_shop => "ofn-i_059-producer",
:producer => "ofn-i_059-producer",
}
icon_fonts[object.category]
end
# Choose producer page icon font - yes, sadly its got to be different.
# This duplicates some code but covers the producer page edge case where
# producer-hub has a producer icon without needing to duplicate the category logic in angular.
def producer_icon_font
icon_fonts = {
:hub => "",
:hub_profile => "",
:producer_hub => "ofn-i_059-producer",
:producer_shop => "ofn-i_059-producer",
:producer => "ofn-i_059-producer",
}
icon_fonts[object.category]
end
end

View File

@@ -1,3 +1,7 @@
class Api::StateSerializer < ActiveModel::Serializer
attributes :id, :name, :abbr
def abbr
object.abbr.upcase
end
end

Some files were not shown because too many files have changed in this diff Show More