lookbook 1.5.0 → 2.0.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -21
  3. data/app/assets/lookbook/css/lookbook.css +9 -0
  4. data/app/assets/lookbook/css/themes/blue.css +7 -0
  5. data/app/assets/lookbook/css/themes/green.css +7 -0
  6. data/app/assets/lookbook/css/themes/indigo.css +7 -0
  7. data/app/assets/lookbook/css/themes/rose.css +7 -0
  8. data/app/assets/lookbook/css/themes/zinc.css +7 -0
  9. data/app/assets/lookbook/css/tooltip.css +9 -6
  10. data/app/assets/lookbook/img/lucide-sprite.svg +4960 -0
  11. data/app/assets/lookbook/js/app.js +22 -4
  12. data/app/assets/lookbook/js/helpers/dom.js +4 -7
  13. data/app/assets/lookbook/js/helpers/string.js +4 -11
  14. data/app/assets/lookbook/js/{embed.js → iframe.js} +0 -0
  15. data/app/assets/lookbook/js/index.js +61 -0
  16. data/app/assets/lookbook/js/lib/lookbook.js +113 -0
  17. data/app/assets/lookbook/js/lib/tippy.js +1 -0
  18. data/app/assets/lookbook/js/lookbook-core.js +1 -0
  19. data/app/assets/lookbook/js/lookbook.js +2 -61
  20. data/app/components/lookbook/base_component.rb +3 -1
  21. data/app/components/lookbook/button/component.html.erb +13 -24
  22. data/app/components/lookbook/button/component.js +13 -3
  23. data/app/components/lookbook/button/component.rb +16 -25
  24. data/app/components/lookbook/code/component.rb +0 -2
  25. data/app/components/lookbook/copy_button/component.html.erb +4 -4
  26. data/app/components/lookbook/copy_button/component.rb +6 -3
  27. data/app/components/lookbook/debug_menu/component.html.erb +1 -0
  28. data/app/components/lookbook/debug_menu/component.rb +12 -1
  29. data/app/components/lookbook/display_options/editor/component.html.erb +1 -1
  30. data/app/components/lookbook/display_options/field/component.css +0 -26
  31. data/app/components/lookbook/display_options/field/component.html.erb +1 -1
  32. data/app/components/lookbook/embed/component.html.erb +6 -51
  33. data/app/components/lookbook/embed/component.rb +17 -16
  34. data/app/components/lookbook/embed/inspector/component.html.erb +102 -0
  35. data/app/components/lookbook/embed/inspector/component.js +46 -0
  36. data/app/components/lookbook/embed/inspector/component.rb +64 -0
  37. data/app/components/lookbook/embed_code_dropdown/component.css +12 -0
  38. data/app/components/lookbook/embed_code_dropdown/component.html.erb +19 -0
  39. data/app/components/lookbook/embed_code_dropdown/component.js +26 -0
  40. data/app/components/lookbook/embed_code_dropdown/component.rb +41 -0
  41. data/app/components/lookbook/header/component.html.erb +7 -6
  42. data/app/components/lookbook/header/component.rb +5 -1
  43. data/app/components/lookbook/icon/component.html.erb +1 -1
  44. data/app/components/lookbook/icon_button/component.html.erb +20 -0
  45. data/app/components/lookbook/icon_button/component.rb +46 -0
  46. data/app/components/lookbook/nav/component.html.erb +0 -1
  47. data/app/components/lookbook/nav/entity/component.rb +2 -2
  48. data/app/components/lookbook/nav/item/component.rb +1 -1
  49. data/app/components/lookbook/params/editor/component.rb +1 -0
  50. data/app/components/lookbook/prose/component.rb +1 -3
  51. data/app/components/lookbook/tabs/component.html.erb +2 -2
  52. data/app/components/lookbook/tabs/component.js +1 -1
  53. data/app/components/lookbook/tag_component.rb +2 -1
  54. data/app/components/lookbook/text_button/component.html.erb +26 -0
  55. data/app/components/lookbook/text_button/component.rb +42 -0
  56. data/app/components/lookbook/toolbar/component.html.erb +1 -1
  57. data/app/components/lookbook/viewport/component.rb +0 -4
  58. data/app/controllers/concerns/lookbook/targetable_concern.rb +24 -19
  59. data/app/controllers/concerns/lookbook/with_panels_concern.rb +30 -0
  60. data/app/controllers/concerns/lookbook/with_preview_controller_concern.rb +4 -3
  61. data/app/controllers/lookbook/application_controller.rb +9 -11
  62. data/app/controllers/lookbook/embeds_controller.rb +148 -0
  63. data/app/controllers/lookbook/inspector_controller.rb +3 -22
  64. data/app/controllers/lookbook/page_controller.rb +7 -6
  65. data/app/controllers/lookbook/pages_controller.rb +3 -4
  66. data/app/controllers/lookbook/preview_controller.rb +17 -0
  67. data/app/controllers/lookbook/previews_controller.rb +25 -7
  68. data/app/helpers/lookbook/application_helper.rb +3 -19
  69. data/app/views/layouts/lookbook/application.html.erb +85 -60
  70. data/app/views/layouts/lookbook/embed.html.erb +16 -0
  71. data/app/views/layouts/lookbook/shell.html.erb +1 -1
  72. data/app/views/layouts/lookbook/skeleton.html.erb +13 -8
  73. data/app/views/lookbook/embeds/show.html.erb +12 -0
  74. data/app/views/lookbook/inspector/panels/_notes.html.erb +1 -1
  75. data/app/views/lookbook/inspector/panels/_output.html.erb +3 -3
  76. data/app/views/lookbook/inspector/panels/_source.html.erb +6 -6
  77. data/app/views/lookbook/inspector/show.html.erb +130 -123
  78. data/app/views/lookbook/pages/show.html.erb +81 -34
  79. data/app/views/lookbook/partials/_iframe_content_scripts.html.erb +1 -0
  80. data/app/views/lookbook/partials/_user_styles.html.erb +5 -0
  81. data/app/views/lookbook/{preview.html.erb → previews/group.html.erb} +7 -7
  82. data/app/views/lookbook/previews/preview.html.erb +5 -0
  83. data/app/views/lookbook/previews/show.html.erb +1 -0
  84. data/config/app.yml +31 -16
  85. data/config/panels.yml +23 -25
  86. data/config/routes.rb +4 -1
  87. data/config/tags.yml +6 -2
  88. data/lib/lookbook/cable/cable.rb +53 -0
  89. data/{app/channels/lookbook → lib/lookbook/cable}/connection.rb +0 -0
  90. data/{app/channels/lookbook → lib/lookbook/cable}/reload_channel.rb +0 -0
  91. data/lib/lookbook/engine.rb +109 -87
  92. data/lib/lookbook/entities/collections/entity_collection.rb +11 -6
  93. data/lib/lookbook/entities/collections/page_collection.rb +33 -8
  94. data/lib/lookbook/entities/collections/preview_collection.rb +42 -17
  95. data/lib/lookbook/entities/collections/render_target_collection.rb +4 -0
  96. data/lib/lookbook/entities/collections/scenario_collection.rb +4 -0
  97. data/lib/lookbook/entities/concerns/{annotatable.rb → annotatable_entity.rb} +7 -6
  98. data/lib/lookbook/entities/concerns/{inspectable.rb → inspectable_entity.rb} +2 -1
  99. data/lib/lookbook/entities/concerns/{locatable.rb → locatable_entity.rb} +8 -14
  100. data/lib/lookbook/entities/concerns/navigable_entity.rb +44 -0
  101. data/lib/lookbook/entities/entity.rb +7 -2
  102. data/lib/lookbook/entities/{page.rb → page_entity.rb} +10 -6
  103. data/lib/lookbook/entities/{page_section.rb → page_section_entity.rb} +1 -1
  104. data/lib/lookbook/entities/preview_entity.rb +99 -0
  105. data/lib/lookbook/entities/renderable_entity.rb +50 -0
  106. data/lib/lookbook/entities/rendered_scenario_entity.rb +53 -0
  107. data/lib/lookbook/entities/scenario_entity.rb +112 -0
  108. data/lib/lookbook/entities/scenario_group_entity.rb +53 -0
  109. data/lib/lookbook/error.rb +5 -5
  110. data/lib/lookbook/file_watcher.rb +19 -35
  111. data/lib/lookbook/helpers/class_names_helper.rb +28 -0
  112. data/lib/lookbook/helpers/page_helper.rb +18 -0
  113. data/{app/helpers/lookbook → lib/lookbook/helpers}/preview_helper.rb +3 -0
  114. data/lib/lookbook/helpers/ui_elements_helper.rb +115 -0
  115. data/lib/lookbook/preview.rb +79 -0
  116. data/lib/lookbook/preview_controller_actions.rb +50 -0
  117. data/lib/lookbook/preview_parser.rb +4 -2
  118. data/lib/lookbook/reloaders.rb +71 -0
  119. data/lib/lookbook/runtime_context.rb +49 -0
  120. data/lib/lookbook/services/data/resolvers/data_resolver.rb +4 -6
  121. data/lib/lookbook/services/entities/entity_tree_builder.rb +6 -6
  122. data/lib/lookbook/services/list_resolver.rb +35 -0
  123. data/lib/lookbook/services/markdown_renderer.rb +12 -2
  124. data/lib/lookbook/services/priority_prefix_parser.rb +16 -0
  125. data/lib/lookbook/services/urls/search_param_encoder.rb +16 -0
  126. data/lib/lookbook/services/urls/search_param_parser.rb +7 -6
  127. data/lib/lookbook/stores/config_store.rb +16 -16
  128. data/lib/lookbook/stores/input_store.rb +1 -3
  129. data/lib/lookbook/stores/panel_store.rb +28 -50
  130. data/lib/lookbook/support/deprecation.rb +5 -0
  131. data/lib/lookbook/support/errors/preview_template_error.rb +7 -0
  132. data/lib/lookbook/support/evented_file_update_checker.rb +69 -0
  133. data/lib/lookbook/support/null_websocket.rb +9 -0
  134. data/lib/lookbook/support/store.rb +9 -0
  135. data/lib/lookbook/support/tree_node.rb +7 -7
  136. data/lib/lookbook/support/utils/path_utils.rb +7 -1
  137. data/lib/lookbook/support/utils/utils.rb +8 -0
  138. data/lib/lookbook/tags/{position_tag.rb → priority_tag.rb} +4 -4
  139. data/lib/lookbook/tags/renders_tag.rb +4 -0
  140. data/lib/lookbook/tags/tag_provider.rb +3 -0
  141. data/lib/lookbook/tags/type_tag.rb +7 -0
  142. data/lib/lookbook/tags/yard_tag.rb +1 -2
  143. data/lib/lookbook/version.rb +1 -1
  144. data/lib/lookbook/websocket.rb +6 -53
  145. data/lib/lookbook.rb +179 -53
  146. data/public/lookbook-assets/css/lookbook.css +141 -83
  147. data/public/lookbook-assets/css/lookbook.css.map +1 -1
  148. data/public/lookbook-assets/css/themes/blue.css +7 -0
  149. data/public/lookbook-assets/css/themes/blue.css.map +1 -1
  150. data/public/lookbook-assets/css/themes/green.css +7 -0
  151. data/public/lookbook-assets/css/themes/green.css.map +1 -1
  152. data/public/lookbook-assets/css/themes/indigo.css +7 -0
  153. data/public/lookbook-assets/css/themes/indigo.css.map +1 -1
  154. data/public/lookbook-assets/css/themes/rose.css +7 -0
  155. data/public/lookbook-assets/css/themes/rose.css.map +1 -1
  156. data/public/lookbook-assets/css/themes/zinc.css +7 -0
  157. data/public/lookbook-assets/css/themes/zinc.css.map +1 -1
  158. data/public/lookbook-assets/img/lucide-sprite.svg +4960 -0
  159. data/public/lookbook-assets/js/embed.js +1363 -841
  160. data/public/lookbook-assets/js/embed.js.map +1 -1
  161. data/public/lookbook-assets/js/iframe.js +906 -0
  162. data/public/lookbook-assets/js/iframe.js.map +1 -0
  163. data/public/lookbook-assets/js/index.js +13567 -0
  164. data/public/lookbook-assets/js/index.js.map +1 -0
  165. data/public/lookbook-assets/js/lookbook-core.js +85 -0
  166. data/public/lookbook-assets/js/lookbook-core.js.map +1 -0
  167. data/public/lookbook-assets/js/lookbook.js +164 -12638
  168. data/public/lookbook-assets/js/lookbook.js.map +1 -1
  169. data/public/lookbook-assets/lookbook-esm.js +1427 -0
  170. data/public/lookbook-assets/lookbook-esm.js.map +1 -0
  171. data/public/lookbook-assets/lookbook-global.js +1427 -0
  172. data/public/lookbook-assets/lookbook-global.js.map +1 -0
  173. data/public/lookbook-assets/lookbook.js +1427 -0
  174. data/public/lookbook-assets/lookbook.js.map +1 -0
  175. metadata +80 -72
  176. data/app/components/lookbook/embed/component.js +0 -39
  177. data/app/helpers/lookbook/component_helper.rb +0 -84
  178. data/app/helpers/lookbook/output_helper.rb +0 -19
  179. data/app/helpers/lookbook/page_helper.rb +0 -34
  180. data/app/views/layouts/lookbook/inspector.html.erb +0 -7
  181. data/app/views/layouts/lookbook/page.html.erb +0 -53
  182. data/app/views/layouts/lookbook/standalone.html.erb +0 -5
  183. data/lib/lookbook/entities/collections/component_collection.rb +0 -4
  184. data/lib/lookbook/entities/collections/preview_example_collection.rb +0 -4
  185. data/lib/lookbook/entities/component.rb +0 -31
  186. data/lib/lookbook/entities/concerns/navigable.rb +0 -43
  187. data/lib/lookbook/entities/preview.rb +0 -87
  188. data/lib/lookbook/entities/preview_example.rb +0 -100
  189. data/lib/lookbook/entities/preview_group.rb +0 -48
  190. data/lib/lookbook/preview_actions.rb +0 -43
  191. data/lib/lookbook/process.rb +0 -21
  192. data/lib/lookbook/rendered_example.rb +0 -37
  193. data/lib/lookbook/services/position_prefix_parser.rb +0 -16
  194. data/lib/lookbook/services/urls/search_param_builder.rb +0 -13
  195. data/lib/lookbook/tags/component_tag.rb +0 -13
@@ -0,0 +1,49 @@
1
+ module Lookbook
2
+ class RuntimeContext
3
+ attr_reader :env
4
+
5
+ def initialize(env: Rails.env)
6
+ @env = env
7
+ end
8
+
9
+ def app_name
10
+ return @_app_name if @_app_name
11
+
12
+ app_class = Rails.application.class
13
+ name = app_class.respond_to?(:module_parent_name) ? app_class.module_parent_name : app_class.parent_name
14
+ @_app_name ||= name.underscore
15
+ end
16
+
17
+ def rails_older_than?(version)
18
+ Gem::Version.new(Rails.version) < Gem::Version.new(version)
19
+ end
20
+
21
+ def rails_newer_than?(version)
22
+ Gem::Version.new(Rails.version) >= Gem::Version.new(version)
23
+ end
24
+
25
+ def actioncable_installed?
26
+ gem_installed?("actioncable")
27
+ end
28
+
29
+ def listen_installed?
30
+ gem_installed?("listen")
31
+ end
32
+
33
+ def gem_installed?(name)
34
+ Gem.loaded_specs.has_key?(name)
35
+ end
36
+
37
+ def web?
38
+ !rake_task? && !Rails.const_defined?(:Console)
39
+ end
40
+
41
+ def rake_task?
42
+ if defined?(Rake) && Rake.respond_to?(:application)
43
+ File.basename($0) == "rake" || Rake.application.top_level_tasks.any?
44
+ else
45
+ false
46
+ end
47
+ end
48
+ end
49
+ end
@@ -5,10 +5,9 @@ module Lookbook
5
5
 
6
6
  attr_reader :eval_context, :base_dir, :file, :fallback
7
7
 
8
- def initialize(input, eval_context: nil, permit_eval: false, fail_silently: false, base_dir: Rails.root, file: nil, fallback: nil)
8
+ def initialize(input, eval_context: nil, fail_silently: false, base_dir: Rails.root, file: nil, fallback: nil)
9
9
  @input = input.to_s
10
10
  @eval_context = eval_context
11
- @permit_eval = permit_eval
12
11
  @fail_silently = fail_silently
13
12
  @fallback = fallback
14
13
  @base_dir = base_dir.to_s
@@ -44,7 +43,9 @@ module Lookbook
44
43
  def evaluate(input, fallback = @fallback)
45
44
  if evaluatable?
46
45
  begin
47
- eval_context.instance_eval(input.to_s)
46
+ proc {
47
+ eval_context.instance_eval(input.to_s)
48
+ }.call
48
49
  rescue => exception
49
50
  raise_error "Could not evaluate statetment (#{exception.message})", exception
50
51
  end
@@ -61,9 +62,6 @@ module Lookbook
61
62
  private
62
63
 
63
64
  def evaluatable?
64
- if !@permit_eval
65
- raise_error "Runtime evaluation is not permitted"
66
- end
67
65
  eval_context.present?
68
66
  end
69
67
  end
@@ -13,15 +13,15 @@ module Lookbook
13
13
  current_node = root_node
14
14
  path_segments = parse_segments(entity.logical_path)
15
15
  path_segments.each.with_index(1) do |segment, i|
16
- name, position_prefix = segment
16
+ name, priority_prefix = segment
17
17
  content = entity if entity.depth == i # entities are always on the leaf nodes
18
18
 
19
- current_node.add_child(name, content, position: position_prefix) unless current_node.has_child?(name)
19
+ current_node.add_child(name, content, priority: priority_prefix) unless current_node.has_child?(name)
20
20
  current_node = current_node.get_child(name)
21
21
 
22
22
  if content && content.type == :preview
23
- content.visible_examples.each do |example|
24
- current_node.add_child(example.name, example)
23
+ content.visible_scenarios.each do |scenario|
24
+ current_node.add_child(scenario.name, scenario)
25
25
  end
26
26
  end
27
27
  end
@@ -32,8 +32,8 @@ module Lookbook
32
32
  def parse_segments(path)
33
33
  path.split("/").map do |segment|
34
34
  unless segment.start_with?(".")
35
- position, name = PositionPrefixParser.call(segment)
36
- [name, position || 10000]
35
+ priority, name = PriorityPrefixParser.call(segment)
36
+ [name, priority || 10000]
37
37
  end
38
38
  end.compact
39
39
  end
@@ -0,0 +1,35 @@
1
+ module Lookbook
2
+ class ListResolver < Service
3
+ attr_reader :item_set
4
+
5
+ def initialize(to_include = nil, item_set = nil)
6
+ @to_include = to_include
7
+ @item_set = Array(item_set).compact.uniq.map(&:to_s)
8
+ end
9
+
10
+ def call(&resolver)
11
+ included = to_include.inject([]) do |result, name|
12
+ if name == "*"
13
+ result += item_set.select { |item| !result.include?(item) }
14
+ elsif item_set.include?(name)
15
+ result << name
16
+ end
17
+ result
18
+ end
19
+
20
+ resolved = resolver ? included.map { |item| resolver.call(item) } : included
21
+ resolved.compact
22
+ end
23
+
24
+ def to_include
25
+ case @to_include
26
+ when true
27
+ ["*"]
28
+ when false
29
+ []
30
+ else
31
+ Array(@to_include).compact.uniq.map(&:to_s)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -6,7 +6,7 @@ module Lookbook
6
6
 
7
7
  def initialize(text, opts = {})
8
8
  @text = text
9
- @opts = opts.to_h
9
+ @opts = Lookbook.config.markdown_options.merge(opts.to_h)
10
10
  end
11
11
 
12
12
  def call
@@ -18,7 +18,17 @@ module Lookbook
18
18
  class LookbookMarkdownRenderer < Redcarpet::Render::HTML
19
19
  def block_code(code, language = "ruby")
20
20
  line_numbers = language.to_s.end_with? "-numbered"
21
- ApplicationController.render(Lookbook::Code::Component.new(source: code, language: language.to_s.chomp("-numbered"), line_numbers: line_numbers), layout: nil)
21
+ ApplicationController.render(Lookbook::Code::Component.new(**{
22
+ source: code,
23
+ language: language.to_s.chomp("-numbered"),
24
+ line_numbers: line_numbers
25
+ }), layout: nil)
26
+ end
27
+
28
+ def postprocess(full_document)
29
+ full_document&.gsub!("<p><lookbook", "<lookbook")
30
+ full_document&.gsub!("</lookbook-embed></p>", "</lookbook-embed>")
31
+ full_document
22
32
  end
23
33
  end
24
34
  end
@@ -0,0 +1,16 @@
1
+ module Lookbook
2
+ class PriorityPrefixParser < Service
3
+ PRIORITY_PREFIX_REGEX = /^(\d+?)[-_]/
4
+
5
+ attr_reader :input
6
+
7
+ def initialize(input)
8
+ @input = String(input)
9
+ end
10
+
11
+ def call
12
+ matches = input.match(PRIORITY_PREFIX_REGEX)
13
+ matches ? [matches[1].to_i, input.gsub(PRIORITY_PREFIX_REGEX, "")] : [nil, input]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require "cgi"
2
+
3
+ module Lookbook
4
+ class SearchParamEncoder < Service
5
+ attr_reader :data
6
+
7
+ def initialize(data)
8
+ @data = data.to_h
9
+ end
10
+
11
+ def call
12
+ json = JSON.generate(data)
13
+ CGI.escape(json)
14
+ end
15
+ end
16
+ end
@@ -1,15 +1,16 @@
1
+ require "cgi"
2
+
1
3
  module Lookbook
2
4
  class SearchParamParser < Service
3
- attr_reader :param_value
5
+ attr_reader :str
4
6
 
5
- def initialize(param_value)
6
- @param_value = param_value.strip
7
+ def initialize(str)
8
+ @str = str.to_s.strip
7
9
  end
8
10
 
9
11
  def call
10
- pairs_str = param_value.split("|")
11
- pairs = pairs_str.map { |pair| [*pair.split(":").map(&:strip)] }
12
- pairs.to_h.symbolize_keys
12
+ json = CGI.unescape(str)
13
+ JsonParser.call(json)
13
14
  end
14
15
  end
15
16
  end
@@ -1,4 +1,18 @@
1
1
  module Lookbook
2
+ # Configuration store for Lookbook.
3
+ #
4
+ # Config option values can be get/set using hash property access syntax
5
+ # or dot-notation syntax.
6
+ #
7
+ # @example :ruby
8
+ # # Lookbook config access in Rails config files:
9
+ # config.lookbook.ui_theme = :zinc
10
+ #
11
+ # # Lookbook config access everywhere else:
12
+ # Lookbook.config.ui_theme = :zinc
13
+ #
14
+ # @ignore methods
15
+ # @api public
2
16
  class ConfigStore
3
17
  CONFIG_FILE = "config/app.yml"
4
18
 
@@ -25,14 +39,8 @@ module Lookbook
25
39
  store[:preview_paths].push(*paths.to_a)
26
40
  end
27
41
 
28
- def preview_display_params
29
- Lookbook.logger.warn "The `preview_display_params` config option has been renamed to `preview_display_options` and will be removed in v2.0"
30
- store[:preview_display_options]
31
- end
32
-
33
- def preview_display_params=(options)
34
- Lookbook.logger.warn "The `preview_display_params` config option has been renamed to `preview_display_options` and will be removed in v2.0"
35
- store[:preview_display_options] = options.to_h
42
+ def component_paths=(paths = nil)
43
+ store[:component_paths].push(*paths.to_a)
36
44
  end
37
45
 
38
46
  def listen_extensions=(extensions = nil)
@@ -64,14 +72,6 @@ module Lookbook
64
72
  end
65
73
  end
66
74
 
67
- def runtime_parsing=(value)
68
- Lookbook.logger.warn "The `runtime_parsing` config option has been deprecated and will be removed in v2.0"
69
- end
70
-
71
- def preview_srcdoc=(enable)
72
- Lookbook.logger.warn "The `preview_srcdoc` config option is deprecated and will be removed in v2.0"
73
- end
74
-
75
75
  def self.init_from_config(env: Rails.env)
76
76
  new(default_config(env: env))
77
77
  end
@@ -2,9 +2,7 @@ module Lookbook
2
2
  class InputStore
3
3
  CONFIG_FILE = "config/inputs.yml"
4
4
 
5
- DEFAULTS = {
6
- # TODO
7
- }
5
+ DEFAULTS = {}
8
6
 
9
7
  attr_reader :store
10
8
  delegate :to_h, to: :store
@@ -3,7 +3,7 @@ module Lookbook
3
3
  CONFIG_FILE = "config/panels.yml"
4
4
 
5
5
  DEFAULTS = {
6
- label: lambda { |data| data.name.titleize },
6
+ label: lambda { |data| data.name.to_s.titleize },
7
7
  hotkey: nil,
8
8
  disabled: false,
9
9
  show: true,
@@ -19,65 +19,53 @@ module Lookbook
19
19
  load_config(config)
20
20
  end
21
21
 
22
- def add_panel(name, group_name, *args)
22
+ def add_panel(name, *args)
23
23
  if get_panel(name)
24
24
  raise ConfigError.new("panel with name '#{name}' already exists", scope: "panels.config")
25
25
  else
26
- panel = build_config(name, group_name, *args)
27
- insert_at_position(group_name, panel.position, panel)
26
+ store[Utils.symbolize_name(name)] = build_config(name, *args)
28
27
  end
29
28
  end
30
29
 
31
30
  def update_panel(name, opts = {})
32
31
  panel = get_panel(name)
33
32
  if panel.present?
34
- panel.merge!(opts.except(:name, :position))
35
- if opts.key?(:position)
36
- remove_panel(name)
37
- insert_at_position(panel.group, opts[:position], panel)
38
- end
33
+ panel.merge!(opts.except(:name))
39
34
  else
40
35
  not_found!(name)
41
36
  end
42
37
  end
43
38
 
44
39
  def remove_panel(name)
45
- store.each do |group_name, panels|
46
- return true unless panels.reject! { |p| p.name == name.to_sym }.nil?
47
- end
48
- not_found!(name)
40
+ store.delete(Utils.symbolize_name(name)) { |name| not_found!(name) }
49
41
  end
50
42
 
51
43
  def load_config(config)
52
- config.to_h.each do |group_name, panels|
53
- panels.each do |opts|
54
- add_panel(opts[:name], group_name, opts.except(:name))
55
- end
56
- end
44
+ config.to_h.each { |name, opts| add_panel(name, opts) }
57
45
  end
58
46
 
59
- def get_panel(name, group_name = nil)
60
- panels(group_name).find { |p| p.name == name.to_sym }
47
+ def get_panel(name)
48
+ panels.find { |panel| panel.name == Utils.symbolize_name(name) }
61
49
  end
62
50
 
63
- def count_panels(group_name = nil)
64
- panels(group_name).count
51
+ def get_panels(*names)
52
+ ListResolver.call(names.flatten, panels.map(&:name)) { |name| get_panel(name) }
65
53
  end
66
54
 
67
- def in_group(name)
68
- store[name.to_sym] ||= []
55
+ def panels
56
+ store.map { |name, panel| panel }
69
57
  end
70
58
 
71
- def panels(group_name = nil)
72
- store.to_h.reduce([]) do |result, (name, group_panels)|
73
- result.push(*group_panels) if group_name.nil? || name == group_name.to_sym
74
- end
59
+ def names
60
+ panels.map(&:name)
75
61
  end
76
62
 
63
+ alias_method :all, :panels
64
+
77
65
  def self.resolve_config(opts, data)
78
66
  if opts[:name].present?
79
67
  data = data.is_a?(Store) ? data : Store.new(data)
80
- data.name = opts[:name].to_s
68
+ data.name = Utils.symbolize_name(opts[:name])
81
69
  resolved = opts.transform_values do |value|
82
70
  value.respond_to?(:call) ? value.call(data) : value
83
71
  end
@@ -93,10 +81,14 @@ module Lookbook
93
81
 
94
82
  def self.default_config
95
83
  config = ConfigLoader.call(CONFIG_FILE)
96
- config.each do |group, panels|
97
- panels.map! do |opts|
98
- opts.transform_values! do |value|
99
- (value.is_a?(String) && value.start_with?("->")) ? eval(value) : value # standard:disable Security/Eval
84
+ config.to_h.transform_values! do |opts|
85
+ opts.transform_values! do |value|
86
+ if value.is_a?(String) && value.start_with?("->")
87
+ proc {
88
+ eval(value) # standard:disable Security/Eval
89
+ }.call
90
+ else
91
+ value
100
92
  end
101
93
  end
102
94
  end
@@ -104,21 +96,8 @@ module Lookbook
104
96
 
105
97
  protected
106
98
 
107
- def insert_at_position(group_name, position, opts)
108
- group_panels = in_group(group_name)
109
- index = insert_index(position, group_panels.count)
110
- group_panels.insert(index, opts.except!(:position))
111
- end
112
-
113
- def insert_index(position, items_count)
114
- index = (position == 0) ? 1 : (position || 0).to_int
115
- last_position = items_count + 1
116
- index = last_position if index > last_position
117
- index - 1
118
- end
119
-
120
- def build_config(name, group_name, *args)
121
- opts = if args.many? && args.last.is_a?(Hash)
99
+ def build_config(name, *args)
100
+ opts = if args.many? && args.first.is_a?(String) && args.last.is_a?(Hash)
122
101
  args.last.merge({partial: args.first})
123
102
  elsif args.any?
124
103
  args.first.is_a?(String) ? {partial: args.first} : args.first
@@ -126,8 +105,7 @@ module Lookbook
126
105
  {}
127
106
  end
128
107
  if opts[:partial].present?
129
- opts[:name] = name.to_sym
130
- opts[:group] = group_name.to_sym
108
+ opts[:name] = Utils.symbolize_name(name)
131
109
  Store.new(DEFAULTS.merge(opts))
132
110
  else
133
111
  raise ConfigError.new("panels must define a partial path", scope: "panels.config")
@@ -0,0 +1,5 @@
1
+ require "active_support"
2
+
3
+ module Lookbook
4
+ Deprecation = ::ActiveSupport::Deprecation.new("3.0", "Lookbook")
5
+ end
@@ -0,0 +1,7 @@
1
+ module Lookbook
2
+ class PreviewTemplateError < LookbookError
3
+ def initialize(msg = nil, scope: "preview", **kwargs)
4
+ super(msg, scope: scope, **kwargs)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,69 @@
1
+ require "active_support/evented_file_update_checker"
2
+
3
+ module Lookbook
4
+ require "listen" unless Engine.runtime_context.rails_newer_than?("6.1.0")
5
+
6
+ class EventedFileUpdateChecker < ActiveSupport::EventedFileUpdateChecker
7
+ if Engine.runtime_context.rails_newer_than?("6.1.0")
8
+
9
+ def initialize(files, dirs = {}, &block)
10
+ super
11
+ @core = Core.new(files, dirs)
12
+ end
13
+
14
+ class Core < ActiveSupport::EventedFileUpdateChecker::Core
15
+ def changed(modified, added, removed)
16
+ super
17
+ Engine.files_changed(modified, added, removed) if @updated
18
+ end
19
+
20
+ # Patched to handle regex-style
21
+ # extension matchers like `.html.*`
22
+ def watching?(file)
23
+ return true if super
24
+
25
+ file = Pathname(file)
26
+ name_parts = file.basename.to_s.split(".")
27
+ ext = "." + name_parts.drop(1).join(".").to_s
28
+
29
+ file.dirname.ascend do |dir|
30
+ matching = @dirs.fetch(dir, []).map { |m| Regexp.new(m) }
31
+ if matching.empty? || matching.find { |m| m.match?(ext) }
32
+ break true
33
+ elsif dir == @common_path || dir.root?
34
+ break false
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ else
41
+
42
+ # Patched to handle regex-style
43
+ # extension matchers like `.html.*`
44
+ def watching?(file)
45
+ return true if super
46
+
47
+ file = Pathname(file)
48
+ name_parts = file.basename.to_s.split(".")
49
+ ext = "." + name_parts.drop(1).join(".").to_s
50
+
51
+ file.dirname.ascend do |dir|
52
+ matching = @dirs.fetch(dir, []).map { |m| Regexp.new(m) }
53
+ if matching.empty? || matching.find { |m| m.match?(ext) }
54
+ break true
55
+ elsif dir == @lcsp || dir.root?
56
+ break false
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def changed(modified, added, removed)
64
+ super
65
+ Engine.files_changed(modified, added, removed) if updated?
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ module Lookbook
2
+ class NullWebsocket < NullObject
3
+ def mountable?
4
+ false
5
+ end
6
+
7
+ alias_method :mounted?, :mountable?
8
+ end
9
+ end
@@ -1,4 +1,13 @@
1
1
  module Lookbook
2
+ # Generic hash-like key/value store.
3
+ #
4
+ # Properties can be get/set using hash access syntax (`data[:key]`)
5
+ # or dot-notation syntax (`data.key`).
6
+ #
7
+ # Based on [ActiveSupport::OrderedOptions](https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html)
8
+ #
9
+ # @ignore methods
10
+ # @api public
2
11
  class Store < ActiveSupport::OrderedOptions
3
12
  def initialize(initial_data = nil, opts = {})
4
13
  @recursive = opts[:recursive] || false
@@ -8,10 +8,10 @@ module Lookbook
8
8
  attr_accessor :path, :content
9
9
  attr_reader :children
10
10
 
11
- def initialize(path = nil, content = nil, position: 10000)
11
+ def initialize(path = nil, content = nil, priority: 10000)
12
12
  @path = path.to_s
13
13
  @content = content
14
- @position = position
14
+ @priority = priority
15
15
  @children = []
16
16
  end
17
17
 
@@ -27,8 +27,8 @@ module Lookbook
27
27
  content_value(:label, name.titleize)
28
28
  end
29
29
 
30
- def position
31
- content_value(:position, @position)
30
+ def priority
31
+ content_value(:priority, @priority)
32
32
  end
33
33
 
34
34
  def type
@@ -39,8 +39,8 @@ module Lookbook
39
39
  path.split("/").size
40
40
  end
41
41
 
42
- def add_child(name, content = nil, position: 10000)
43
- children << TreeNode.new("#{path}/#{name}", content, position: position)
42
+ def add_child(name, content = nil, priority: 10000)
43
+ children << TreeNode.new("#{path}/#{name}", content, priority: priority)
44
44
  end
45
45
 
46
46
  def has_child?(name)
@@ -69,7 +69,7 @@ module Lookbook
69
69
  if content?
70
70
  content <=> (other.content? ? other.content : other)
71
71
  else
72
- [position, label] <=> [other.position, other.label]
72
+ [priority, label] <=> [other.priority, other.label]
73
73
  end
74
74
  end
75
75
 
@@ -15,7 +15,7 @@ module Lookbook
15
15
 
16
16
  segments = [*directory_path&.split("/"), file_name].compact
17
17
  stripped_segments = segments.map! do |segment|
18
- PositionPrefixParser.call(segment).last.tr("-", "_")
18
+ PriorityPrefixParser.call(segment).last.tr("-", "_")
19
19
  end
20
20
 
21
21
  to_path(stripped_segments)
@@ -41,6 +41,12 @@ module Lookbook
41
41
  def strip_slashes(path)
42
42
  path.to_s.gsub(/\A\/|\/\z/, "")
43
43
  end
44
+
45
+ def determine_full_path(rel_path, search_dirs = [])
46
+ base_path = search_dirs.detect { |p| Dir["#{p}/#{rel_path}"].first }
47
+ path = Dir["#{base_path}/#{rel_path}"].first
48
+ Pathname(path) if path
49
+ end
44
50
  end
45
51
  end
46
52
  end
@@ -7,10 +7,18 @@ module Lookbook
7
7
  PathUtils.strip_slashes(id_str).tr("/", "-").gsub("--", "-")
8
8
  end
9
9
 
10
+ def temp_id(prefix: nil)
11
+ [prefix, (Time.now.to_f * 1000).to_i + rand(0..100)].compact.join("-")
12
+ end
13
+
10
14
  def name(str)
11
15
  str.to_s.parameterize.tr("-", "_")
12
16
  end
13
17
 
18
+ def symbolize_name(str)
19
+ name(str).to_sym
20
+ end
21
+
14
22
  def value_or_fallback(value, fallback = nil, &block)
15
23
  if value.nil?
16
24
  fallback_block = block || proc { fallback }
@@ -1,13 +1,13 @@
1
1
  module Lookbook
2
- class PositionTag < YardTag
3
- DEFAULT_POSITION = 100000
2
+ class PriorityTag < YardTag
3
+ DEFAULT_PRIORITY = 100000
4
4
 
5
5
  def value
6
6
  if text.present?
7
7
  int = text.to_i
8
- (int == 0) ? DEFAULT_POSITION : int
8
+ (int == 0) ? DEFAULT_PRIORITY : int
9
9
  else
10
- DEFAULT_POSITION
10
+ DEFAULT_PRIORITY
11
11
  end
12
12
  end
13
13
 
@@ -0,0 +1,4 @@
1
+ module Lookbook
2
+ class RendersTag < YardTag
3
+ end
4
+ end
@@ -7,6 +7,9 @@ module Lookbook
7
7
  raise NameError.new "'custom' is a reserved tag name and cannot be used"
8
8
  end
9
9
 
10
+ # Handle aliasing of removed `@component` tags
11
+ tag_name = "renders" if tag_name == "component"
12
+
10
13
  begin
11
14
  tag_class = "Lookbook::#{tag_name.camelize}Tag".constantize
12
15
  tag_class.new(text)