Bulk Edit image upload

Image Upload Translations

Squashme

Squashme

Squashme

Code review tweaks
This commit is contained in:
Matt-Yorkley
2017-02-22 00:09:54 +00:00
parent 24dca5e51f
commit 7d27d1325e
15 changed files with 338 additions and 6 deletions

View File

@@ -1,3 +1,14 @@
angular.module("ofn.admin", ["ngResource", "ngAnimate", "admin.utils", "admin.indexUtils", "admin.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) ->
angular.module("ofn.admin", [
"ngResource",
"mm.foundation",
"angularFileUpload",
"ngAnimate",
"admin.utils",
"admin.indexUtils",
"admin.dropdown",
"admin.products",
"admin.taxons",
"infinite-scroll"
]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -51,7 +51,6 @@
//= require textAngular.min.js
//= require i18n/translations
//= require darkswarm/i18n.translate.js
//
//= require moment
//= require moment/en-gb.js
//= require moment/es.js
@@ -60,5 +59,7 @@
//= require moment/nb.js
//= require moment/pt-br.js
//= require moment/sv.js
//= require ../shared/mm-foundation-tpls-0.8.0.min.js
//= require angularjs-file-upload
//= require_tree .

View File

@@ -0,0 +1,6 @@
angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImageService) ->
$scope.imageUploader = ProductImageService.imageUploader
$scope.imagePreview = ProductImageService.imagePreview
$scope.$watch 'product.image_url', (newValue) ->
$scope.imagePreview = newValue if newValue

View File

@@ -0,0 +1,6 @@
angular.module("ofn.admin").directive "imageModal", ($modal, ProductImageService) ->
restrict: 'C'
link: (scope, elem, attrs, ctrl) ->
elem.on "click", (ev) =>
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/image_upload.html', controller: ctrl, scope: scope, windowClass: 'product-image-upload')
ProductImageService.configure(scope.product)

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").factory "ProductImageService", (FileUploader, SpreeApiKey) ->
new class ProductImageService
imagePreview: null
imageUploader: new FileUploader
headers:
'X-Spree-Token': SpreeApiKey
autoUpload: true
configure: (product) =>
@imageUploader.url = "/api/images/product/#{product.id}"
@imagePreview = product.image_url
@imageUploader.onSuccessItem = (image, response) =>
product.thumb_url = response.thumb_url
product.image_url = response.image_url

View File

@@ -1,4 +1,5 @@
window.Darkswarm = angular.module("Darkswarm", ["ngResource",
window.Darkswarm = angular.module("Darkswarm", [
'ngResource',
'mm.foundation',
'LocalStorageModule',
'infinite-scroll',
@@ -10,7 +11,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'duScroll',
'angularFileUpload',
'angularSlideables'
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
$httpProvider.defaults.headers['common']['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
$httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*"

View File

@@ -0,0 +1,10 @@
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.fa.fa-times-circle{'aria-hidden' => "true"}
%form#image_upload{ name: 'form', novalidate: true, enctype: 'multipart/form-data', multipart: true, ng: { controller: "ProductImageCtrl" } }
%div.image-preview
%img.spinner{ src: "/assets/spinning-circles.svg", ng: { hide: "!imageUploader.isUploading" }}
%img.preview{ng: {src: "{{imagePreview}}", class: "{'faded': imageUploader.isUploading}"}}
%label{for: 'image-upload', class: 'button'} #{t('admin.products.bulk_edit.upload_an_image')}
%input#image-upload{hidden: true, type: 'file', 'nv-file-select' => true, uploader: "imageUploader"}

View File

@@ -0,0 +1,198 @@
.reveal-modal-bg {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: black;
z-index: 1004;
display: none;
left: 0;
}
.reveal-modal, dialog {
visibility: hidden;
display: none;
position: absolute;
z-index: 1005;
width: 100vw;
top: 0;
border-radius: 0.4em;
border: 0px none;
left: 0;
background-color: white;
padding: 1.25rem;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
padding: 1.875rem;
}
.reveal-modal .column, dialog .column, .reveal-modal .columns, dialog .columns {
min-width: 0;
}
.reveal-modal > :first-child, dialog > :first-child {
margin-top: 0;
}
.reveal-modal > :last-child, dialog > :last-child {
margin-bottom: 0;
}
@media only screen and (min-width: 40.063em) {
.reveal-modal, dialog {
width: 80%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
}
.reveal-modal.radius, dialog.radius {
border-radius: 3px;
}
.reveal-modal.round, dialog.round {
border-radius: 1000px;
}
.reveal-modal.collapse, dialog.collapse {
padding: 0;
}
.reveal-modal.full, dialog.full {
top: 0;
left: 0;
height: 100%;
height: 100vh;
min-height: 100vh;
max-width: none !important;
margin-left: 0 !important;
}
.reveal-modal .close-reveal-modal, dialog .close-reveal-modal {
font-size: 2.5rem;
line-height: 1;
position: absolute;
top: 0.625rem;
right: 1.375rem;
color: #aaaaaa;
font-weight: bold;
cursor: pointer;
}
dialog::backdrop, dialog + .backdrop {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: black;
background: rgba(0, 0, 0, 0.45);
z-index: auto;
display: none;
left: 0;
}
dialog[open] {
display: block;
}
@media print {
dialog, .reveal-modal, dialog {
display: none;
background: white !important;
}
}
// ANIMATION CLASSES
.fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
transition: opacity 0.15s linear;
}
.fade.in {
opacity: 1;
}
.reveal-modal.fade {
-webkit-transition: -webkit-transform 0.2s ease-out;
-moz-transition: -moz-transform 0.2s ease-out;
-o-transition: -o-transform 0.2s ease-out;
transition: transform 0.2s ease-out;
-webkit-transform: translate(0, -25%);
-ms-transform: translate(0, -25%);
transform: translate(0, -25%);
}
.reveal-modal.in {
-webkit-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
.reveal-modal-bg.fade {
filter: alpha(opacity = 0);
opacity: 0;
}
.reveal-modal-bg.in {
filter: alpha(opacity = 50);
opacity: 0.5;
}
@media only screen and (max-width: 40em) {
.reveal-modal, dialog {
min-height: 100vh;
}
}
@media only screen and (min-width: 40.063em) {
.reveal-modal, dialog {
top: 6.25rem;
}
.reveal-modal.tiny, dialog.tiny {
width: 30%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
.reveal-modal.small, dialog.small {
width: 40%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
.reveal-modal.medium, dialog.medium {
width: 60%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
.reveal-modal.large, dialog.large {
width: 70%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
.reveal-modal.xlarge, dialog.xlarge {
width: 95%;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
.reveal-modal.full, dialog.full {
width: 100vw;
max-width: 62.5rem;
left: 0;
right: 0;
margin: 0 auto;
}
}

View File

@@ -66,8 +66,10 @@ table#listing_products.bulk {
td.image {
padding: 10px;
text-align: center;
img {
border: 1px solid transparent;
border-radius: 0.4em;
}
img:hover {
opacity: 0.8;
@@ -92,3 +94,46 @@ table#listing_products.bulk {
}
}
}
.reveal-modal.product-image-upload {
width: 300px;
a.close-reveal-modal {
font-size: 23px;
color: #de6060;
right: 0.45rem;
top: 0.35rem;
:hover {
color: #bf4545;
}
}
div.image-preview {
//float: left;
}
label {
margin-top: 20px;
}
}
form#image_upload {
text-align: center;
.spinner {
width: 160px;
height: 65%;
position: absolute;
//background-color: rgba(255, 255, 255, 0.75);
padding: 32px;
margin: 0px -80px;
left: 50%;
z-index: 100;
}
.preview {
width: 240px;
}
.faded {
opacity: 0.25;
}
.button:hover {
cursor: pointer;
}
}

View File

@@ -0,0 +1,15 @@
Spree::Api::ImagesController.class_eval do
def update_product_image
@product = Spree::Product.find(params[:product_id])
authorize! :update, @product
if @product.images.first.nil?
@image = Spree::Image.create(attachment: params[:file], viewable_id: @product.master.id, viewable_type: 'Spree::Variant')
respond_with(@image, status: 201)
else
@image = @product.images.first
@image.update_attributes(attachment: params[:file])
respond_with(@image, status: 200)
end
end
end

View File

@@ -1,13 +1,21 @@
class Api::Admin::ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties
attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id
attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id, :image_url, :thumb_url
has_one :supplier, key: :producer_id, embed: :id
has_one :primary_taxon, key: :category_id, embed: :id
has_many :variants, key: :variants, serializer: Api::Admin::VariantSerializer # embed: ids
has_one :master, serializer: Api::Admin::VariantSerializer
def image_url
object.images.present? ? object.images.first.attachment.url(:product) : "/assets/noimage/product.png"
end
def thumb_url
object.images.present? ? object.images.first.attachment.url(:mini) : "/assets/noimage/mini.png"
end
def on_hand
object.on_hand.nil? ? 0 : object.on_hand.to_f.finite? ? object.on_hand : I18n.t(:on_demand)
end

View File

@@ -3,7 +3,8 @@
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
%td.image{ 'ng-show' => 'columns.image.visible' }
%img{'ng-src' => '{{ product.image_url }}'}
%a{class: 'image-modal'}
%img{'ng-src' => '{{ product.thumb_url }}'}
%td.producer{ 'ng-show' => 'columns.producer.visible' }
%select.select2.fullwidth{ 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
%td.sku{ 'ng-show' => 'columns.sku.visible' }

View File

@@ -0,0 +1,5 @@
object @image
attributes(*image_attributes)
attributes :viewable_type, :viewable_id
node( :thumb_url ) { @product.images.first.attachment.url(:mini) }
node( :image_url ) { @product.images.first.attachment.url(:product) }

View File

@@ -441,6 +441,15 @@ en:
products:
unit_name_placeholder: 'eg. bunches'
bulk_edit:
unit: Unit
display_as: Display As
category: Category
tax_category: Tax Category
inherits_properties?: Inherits Properties?
available_on: Available On
av_on: "Av. On"
upload_an_image: Upload an image
properties:
property_name: Property Name
inherited_property: Inherited Property

View File

@@ -286,6 +286,7 @@ Spree::Core::Engine.routes.prepend do
get :managed, on: :collection
end
post '/images/product/:product_id', to: 'images#update_product_image'
end
namespace :admin do