vinsol_spree_themes 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/.ruby-version +1 -0
- data/Appraisals +16 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +386 -0
- data/LICENSE +26 -0
- data/README.md +250 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/spree/backend/codemirror/codemirror.js +9351 -0
- data/app/assets/javascripts/spree/backend/codemirror/css-hint.js +60 -0
- data/app/assets/javascripts/spree/backend/codemirror/fullscreen.js +41 -0
- data/app/assets/javascripts/spree/backend/codemirror/html-hint.js +348 -0
- data/app/assets/javascripts/spree/backend/codemirror/javascript-hint.js +155 -0
- data/app/assets/javascripts/spree/backend/codemirror/javascript.js +813 -0
- data/app/assets/javascripts/spree/backend/codemirror/markdown.js +796 -0
- data/app/assets/javascripts/spree/backend/codemirror/ruby.js +295 -0
- data/app/assets/javascripts/spree/backend/codemirror/show-hint.js +438 -0
- data/app/assets/javascripts/spree/backend/editor.js +11 -0
- data/app/assets/javascripts/spree/backend/jquery.treemenu.js +87 -0
- data/app/assets/javascripts/spree/backend/main.js +26 -0
- data/app/assets/javascripts/spree/backend/spree_themes.js +2 -0
- data/app/assets/javascripts/spree/backend/template.js +51 -0
- data/app/assets/javascripts/spree/backend/theme.js +17 -0
- data/app/assets/javascripts/spree/frontend/spree_themes.js +2 -0
- data/app/assets/stylesheets/spree/backend/codemirror/codemirror.css +340 -0
- data/app/assets/stylesheets/spree/backend/codemirror/fullscreen.css +6 -0
- data/app/assets/stylesheets/spree/backend/codemirror/show-hint.css +36 -0
- data/app/assets/stylesheets/spree/backend/editor.css +7 -0
- data/app/assets/stylesheets/spree/backend/jquery.treemenu.css +8 -0
- data/app/assets/stylesheets/spree/backend/spree_themes.css +7 -0
- data/app/assets/stylesheets/spree/backend/style.css +239 -0
- data/app/assets/stylesheets/spree/backend/template_editor.css +39 -0
- data/app/assets/stylesheets/spree/frontend/spree_themes.css +6 -0
- data/app/assets/stylesheets/spree/frontend/theme_preview.css +9 -0
- data/app/controllers/spree/admin/themes_controller.rb +80 -0
- data/app/controllers/spree/admin/themes_preview_controller.rb +30 -0
- data/app/controllers/spree/admin/themes_templates_controller.rb +60 -0
- data/app/controllers/spree/fragment_cache_controller.rb +13 -0
- data/app/controllers/spree/store_controller_decorator.rb +37 -0
- data/app/helpers/spree/base_helper_decorator.rb +45 -0
- data/app/models/spree/theme.rb +153 -0
- data/app/models/spree/themes_template.rb +67 -0
- data/app/models/spree/themes_template/cache_resolver.rb +37 -0
- data/app/overrides/spree/add_preview_bar_to_store.html.rb +6 -0
- data/app/overrides/spree/add_theme_meta_details_to_store.html.rb +6 -0
- data/app/overrides/spree/admin/add_themes_tab.html.rb +6 -0
- data/app/services/assets_precompiler_service.rb +86 -0
- data/app/services/file_generator_service.rb +29 -0
- data/app/services/template_generator_service.rb +101 -0
- data/app/services/zip_file_builder.rb +87 -0
- data/app/services/zip_file_extractor.rb +52 -0
- data/app/views/spree/admin/shared/_theme_menu_button.html.erb +5 -0
- data/app/views/spree/admin/themes/_themes.html.erb +44 -0
- data/app/views/spree/admin/themes/_upload.html.erb +17 -0
- data/app/views/spree/admin/themes/index.html.erb +50 -0
- data/app/views/spree/admin/themes_templates/_editor.html.erb +29 -0
- data/app/views/spree/admin/themes_templates/_template.html.erb +12 -0
- data/app/views/spree/admin/themes_templates/edit.html.erb +13 -0
- data/app/views/spree/admin/themes_templates/edit.js.erb +1 -0
- data/app/views/spree/admin/themes_templates/index.html.erb +27 -0
- data/app/views/spree/admin/themes_templates/new.html.erb +56 -0
- data/app/views/spree/shared/_preview_bar.html.erb +7 -0
- data/app/views/spree/shared/_theme_metadata.html.erb +9 -0
- data/bin/rails +7 -0
- data/config/initializers/assets.rb +24 -0
- data/config/initializers/sprockets.rb +85 -0
- data/config/locales/en.yml +52 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20170628072452_vinsol_spree_themes_create_themes_and_themes_templates_table.rb +24 -0
- data/gemfiles/spree_3_1.gemfile +8 -0
- data/gemfiles/spree_3_2.gemfile +9 -0
- data/gemfiles/spree_master.gemfile +9 -0
- data/lib/generators/themes/default.zip +0 -0
- data/lib/generators/vinsol_spree_themes/install/install_generator.rb +50 -0
- data/lib/tasks/sync_templates.rake +47 -0
- data/lib/vinsol_spree_themes.rb +3 -0
- data/lib/vinsol_spree_themes/engine.rb +22 -0
- data/lib/vinsol_spree_themes/factories.rb +6 -0
- data/lib/vinsol_spree_themes/version.rb +18 -0
- data/spec/spec_helper.rb +93 -0
- data/vinsol_spree_themes.gemspec +43 -0
- metadata +374 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
class AssetsPrecompilerService
|
2
|
+
|
3
|
+
attr_reader :theme, :env, :manifest
|
4
|
+
|
5
|
+
PUBLIC_PRECOMPILED_ASSET_PATH = File.join('public', 'assets', 'vinsol_spree_theme')
|
6
|
+
PUBLIC_PRECOMPILED_ASSET_PATH_FOR_PREVIEW = File.join('public', 'assets', 'preview_vinsol_spree_theme')
|
7
|
+
THEME_PATH = File.join('public', 'vinsol_spree_themes')
|
8
|
+
CURRENT_THEME_PATH = File.join(THEME_PATH, 'current')
|
9
|
+
|
10
|
+
def initialize(theme)
|
11
|
+
@theme = theme
|
12
|
+
end
|
13
|
+
|
14
|
+
def minify(options= {})
|
15
|
+
options.merge!({ precompile: true }) unless options.key?(:precompile)
|
16
|
+
build_environment
|
17
|
+
build_manifest
|
18
|
+
options[:precompile] ? assets_precompile : manifest
|
19
|
+
end
|
20
|
+
|
21
|
+
def copy_assets
|
22
|
+
source_path = File.join(CURRENT_THEME_PATH, 'precompiled_assets', '.')
|
23
|
+
|
24
|
+
FileUtils.rm_r(PUBLIC_PRECOMPILED_ASSET_PATH) if File.exists?(PUBLIC_PRECOMPILED_ASSET_PATH)
|
25
|
+
FileUtils.mkdir_p(PUBLIC_PRECOMPILED_ASSET_PATH)
|
26
|
+
FileUtils.cp_r(source_path, PUBLIC_PRECOMPILED_ASSET_PATH)
|
27
|
+
end
|
28
|
+
|
29
|
+
def copy_preview_assets
|
30
|
+
source_path = File.join(theme_path, 'precompiled_assets', '.')
|
31
|
+
|
32
|
+
FileUtils.rm_r(PUBLIC_PRECOMPILED_ASSET_PATH_FOR_PREVIEW) if File.exists?(PUBLIC_PRECOMPILED_ASSET_PATH_FOR_PREVIEW)
|
33
|
+
FileUtils.mkdir_p(PUBLIC_PRECOMPILED_ASSET_PATH_FOR_PREVIEW)
|
34
|
+
FileUtils.cp_r(source_path, PUBLIC_PRECOMPILED_ASSET_PATH_FOR_PREVIEW)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def build_environment
|
40
|
+
@env ||= Sprockets::Environment.new()
|
41
|
+
prepend_stylesheet_path
|
42
|
+
prepend_javascript_path
|
43
|
+
prepend_images_path
|
44
|
+
prepend_fonts_path
|
45
|
+
set_compressors
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_manifest
|
49
|
+
@manifest ||= Sprockets::Manifest.new(env, File.join(theme_path, 'precompiled_assets'))
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepend_stylesheet_path
|
53
|
+
env.prepend_path(File.join(source_asset_path, 'stylesheets'))
|
54
|
+
end
|
55
|
+
|
56
|
+
def prepend_javascript_path
|
57
|
+
env.prepend_path(File.join(source_asset_path, 'javascripts'))
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepend_images_path
|
61
|
+
env.prepend_path(File.join(source_asset_path, 'images'))
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepend_fonts_path
|
65
|
+
env.prepend_path(File.join(source_asset_path, 'fonts'))
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_compressors
|
69
|
+
env.js_compressor = Rails.application.config.assets.js_compressor
|
70
|
+
env.css_compressor = Rails.application.config.assets.css_compressor
|
71
|
+
end
|
72
|
+
|
73
|
+
def assets_precompile
|
74
|
+
manifest.clobber
|
75
|
+
manifest.compile('*')
|
76
|
+
end
|
77
|
+
|
78
|
+
def source_asset_path
|
79
|
+
theme.published? ? CURRENT_THEME_PATH : theme_path
|
80
|
+
end
|
81
|
+
|
82
|
+
def theme_path
|
83
|
+
File.join(THEME_PATH, theme.name)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class FileGeneratorService
|
2
|
+
|
3
|
+
attr_reader :template, :theme
|
4
|
+
|
5
|
+
def initialize(template)
|
6
|
+
@template = template
|
7
|
+
@theme = template.theme
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.create(template)
|
11
|
+
new(template).send :create
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def create
|
17
|
+
FileUtils.mkdir_p(output_directory)
|
18
|
+
File.open(filepath, 'w+') { |file| file.puts(template.body) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def filepath
|
22
|
+
File.join(template.path, template.name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def output_directory
|
26
|
+
File.dirname(filepath)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class TemplateGeneratorService
|
2
|
+
|
3
|
+
DEFAULT_LOCALE = 'en'
|
4
|
+
FILE_EXTENSIONS = { css: '.css', scss: '.scss', js: '.js', yml: '.yml' }
|
5
|
+
IMAGE_EXTENSIONS = ['.png', '.gif', '.jpeg', '.jpg']
|
6
|
+
FONT_EXTENSIONS = ['.woff', '.ttf', '.svg', '.eot', '.woff2', '.otf']
|
7
|
+
|
8
|
+
attr_reader :filepath, :theme_template, :theme
|
9
|
+
|
10
|
+
def initialize(filepath, theme)
|
11
|
+
@filepath = filepath
|
12
|
+
@theme = theme
|
13
|
+
@theme_template = build_template
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
return nil if(image_file?(filepath) || font_directory?(filepath))
|
18
|
+
theme_template.assign_attributes(template_attributes)
|
19
|
+
theme_template.save
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_template
|
25
|
+
@theme.templates.find_or_initialize_by(path: get_path, name: file_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def template_attributes
|
29
|
+
{
|
30
|
+
body: File.read(filepath),
|
31
|
+
path: get_path,
|
32
|
+
partial: is_partial?,
|
33
|
+
handler: get_handler,
|
34
|
+
format: get_format,
|
35
|
+
locale: DEFAULT_LOCALE,
|
36
|
+
theme_id: theme.id,
|
37
|
+
name: file_name
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_path
|
42
|
+
File.dirname(filepath)
|
43
|
+
end
|
44
|
+
|
45
|
+
def is_partial?
|
46
|
+
file_name.starts_with?('_')
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_handler
|
50
|
+
return nil if assets_file?(filepath) || yml_files?(filepath)
|
51
|
+
File.extname(filepath).gsub('.', '')
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_format
|
55
|
+
return nil if assets_file?(filepath) || yml_files?(filepath)
|
56
|
+
# In spree few `.js.erb` files related to google are rendered in html format.
|
57
|
+
script_embeded_partial? ? 'html' : format
|
58
|
+
end
|
59
|
+
|
60
|
+
def script_embeded_partial?
|
61
|
+
is_partial? && format.eql?('js')
|
62
|
+
end
|
63
|
+
|
64
|
+
def file_name
|
65
|
+
File.basename(filepath)
|
66
|
+
end
|
67
|
+
|
68
|
+
def format
|
69
|
+
file_name_without_ext.split('.')[-1]
|
70
|
+
end
|
71
|
+
|
72
|
+
def file_name_without_ext
|
73
|
+
File.basename(filepath, get_handler)
|
74
|
+
end
|
75
|
+
|
76
|
+
def stylesheet_file?(filename)
|
77
|
+
File.extname(filename) == FILE_EXTENSIONS[:css] || FILE_EXTENSIONS[:scss]
|
78
|
+
end
|
79
|
+
|
80
|
+
def javascript_file?(filename)
|
81
|
+
File.extname(filename) == FILE_EXTENSIONS[:js]
|
82
|
+
end
|
83
|
+
|
84
|
+
def assets_file?(filename)
|
85
|
+
stylesheet_file?(filename) || javascript_file?(filename)
|
86
|
+
end
|
87
|
+
|
88
|
+
# FIX_ME_PG:- considering all images used in themes to be kept in images directory. Later invalidate using file content-type.
|
89
|
+
def image_file?(filename)
|
90
|
+
file_name == 'snapshot.png' || get_path.split('/').include?('images') && IMAGE_EXTENSIONS.include?(File.extname(filename))
|
91
|
+
end
|
92
|
+
|
93
|
+
def font_directory?(filename)
|
94
|
+
get_path.split('/').include?('fonts') && FONT_EXTENSIONS.include?(File.extname(filename))
|
95
|
+
end
|
96
|
+
|
97
|
+
def yml_files?(filename)
|
98
|
+
File.extname(filename) == FILE_EXTENSIONS[:yml]
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
class ZipFileBuilder
|
4
|
+
|
5
|
+
THEMES_PATH = File.join('public', 'vinsol_spree_themes')
|
6
|
+
INVALID_DIRECTORIES = ['.', '..', 'precompiled_assets']
|
7
|
+
|
8
|
+
attr_reader :theme, :entries, :input_path, :output_path, :zip, :name
|
9
|
+
|
10
|
+
def initialize(theme)
|
11
|
+
@theme = theme
|
12
|
+
@name = name
|
13
|
+
input_theme_directory_path
|
14
|
+
output_zip_file_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def archive
|
18
|
+
remove
|
19
|
+
@entries = get_directory_content(input_theme_directory_path)
|
20
|
+
delete_non_zip_content(@entries)
|
21
|
+
build_zip_file
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove
|
25
|
+
File.delete(output_path) if File.exist?(output_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
"#{ theme.name }.zip"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def input_theme_directory_path
|
35
|
+
@input_path = File.join(THEMES_PATH, theme.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def output_zip_file_path
|
39
|
+
@output_path = File.join('tmp', name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_directory_content(directory_path)
|
43
|
+
Dir.entries(directory_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_non_zip_content(entries)
|
47
|
+
INVALID_DIRECTORIES.each { |dir| entries.delete(dir) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_zip_file
|
51
|
+
open_zip_file
|
52
|
+
write_entries('', entries)
|
53
|
+
zip.close();
|
54
|
+
end
|
55
|
+
|
56
|
+
def open_zip_file
|
57
|
+
@zip = Zip::File.open(output_path, Zip::File::CREATE)
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_entries(path, entries)
|
61
|
+
entries.each do |e|
|
62
|
+
zip_path = path == '' ? e : File.join(path, e)
|
63
|
+
filepath = File.join(input_path, zip_path)
|
64
|
+
|
65
|
+
if File.directory?(filepath)
|
66
|
+
build_sub_directory(zip_path, filepath)
|
67
|
+
else
|
68
|
+
build_file_to_zip(zip_path, filepath)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_sub_directory(zip_path, filepath)
|
74
|
+
zip.mkdir(zip_path)
|
75
|
+
subdir_entries = get_directory_content(filepath);
|
76
|
+
delete_non_zip_content(subdir_entries)
|
77
|
+
|
78
|
+
write_entries(zip_path, subdir_entries)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_file_to_zip(zip_path, filepath)
|
82
|
+
zip.get_output_stream(zip_path) do |file|
|
83
|
+
file.puts(File.open(filepath, "rb").read())
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
class ZipFileExtractor
|
4
|
+
|
5
|
+
OUTPUT_PATH = File.join('public', 'vinsol_spree_themes')
|
6
|
+
IGNORED_FILES_REGEX = /\/(\.|__)/
|
7
|
+
|
8
|
+
attr_reader :file_path, :theme
|
9
|
+
|
10
|
+
def initialize(file_path, theme)
|
11
|
+
@file_path = file_path
|
12
|
+
@theme = theme
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract
|
16
|
+
FileUtils.mkdir_p(OUTPUT_PATH)
|
17
|
+
parse_file
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_file
|
23
|
+
Zip::File.open(file_path) do |zip_file|
|
24
|
+
zip_file.each do |file|
|
25
|
+
filepath = File.join(output_path, file.name)
|
26
|
+
next if filepath =~ IGNORED_FILES_REGEX
|
27
|
+
unless File.exist?(filepath)
|
28
|
+
FileUtils::mkdir_p(File.dirname(filepath))
|
29
|
+
zip_file.extract(file, filepath)
|
30
|
+
end
|
31
|
+
generate_template(filepath) if File.file?(filepath)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def output_path
|
37
|
+
@output_path ||= File.join(OUTPUT_PATH, file_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_name
|
41
|
+
@file_name ||= File.basename(file_path, file_extension)
|
42
|
+
end
|
43
|
+
|
44
|
+
def file_extension
|
45
|
+
File.extname(file_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_template(filepath)
|
49
|
+
TemplateGeneratorService.new(filepath, theme).generate
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<!-- LISTING ALL THE THEMES -->
|
2
|
+
<div class="themes-container">
|
3
|
+
|
4
|
+
<% sorted_themes(@themes).each do |theme| %>
|
5
|
+
<div class="theme theme-col col-md-4 col-sm-6 <%= theme.state %>">
|
6
|
+
<div class="theme-block">
|
7
|
+
<div class="thumbnail cmn-height-block">
|
8
|
+
<%= link_to image_tag(snapshot_path(theme)), admin_theme_templates_path(theme), class: 'img-responsive theme-image' %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="theme-content">
|
12
|
+
<div class="theme-name-area">
|
13
|
+
<h2 class="theme-name"><%= theme.name %></h2>
|
14
|
+
|
15
|
+
<div class="btn-group dropup">
|
16
|
+
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="glyphicon glyphicon-option-horizontal more-action-glyphicon"></i></button>
|
17
|
+
|
18
|
+
<div class="dropdown-menu">
|
19
|
+
<% if theme.drafted? %>
|
20
|
+
<%= link_to Spree.t('actions.compiled'), state_change_admin_theme_path(theme, state: 'compiled'), method: :patch, class: 'dropdown-item' %>
|
21
|
+
<% elsif theme.compiled? %>
|
22
|
+
<%= link_to Spree.t('actions.published'), state_change_admin_theme_path(theme, state: 'published'), method: :patch, class: 'dropdown-item' %>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<% unless theme.published? %>
|
26
|
+
<%= link_to 'Delete', admin_theme_path(theme), method: :delete, class: 'dropdown-item', data: { confirm: 'Are you sure?' } %>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<%= link_to Spree.t('actions.download'), download_admin_theme_path(theme), method: :patch, class: 'dropdown-item action-download' %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div class="theme-action-view">
|
36
|
+
<%= link_to Spree.t(:templates), admin_theme_templates_path(theme) , class: 'btn btn-primary btn-sm' %>
|
37
|
+
<%= link_to Spree.t('actions.preview'), admin_theme_preview_path(theme_id: theme), class: 'btn btn-info btn-sm', target: '_blank' %>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
</div>
|
43
|
+
|
44
|
+
<%= paginate @themes %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= form_for @theme, url: upload_admin_themes_path, method: :post do |f| %>
|
2
|
+
<div class="updload-theme-panel col-md-4 col-sm-6" data-hook="theme-file-upload-form">
|
3
|
+
<div class="upload-theme-col">
|
4
|
+
<label class="cmn-height-block text-center">
|
5
|
+
<%= f.file_field :template_file, class: 'form-control' %>
|
6
|
+
<div class="file-btn-helper">
|
7
|
+
<i class="glyphicon glyphicon-upload upload-icon"></i><br />
|
8
|
+
ADD NEW THEME
|
9
|
+
</div>
|
10
|
+
</label>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="form-actions hidden" data-hook="buttons">
|
15
|
+
<%= f.submit Spree.t('actions.upload'), class: 'btn-success' %>
|
16
|
+
</div>
|
17
|
+
<% end %>
|