coprl 3.0.0.beta.2 → 3.0.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +9 -15
  3. data/CHANGELOG.md +57 -0
  4. data/Gemfile +12 -1
  5. data/Gemfile.lock +105 -36
  6. data/README.md +3 -3
  7. data/app/demo/components/dialogs.pom +1 -1
  8. data/app/demo/components/nav/menu.pom +0 -1
  9. data/app/demo/components/snackbar.pom +9 -3
  10. data/app/demo/events/content_as_form.pom +3 -3
  11. data/app/demo/events/halted.pom +23 -0
  12. data/app/demo/events/nav/drawer.pom +1 -1
  13. data/app/demo/events/tagged_input.pom +2 -2
  14. data/app/demo/patterns/search_select.pom +1 -1
  15. data/app/demo/plugins/animate.pom +144 -0
  16. data/app/demo/plugins/cacheable.pom +64 -0
  17. data/app/demo/plugins/clipboard.pom +21 -0
  18. data/app/demo/plugins/color_picker.pom +17 -0
  19. data/app/demo/plugins/google_maps.pom +24 -0
  20. data/app/demo/plugins/iframe.pom +14 -0
  21. data/app/demo/plugins/image_crop.pom +1 -1
  22. data/app/demo/plugins/markup.pom +14 -0
  23. data/app/demo/plugins/nav/drawer.pom +1 -1
  24. data/app/demo/plugins/script.pom +17 -0
  25. data/app/demo/plugins/scroll_to.pom +22 -0
  26. data/app/demo/plugins/timer.pom +24 -0
  27. data/app/demo/shared/context_list.pom +1 -1
  28. data/config.ru +15 -1
  29. data/coprl.gemspec +1 -2
  30. data/lib/coprl/presenters/cli.rb +10 -0
  31. data/lib/coprl/presenters/dsl/components/actions/base.rb +5 -1
  32. data/lib/coprl/presenters/dsl/components/base.rb +6 -4
  33. data/lib/coprl/presenters/dsl/components/event.rb +6 -1
  34. data/lib/coprl/presenters/dsl/components/multi_select.rb +3 -3
  35. data/lib/coprl/presenters/dsl/components/table.rb +2 -2
  36. data/lib/coprl/presenters/dsl/definition.rb +2 -2
  37. data/lib/coprl/presenters/dsl/user_interface.rb +2 -2
  38. data/lib/coprl/presenters/generators/plugin.rb +21 -6
  39. data/lib/coprl/presenters/generators/templates/plugin/.github/workflows/semantic-release.yml +41 -0
  40. data/lib/coprl/presenters/generators/templates/plugin/.releaserc +15 -0
  41. data/lib/coprl/presenters/generators/templates/plugin/.ruby-version +1 -0
  42. data/lib/coprl/presenters/generators/templates/plugin/README.md.tt +34 -0
  43. data/lib/coprl/presenters/generators/templates/plugin/demo/plugin.pom.tt +15 -0
  44. data/lib/coprl/presenters/generators/templates/plugin/lib/coprl/presenters/plugins/components/actions/dsl.rb.tt +1 -1
  45. data/lib/coprl/presenters/generators/templates/plugin/lib/coprl/presenters/plugins/version.rb.tt +3 -0
  46. data/lib/coprl/presenters/generators/templates/plugin/presenter_plugin.gemspec.tt +8 -7
  47. data/lib/coprl/presenters/generators/templates/plugin/views/components/application/component.erb.tt +1 -1
  48. data/lib/coprl/presenters/helpers/rails.rb +1 -4
  49. data/lib/coprl/presenters/helpers/rails/routes.rb +14 -0
  50. data/lib/coprl/presenters/rails/concerns/coprl_partial.rb +51 -0
  51. data/lib/coprl/presenters/rails/engine.rb +5 -0
  52. data/lib/coprl/presenters/rails/railtie.rb +6 -14
  53. data/lib/coprl/presenters/rails/reloader.rb +15 -0
  54. data/lib/coprl/presenters/settings.rb +1 -1
  55. data/lib/coprl/presenters/version.rb +1 -1
  56. data/lib/coprl/presenters/web_client/helpers/headers.rb +8 -6
  57. data/lib/coprl/presenters/web_client/helpers/rails.rb +1 -0
  58. data/lib/coprl/presenters/web_client/helpers/rails/template_helper.rb +10 -0
  59. data/lib/coprl/presenters/web_client/helpers/sinatra.rb +1 -0
  60. data/lib/coprl/presenters/web_client/helpers/sinatra/template_helper.rb +20 -0
  61. data/lib/coprl/presenters/web_client/plugin_views_path.rb +5 -5
  62. data/public/bundle.js +10 -4
  63. data/public/wc.js +10 -4
  64. data/rails-engine/app/controllers/coprl_controller.rb +1 -1
  65. data/rails-engine/app/views/layouts/coprl.html.erb +4 -4
  66. data/rails-engine/config/initializers/presenters.rb +4 -2
  67. data/rails-engine/config/initializers/routes.rb +5 -0
  68. data/rails-engine/config/initializers/session.rb +8 -0
  69. data/views/mdc/assets/js/components/events/posts.js +10 -6
  70. data/views/mdc/body/{_preamble.erb → _wrapper.erb} +16 -5
  71. data/views/mdc/components/_card.erb +2 -2
  72. data/views/mdc/components/_checkbox.erb +1 -1
  73. data/views/mdc/components/_chip.erb +2 -2
  74. data/views/mdc/components/_content.erb +2 -2
  75. data/views/mdc/components/_datetime.erb +1 -1
  76. data/views/mdc/components/_dialog.erb +2 -2
  77. data/views/mdc/components/_form.erb +2 -2
  78. data/views/mdc/components/_grid.erb +2 -2
  79. data/views/mdc/components/_hidden_field.erb +1 -1
  80. data/views/mdc/components/_multi_select.erb +1 -1
  81. data/views/mdc/components/_number_field.erb +1 -1
  82. data/views/mdc/components/_radio_button.erb +1 -1
  83. data/views/mdc/components/_rich_text_area.erb +1 -1
  84. data/views/mdc/components/_select.erb +1 -1
  85. data/views/mdc/components/_slider.erb +2 -2
  86. data/views/mdc/components/_stepper.erb +4 -4
  87. data/views/mdc/components/_switch.erb +1 -1
  88. data/views/mdc/components/_text_area.erb +1 -1
  89. data/views/mdc/components/_text_field.erb +1 -1
  90. data/views/mdc/components/buttons/_image.erb +1 -1
  91. data/views/mdc/components/unordered_list/_list_item.erb +3 -3
  92. data/views/mdc/layout.erb +4 -4
  93. metadata +33 -30
  94. data/app/demo/components/google_maps.pom +0 -22
  95. data/app/demo/components/snackbar_attached.pom +0 -6
  96. data/lib/coprl/presenters/generators/templates/plugin/README.md +0 -253
  97. data/lib/coprl/presenters/plugins/google_maps.rb +0 -24
  98. data/lib/coprl/presenters/plugins/google_maps/google_map.erb +0 -10
  99. data/lib/coprl/presenters/plugins/google_maps/google_map.rb +0 -41
  100. data/views/mdc/body/_postamble.erb +0 -17
@@ -11,4 +11,4 @@ Any additional data you want to pass to your component javascript should be setu
11
11
  -->
12
12
  <iframe id="random_fact" class='v-plugin v-input' data-plugin-callback='RandomFacts'
13
13
  src="<%%= comp.random_fact %>" height=512
14
- <%%= erb :"components/event", :locals => {comp: comp, events: comp.events, parent_id: comp.id} %>></iframe>
14
+ <%%= partial "components/event", :locals => {comp: comp, events: comp.events, parent_id: comp.id} %>></iframe>
@@ -6,12 +6,9 @@ if defined?(Rails)
6
6
  include ActionView::Helpers::AssetUrlHelper
7
7
  include Coprl::Presenters::Helpers::Rails::Currency
8
8
  include Coprl::Presenters::Helpers::Rails::ModelTable
9
+ include Coprl::Presenters::Helpers::Rails::Routes
9
10
  include Namespace
10
11
 
11
- def default_url_options
12
- {}
13
- end
14
-
15
12
  def presenters_path(presenter, host: false, **params)
16
13
  presenter = _expand_namespace_(presenter, namespace)
17
14
  presenter = presenter.gsub(':', '/')
@@ -0,0 +1,14 @@
1
+ module Coprl
2
+ module Presenters
3
+ module Helpers
4
+ module Rails
5
+ module Routes
6
+ include ::Rails.application.routes.url_helpers
7
+ def default_url_options
8
+ ::Rails.application.config.action_controller.default_url_options || { :host => context[:request].host_with_port }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ module Coprl
2
+ module Presenters
3
+ module Rails
4
+ module Concerns
5
+ module CoprlPartial
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :set_view_path
10
+ end
11
+
12
+ module ClassMethods
13
+ @plugins = []
14
+ def presenter_plugin(*plugins)
15
+ @plugins += Array(plugins)
16
+ end
17
+
18
+ def plugins
19
+ @plugins
20
+ end
21
+ end
22
+
23
+ def set_view_path
24
+ paths = Coprl::Presenters::WebClient::PluginViewsPath.new(pom: nil, plugins: self.class.plugins).render
25
+ paths.each do |path|
26
+ prepend_view_path path
27
+ end
28
+ end
29
+
30
+ def prepare_context(base_params = params)
31
+ prepare_context = Coprl::Presenters::Settings.config.presenters.web_client.prepare_context.dup
32
+ prepare_context.push(method(:scrub_context))
33
+ context = base_params.dup.to_unsafe_hash
34
+ prepare_context.reduce(context) do |params, context_proc|
35
+ context = context_proc.call(params, session, request.env)
36
+ end
37
+ context
38
+ end
39
+
40
+ def scrub_context(params, _session, _env)
41
+ %i(grid_nesting input_tag).each do |key|
42
+ params.delete(key) {params.delete(key.to_s)}
43
+ end
44
+ params
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -12,6 +12,11 @@ module Coprl
12
12
  # TODO: should rename these since they are common names that are likely going to collide
13
13
  app.middleware.use ::ActionDispatch::Static, File.join(root, '..', 'public')
14
14
  end
15
+
16
+ ActiveSupport.on_load(:action_controller) do
17
+ include Concerns::CoprlPartial
18
+ end
19
+
15
20
  end
16
21
  end
17
22
  end
@@ -1,5 +1,3 @@
1
- require 'filewatcher'
2
-
3
1
  module Coprl
4
2
  module Presenters
5
3
  module Rails
@@ -15,20 +13,14 @@ module Coprl
15
13
 
16
14
  WATCH = -> {
17
15
  return unless ::Rails.env.development?
16
+
18
17
  path = ::Rails.root.join('app', '**', '*.pom')
19
- puts "Watching #{path} for changes..."
20
- filewatcher = Filewatcher.new(path)
21
- Thread.new(filewatcher) do |fw|
22
- fw.watch do |f|
23
- puts "Detected updated POM file: #{f}"
24
- begin
25
- BOOT.call
26
- rescue Exception => exc
27
- puts exc.backtrace
28
- puts exc.message
29
- end
30
- end
18
+ file_watcher = ActiveSupport::FileUpdateChecker.new(Dir[path]) do
19
+ BOOT.call
31
20
  end
21
+
22
+ ::Rails.application.reloaders << Reloader.new(file_watcher)
23
+
32
24
  } unless defined?(WATCH)
33
25
 
34
26
  config.after_initialize do
@@ -0,0 +1,15 @@
1
+ module Coprl
2
+ module Presenters
3
+ module Rails
4
+ class Reloader
5
+ def initialize(file_watcher)
6
+ @file_watcher = file_watcher
7
+ end
8
+
9
+ def updated?
10
+ @file_watcher.execute_if_updated
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -32,7 +32,7 @@ unless defined?(Coprl::Presenters::Settings)
32
32
  setting :custom_css, '../public/presenters'
33
33
  setting :protect_from_forgery, false
34
34
  end
35
- setting :plugins, [:google_maps]
35
+ setting :plugins, []
36
36
  setting :components do
37
37
  setting :defaults do
38
38
  setting :datetime do
@@ -1,7 +1,7 @@
1
1
  module Coprl
2
2
  module Presenters
3
3
  module Version
4
- VERSION = '3.0.0.beta.2'
4
+ VERSION = '3.0.0.beta.7'
5
5
  end
6
6
  end
7
7
  end
@@ -2,16 +2,18 @@ module Coprl::Presenters::WebClient::Helpers
2
2
  module Headers
3
3
  include Coprl::Presenters::WebClient::Helpers::HtmlSafe
4
4
 
5
- def coprl_headers(base_url, request, pom)
5
+ def coprl_headers
6
+ return unless @pom
7
+
6
8
  html_safe (<<~HEADERS)
7
9
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" type="text/css">
8
10
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
9
11
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
10
- <link rel="stylesheet" href="#{ base_url }/#{ request.env['SCRIPT_NAME'] }bundle.css">
11
- <script defer src="#{ base_url }/#{ request.env['SCRIPT_NAME'] }bundle.js"></script>
12
- #{plugin_headers(pom)}
13
- #{custom_css(request.env['REQUEST_PATH'], base_url)}
14
- #{pom.csrf_meta_tags}
12
+ <link rel="stylesheet" href="#{ @base_url }/#{ request.env['SCRIPT_NAME'] }bundle.css">
13
+ <script defer src="#{ @base_url }/#{ request.env['SCRIPT_NAME'] }bundle.js"></script>
14
+ #{plugin_headers(@pom)}
15
+ #{custom_css(request.env['REQUEST_PATH'], @base_url)}
16
+ #{@pom.csrf_meta_tags}
15
17
  HEADERS
16
18
  end
17
19
 
@@ -8,5 +8,6 @@ module Coprl::Presenters::WebClient::Helpers
8
8
  include Namespaced
9
9
  include Raw
10
10
  include EscapeHtml
11
+ include TemplateHelper
11
12
  end
12
13
  end
@@ -0,0 +1,10 @@
1
+ module Coprl::Presenters::WebClient::Helpers::Rails
2
+ module TemplateHelper
3
+ def with_presenters_wrapper(&block)
4
+ render partial: 'body/wrapper', locals: {
5
+ body_content: capture(&block), header: @pom&.header, drawer: @pom&.drawer,
6
+ footer: @pom&.footer
7
+ }
8
+ end
9
+ end
10
+ end
@@ -7,5 +7,6 @@ module Coprl::Presenters::WebClient::Helpers
7
7
  include SafeMarkdown
8
8
  include Raw
9
9
  include EscapeHtml
10
+ include TemplateHelper
10
11
  end
11
12
  end
@@ -0,0 +1,20 @@
1
+ module Coprl::Presenters::WebClient::Helpers::Sinatra
2
+ module TemplateHelper
3
+ def with_presenters_wrapper(&block)
4
+ buffer << partial("body/wrapper", locals: {
5
+ body_content: capture(buffer, &block), header: @pom&.header, drawer: @pom&.drawer,
6
+ footer: @pom&.footer
7
+ })
8
+ end
9
+
10
+ def buffer()
11
+ @_out_buf
12
+ end
13
+
14
+ def capture(buffer)
15
+ pos = buffer.size
16
+ yield
17
+ buffer.slice!(pos..buffer.size)
18
+ end
19
+ end
20
+ end
@@ -8,17 +8,17 @@ module Coprl
8
8
  extend Pluggable
9
9
  include_plugins(:WebClientComponents)
10
10
 
11
- def initialize(pom:)
11
+ def initialize(pom: nil, plugins: nil)
12
12
  @pom = pom
13
+ @plugins = plugins || []
13
14
  initialize_plugins
14
15
  end
15
16
 
16
17
  def render
17
18
  results = []
18
- ((@plugins||[]) + Coprl::Presenters::Settings.config.presenters.plugins).each do |plugin|
19
+ (@plugins + Coprl::Presenters::Settings.config.presenters.plugins).each do |plugin|
19
20
  view_dir_method = :"view_dir_#{plugin}"
20
- results << send(view_dir_method,
21
- @pom) if respond_to?(view_dir_method)
21
+ results << send(view_dir_method, @pom) if respond_to?(view_dir_method)
22
22
  end
23
23
  results
24
24
  end
@@ -26,7 +26,7 @@ module Coprl
26
26
  private
27
27
 
28
28
  def initialize_plugins
29
- @plugins = @pom.send(:plugins)
29
+ @plugins += @pom.send(:plugins) if @pom
30
30
  self.class.include_plugins(:WebClientComponents, plugins: @plugins)
31
31
  end
32
32
  end
data/public/bundle.js CHANGED
@@ -48739,9 +48739,7 @@ var VPosts = function (_VBase) {
48739
48739
  }
48740
48740
  }
48741
48741
 
48742
- var paramCount = Array.from(formData).length;
48743
-
48744
- if (paramCount < 1) {
48742
+ if (this.paramCount(formData) < 1) {
48745
48743
  console.warn('Creating request with no data!' + ' Are you sure you\'ve hooked everything up correctly?');
48746
48744
  }
48747
48745
 
@@ -48910,9 +48908,17 @@ var VPosts = function (_VBase) {
48910
48908
  }
48911
48909
 
48912
48910
  // Send our FormData object; HTTP headers are set automatically
48913
- httpRequest.send(formData);
48911
+ // Rack 2.2 will throw an exception https://github.com/rack/rack/issues/1603
48912
+ // if we set the header as multi-part form data with no data in the body
48913
+ // So we set the body to null in this case.
48914
+ httpRequest.send(_this2.paramCount(formData) < 1 ? null : formData);
48914
48915
  });
48915
48916
  }
48917
+ }, {
48918
+ key: 'paramCount',
48919
+ value: function paramCount(formData) {
48920
+ return Array.from(formData).length;
48921
+ }
48916
48922
  }, {
48917
48923
  key: 'isForm',
48918
48924
  value: function isForm() {
data/public/wc.js CHANGED
@@ -34213,9 +34213,7 @@ var VPosts = function (_VBase) {
34213
34213
  }
34214
34214
  }
34215
34215
 
34216
- var paramCount = Array.from(formData).length;
34217
-
34218
- if (paramCount < 1) {
34216
+ if (this.paramCount(formData) < 1) {
34219
34217
  console.warn('Creating request with no data!' + ' Are you sure you\'ve hooked everything up correctly?');
34220
34218
  }
34221
34219
 
@@ -34384,9 +34382,17 @@ var VPosts = function (_VBase) {
34384
34382
  }
34385
34383
 
34386
34384
  // Send our FormData object; HTTP headers are set automatically
34387
- httpRequest.send(formData);
34385
+ // Rack 2.2 will throw an exception https://github.com/rack/rack/issues/1603
34386
+ // if we set the header as multi-part form data with no data in the body
34387
+ // So we set the body to null in this case.
34388
+ httpRequest.send(_this2.paramCount(formData) < 1 ? null : formData);
34388
34389
  });
34389
34390
  }
34391
+ }, {
34392
+ key: 'paramCount',
34393
+ value: function paramCount(formData) {
34394
+ return Array.from(formData).length;
34395
+ }
34390
34396
  }, {
34391
34397
  key: 'isForm',
34392
34398
  value: function isForm() {
@@ -12,7 +12,7 @@ class CoprlController < ApplicationController
12
12
  fq_presenter_name = [presenter_name, 'index'].compact.join(':')
13
13
  presenter_name = fq_presenter_name if Coprl::Presenters::App.registered?(fq_presenter_name)
14
14
  presenter = Coprl::Presenters::App[presenter_name].call
15
- context = params.dup.to_unsafe_hash
15
+ context = prepare_context(params)
16
16
  router = Coprl::Presenters::WebClient::Router.new(base_url: request.base_url)
17
17
  @pom = presenter.expand(router: router, context: context)
18
18
  end
@@ -7,14 +7,14 @@
7
7
  <title><%= @pom.page.title if @pom.page %></title>
8
8
  <meta name="description" content="">
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1">
10
- <%= coprl_headers(@base_url, request, @pom) %>
10
+ <%= coprl_headers %>
11
11
  <%= csrf_meta_tags %>
12
12
  <%= csp_meta_tag if defined?(csp_meta_tag) %>
13
13
  </head>
14
14
  <body>
15
- <%= partial "body/preamble", :locals => {pom:@pom} %>
16
- <%= yield %>
17
- <%= partial "body/postamble", :locals => {pom:@pom} %>
15
+ <%= with_presenters_wrapper do %>
16
+ <%= yield %>
17
+ <% end %>
18
18
  <noscript><p>JavaScript must be enabled to continue.</p></noscript>
19
19
  </body>
20
20
  </html>
@@ -28,9 +28,11 @@ class CoprlTemplateHandler
28
28
  # otherwise we need to get the presenter name built from params
29
29
  presenter_name = local_assigns[:presenter] ? presenter : namespaced_presenter(params)
30
30
  presenter = Coprl::Presenters::App[presenter_name].call
31
- context = params.dup.to_unsafe_hash
31
+ context = self.controller.prepare_context(params)
32
32
  router = Coprl::Presenters::WebClient::Router.new(base_url: request.base_url)
33
- @pom = presenter.expand(router: router, context: context)
33
+ plugins = self.controller.class.plugins
34
+
35
+ @pom = presenter.expand(router: router, context: context, plugins: plugins)
34
36
  end
35
37
  #{source}
36
38
  end
@@ -0,0 +1,5 @@
1
+ Rails.application.config.to_prepare do
2
+ Coprl::Presenters::Settings.configure do |config|
3
+ config.presenters.helpers << Coprl::Presenters::Helpers::Rails
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ Rails.application.config.to_prepare do
2
+ Coprl::Presenters::Settings.configure do |config|
3
+ config.presenters.web_client.prepare_context.prepend ->(context, session, _env) {
4
+ context.merge(request: Rack::Request.new(_env),
5
+ session: session)
6
+ }
7
+ end
8
+ end
@@ -59,13 +59,10 @@ export class VPosts extends VBase {
59
59
  formData.append(name, encode(value));
60
60
  }
61
61
 
62
- const paramCount = Array.from(formData).length;
63
-
64
- if (paramCount < 1) {
62
+ if (this.paramCount(formData) < 1) {
65
63
  console.warn(
66
64
  'Creating request with no data!'
67
- + ' Are you sure you\'ve hooked everything up correctly?',
68
- );
65
+ + ' Are you sure you\'ve hooked everything up correctly?');
69
66
  }
70
67
 
71
68
  let errors = this.validate(formData);
@@ -185,10 +182,17 @@ export class VPosts extends VBase {
185
182
  }
186
183
 
187
184
  // Send our FormData object; HTTP headers are set automatically
188
- httpRequest.send(formData);
185
+ // Rack 2.2 will throw an exception https://github.com/rack/rack/issues/1603
186
+ // if we set the header as multi-part form data with no data in the body
187
+ // So we set the body to null in this case.
188
+ httpRequest.send(this.paramCount(formData) < 1 ? null : formData);
189
189
  });
190
190
  }
191
191
 
192
+ paramCount(formData){
193
+ return Array.from(formData).length;
194
+ }
195
+
192
196
  isForm() {
193
197
  var parentElement = this.parentElement();
194
198
  return parentElement && parentElement.elements;
@@ -1,15 +1,10 @@
1
1
  <%
2
- header = pom.header
3
- drawer = pom.drawer
4
- footer = pom.footer
5
-
6
2
  root_classes = []
7
3
  root_classes << 'v-root--header-present' if header
8
4
  root_classes << 'v-root--drawer-present' if drawer
9
5
  root_classes << 'v-root--footer-present' if footer
10
6
  %>
11
7
  <div class="v-root compatibility-wrapper mdc-typography <%= root_classes.join(' ') %>">
12
-
13
8
  <% if drawer %>
14
9
  <div class="drawer-frame-root">
15
10
  <%= partial "body/dismissable-drawer", :locals => {drawer: drawer} %>
@@ -22,5 +17,21 @@
22
17
  <div class="drawer-main-content">
23
18
  <%= partial "body/modal-drawer", :locals => {drawer: drawer} %>
24
19
  <% end %>
20
+
21
+ <%= body_content %>
22
+
25
23
  <div class="v-page-content page-content">
26
24
  <div class="v-errors">
25
+ </div>
26
+ </div>
27
+ <% if drawer %>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <% end %>
32
+
33
+ <%= partial "body/snackbar" %>
34
+ <% if footer %>
35
+ <%= partial "body/footer", :locals => {:footer => footer} %>
36
+ <% end %>
37
+ </div>