lookbook 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -11
  3. data/app/assets/lookbook/css/themes/blue.css +1 -1
  4. data/app/assets/lookbook/css/themes/green.css +66 -0
  5. data/app/assets/lookbook/css/themes/indigo.css +1 -1
  6. data/app/assets/lookbook/css/themes/rose.css +66 -0
  7. data/app/assets/lookbook/css/themes/zinc.css +1 -1
  8. data/app/components/lookbook/base_component.rb +2 -2
  9. data/app/components/lookbook/code/component.rb +2 -2
  10. data/app/components/lookbook/header/component.html.erb +1 -1
  11. data/app/components/lookbook/inspector_panel/component.rb +1 -1
  12. data/app/components/lookbook/nav/component.rb +8 -15
  13. data/app/components/lookbook/nav/directory/component.html.erb +26 -0
  14. data/app/components/lookbook/nav/directory/component.rb +4 -0
  15. data/app/components/lookbook/nav/{item → entity}/component.html.erb +5 -7
  16. data/app/components/lookbook/nav/entity/component.rb +49 -0
  17. data/app/components/lookbook/nav/item/component.css +15 -0
  18. data/app/components/lookbook/nav/item/component.js +4 -0
  19. data/app/components/lookbook/nav/item/component.rb +13 -56
  20. data/app/components/lookbook/tag_component.rb +1 -1
  21. data/app/components/lookbook/viewport/component.rb +1 -1
  22. data/app/controllers/concerns/lookbook/targetable_concern.rb +13 -38
  23. data/app/controllers/lookbook/application_controller.rb +9 -7
  24. data/app/controllers/lookbook/page_controller.rb +2 -2
  25. data/app/controllers/lookbook/pages_controller.rb +9 -15
  26. data/app/helpers/lookbook/application_helper.rb +2 -2
  27. data/app/helpers/lookbook/page_helper.rb +7 -4
  28. data/app/views/layouts/lookbook/application.html.erb +3 -4
  29. data/app/views/layouts/lookbook/page.html.erb +2 -2
  30. data/app/views/layouts/lookbook/shell.html.erb +2 -2
  31. data/app/views/layouts/lookbook/skeleton.html.erb +7 -7
  32. data/app/views/lookbook/index.html.erb +3 -3
  33. data/app/views/lookbook/inspector/panels/_params.html.erb +2 -2
  34. data/config/languages.yml +41 -0
  35. data/config/panels.yml +1 -1
  36. data/config/tags.yml +4 -0
  37. data/lib/lookbook/engine.rb +65 -43
  38. data/lib/lookbook/entities/collections/component_collection.rb +4 -0
  39. data/lib/lookbook/entities/collections/concerns/hierarchical_collection.rb +27 -0
  40. data/lib/lookbook/entities/collections/entity_collection.rb +66 -0
  41. data/lib/lookbook/entities/collections/page_collection.rb +30 -0
  42. data/lib/lookbook/entities/collections/preview_collection.rb +35 -0
  43. data/lib/lookbook/entities/collections/preview_example_collection.rb +9 -0
  44. data/lib/lookbook/entities/component.rb +31 -0
  45. data/lib/lookbook/entities/concerns/annotatable.rb +58 -0
  46. data/lib/lookbook/entities/concerns/inspectable.rb +44 -0
  47. data/lib/lookbook/entities/concerns/locatable.rb +73 -0
  48. data/lib/lookbook/entities/concerns/navigable.rb +27 -0
  49. data/lib/lookbook/entities/entity.rb +48 -0
  50. data/lib/lookbook/entities/page.rb +80 -0
  51. data/lib/lookbook/entities/page_section.rb +43 -0
  52. data/lib/lookbook/entities/preview.rb +87 -0
  53. data/lib/lookbook/entities/preview_example.rb +91 -0
  54. data/lib/lookbook/entities/preview_group.rb +48 -0
  55. data/lib/lookbook/file_watcher.rb +1 -1
  56. data/lib/lookbook/lang.rb +12 -35
  57. data/lib/lookbook/param.rb +2 -2
  58. data/lib/lookbook/preview_parser.rb +1 -1
  59. data/lib/lookbook/rendered_example.rb +37 -0
  60. data/lib/lookbook/services/code/code_beautifier.rb +1 -1
  61. data/lib/lookbook/services/code/code_highlighter.rb +1 -1
  62. data/lib/lookbook/services/code/code_indenter.rb +14 -0
  63. data/lib/lookbook/services/data/resolvers/data_resolver.rb +2 -2
  64. data/lib/lookbook/services/data/resolvers/method_resolver.rb +1 -1
  65. data/lib/lookbook/services/entities/entity_tree_builder.rb +45 -0
  66. data/lib/lookbook/services/position_prefix_parser.rb +16 -0
  67. data/lib/lookbook/stores/config_store.rb +1 -1
  68. data/lib/lookbook/stores/panel_store.rb +2 -2
  69. data/lib/lookbook/support/store.rb +1 -34
  70. data/lib/lookbook/support/tree_node.rb +83 -0
  71. data/lib/lookbook/support/utils/path_utils.rb +26 -2
  72. data/lib/lookbook/support/utils/utils.rb +24 -0
  73. data/lib/lookbook/tags/component_tag.rb +6 -0
  74. data/lib/lookbook/tags/custom_tag.rb +2 -0
  75. data/lib/lookbook/tags/id_tag.rb +1 -1
  76. data/lib/lookbook/tags/logical_path_tag.rb +3 -0
  77. data/lib/lookbook/tags/param_tag.rb +2 -0
  78. data/lib/lookbook/tags/position_tag.rb +1 -1
  79. data/lib/lookbook/tags/source_tag.rb +7 -0
  80. data/lib/lookbook/tags/yard_tag.rb +35 -7
  81. data/lib/lookbook/theme.rb +8 -0
  82. data/lib/lookbook/version.rb +1 -1
  83. data/lib/lookbook.rb +11 -7
  84. data/public/lookbook-assets/css/lookbook.css +55 -125
  85. data/public/lookbook-assets/css/lookbook.css.map +1 -1
  86. data/public/lookbook-assets/css/themes/blue.css.map +1 -1
  87. data/public/lookbook-assets/css/themes/green.css +68 -0
  88. data/public/lookbook-assets/css/themes/green.css.map +1 -0
  89. data/public/lookbook-assets/css/themes/indigo.css.map +1 -1
  90. data/public/lookbook-assets/css/themes/rose.css +68 -0
  91. data/public/lookbook-assets/css/themes/rose.css.map +1 -0
  92. data/public/lookbook-assets/css/themes/zinc.css.map +1 -1
  93. data/public/lookbook-assets/js/embed.js +9 -0
  94. data/public/lookbook-assets/js/embed.js.map +1 -1
  95. data/public/lookbook-assets/js/lookbook.js +288 -565
  96. data/public/lookbook-assets/js/lookbook.js.map +1 -1
  97. metadata +41 -18
  98. data/lib/lookbook/collection.rb +0 -161
  99. data/lib/lookbook/component.rb +0 -34
  100. data/lib/lookbook/entity.rb +0 -47
  101. data/lib/lookbook/page.rb +0 -194
  102. data/lib/lookbook/page_collection.rb +0 -19
  103. data/lib/lookbook/page_section.rb +0 -29
  104. data/lib/lookbook/preview.rb +0 -181
  105. data/lib/lookbook/preview_collection.rb +0 -23
  106. data/lib/lookbook/preview_example.rb +0 -93
  107. data/lib/lookbook/preview_group.rb +0 -58
  108. data/lib/lookbook/source_inspector.rb +0 -76
  109. data/lib/lookbook/support/utils/attribute_utils.rb +0 -14
  110. data/lib/lookbook/utils.rb +0 -65
@@ -9,16 +9,16 @@ module Lookbook
9
9
  helper Lookbook::ComponentHelper
10
10
 
11
11
  before_action :generate_theme_overrides
12
- before_action :assign_collections
12
+ before_action :assign_instance_vars
13
13
 
14
14
  def self.controller_path
15
15
  "lookbook"
16
16
  end
17
17
 
18
18
  def index
19
- landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
19
+ landing = Lookbook.pages.find(&:landing?) || Lookbook.pages.first
20
20
  if landing.present?
21
- redirect_to lookbook_page_path(landing.lookup_path)
21
+ redirect_to lookbook_page_path(landing.path)
22
22
  else
23
23
  render "lookbook/index"
24
24
  end
@@ -30,9 +30,11 @@ module Lookbook
30
30
  @theme_overrides ||= Lookbook.theme.to_css
31
31
  end
32
32
 
33
- def assign_collections
34
- @previews = Preview.all
35
- @pages = Page.all
33
+ def assign_instance_vars
34
+ @previews = Lookbook.previews
35
+ @pages = Lookbook.pages
36
+ @config = Lookbook.config
37
+ @engine = Lookbook.engine
36
38
  end
37
39
 
38
40
  def feature_enabled?(feature)
@@ -48,7 +50,7 @@ module Lookbook
48
50
  error_params = {}
49
51
  if exception.is_a?(ViewComponent::PreviewTemplateError)
50
52
  error_params = {
51
- file_path: @preview&.full_path,
53
+ file_path: @preview&.file_path,
52
54
  line_number: 0,
53
55
  source_code: @target&.source
54
56
  }
@@ -11,8 +11,8 @@ module Lookbook
11
11
  def render_page(page, locals = {})
12
12
  @page = page
13
13
  @pages = Lookbook.pages
14
- @next_page = @pages.find_next(@page)
15
- @previous_page = @pages.find_previous(@page)
14
+ @next_page = @pages.next(@page)
15
+ @previous_page = @pages.previous(@page)
16
16
 
17
17
  content = ActionViewAnnotationsHandler.call(disable_annotations: true) do
18
18
  render_to_string inline: @page.content, locals: {
@@ -9,9 +9,9 @@ module Lookbook
9
9
  end
10
10
 
11
11
  def index
12
- landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
12
+ landing = Lookbook.pages.find(&:landing?) || Lookbook.pages.first
13
13
  if landing.present?
14
- redirect_to lookbook_page_path landing.lookup_path
14
+ redirect_to lookbook_page_path(landing.path)
15
15
  else
16
16
  show_404
17
17
  end
@@ -20,21 +20,15 @@ module Lookbook
20
20
  def show
21
21
  @page = @pages.find_by_path(params[:path])
22
22
  if @page
23
- @next_page = @pages.find_next(@page)
24
- @previous_page = @pages.find_previous(@page)
25
- if @page.errors.any?
23
+ @next_page = @pages.next(@page)
24
+ @previous_page = @pages.previous(@page)
25
+ begin
26
+ @page_content = page_controller.render_page(@page)
27
+ @title = @page.title
28
+ rescue => exception
26
29
  render_in_layout "lookbook/error",
27
30
  layout: "lookbook/page",
28
- error: @page.errors.first
29
- else
30
- begin
31
- @page_content = page_controller.render_page(@page)
32
- @title = @page.title
33
- rescue => exception
34
- render_in_layout "lookbook/error",
35
- layout: "lookbook/page",
36
- error: Lookbook::Error.new(exception, file_path: @page.full_path, source_code: @page.content)
37
- end
31
+ error: Lookbook::Error.new(exception, file_path: @page.file_path, source_code: @page.content)
38
32
  end
39
33
  else
40
34
  show_404
@@ -18,7 +18,7 @@ module Lookbook
18
18
  end
19
19
 
20
20
  def landing_path
21
- landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
21
+ landing = Lookbook.pages.find(&:landing?) || Lookbook.pages.first
22
22
  if landing.present?
23
23
  lookbook_page_path landing.lookup_path
24
24
  else
@@ -27,7 +27,7 @@ module Lookbook
27
27
  end
28
28
 
29
29
  def generate_id(*args)
30
- args.map { |args| args.delete_prefix("/").tr("&?=/_\-", "-") }.join("-")
30
+ args.map { |args| args.delete_prefix("/").tr("&?=/_-", "-") }.join("-")
31
31
  end
32
32
  end
33
33
  end
@@ -3,7 +3,7 @@ module Lookbook
3
3
  include Utils
4
4
 
5
5
  def page_path(id)
6
- page = id.is_a?(Page) ? id : Lookbook.pages.find(id)
6
+ page = id.is_a?(Page) ? id : Lookbook.pages.find_by_id(id)
7
7
  if page.present?
8
8
  lookbook_page_path page.lookup_path
9
9
  else
@@ -16,10 +16,13 @@ module Lookbook
16
16
 
17
17
  @embed_counter ||= 0
18
18
 
19
- preview_lookup = args.first.is_a?(Symbol) ? args.first : preview_class_path(args.first)
20
- preview = Lookbook.previews.find(preview_lookup)
21
- example = args[1] ? preview&.example(args[1]) : preview&.default_example
19
+ preview = if args.first.is_a?(Symbol)
20
+ Lookbook.previews.find_by_path(args.first)
21
+ else
22
+ Lookbook.previews.find_by_preview_class(args.first)
23
+ end
22
24
 
25
+ example = args[1] ? preview&.example(args[1]) : preview&.default_example
23
26
  embed_id = "#{url_for}/embed/#{example.lookup_path}".delete_prefix("/").tr("/", "-")
24
27
 
25
28
  lookbook_render :embed,
@@ -21,9 +21,8 @@
21
21
  <% layout.pane class: "overflow-hidden" do %>
22
22
  <%= lookbook_render :nav,
23
23
  id: "previews-nav",
24
- collection: @previews,
25
- alpine_data: "$store.nav.previews",
26
- collapse_singles: true do |nav| %>
24
+ tree: @previews.to_tree,
25
+ alpine_data: "$store.nav.previews" do |nav| %>
27
26
  <%= nav.toolbar do |toolbar| %>
28
27
  <% toolbar.section padded: true do %>
29
28
  <h4 class="pt-1">Previews</h4>
@@ -45,7 +44,7 @@
45
44
  <% layout.pane class: "overflow-hidden" do %>
46
45
  <%= lookbook_render :nav,
47
46
  id: "pages-nav",
48
- collection: @pages,
47
+ tree: @pages.to_tree,
49
48
  alpine_data: "$store.nav.pages" do |nav| %>
50
49
  <%= nav.toolbar do |toolbar| %>
51
50
  <% toolbar.section padded: true do %>
@@ -16,7 +16,7 @@
16
16
  size: :lg,
17
17
  icon: :chevron_left,
18
18
  tooltip: "Previous page",
19
- href: lookbook_page_path(@previous_page.lookup_path),
19
+ href: lookbook_page_path(@previous_page.path),
20
20
  class: "pr-0.5 bg-transparent" %>
21
21
  <% else %>
22
22
  <%= lookbook_render :button,
@@ -31,7 +31,7 @@
31
31
  size: :lg,
32
32
  icon: :chevron_right,
33
33
  tooltip: "Next page",
34
- href: lookbook_page_path(@next_page.lookup_path),
34
+ href: lookbook_page_path(@next_page.path),
35
35
  class: "pl-0.5 bg-transparent" %>
36
36
  <% else %>
37
37
  <%= lookbook_render :button,
@@ -13,8 +13,8 @@
13
13
  </style>
14
14
  <% end %>
15
15
 
16
- <%= lookbook_render :header, id: "app-header", debug_menu: config.debug_menu do |header| %>
17
- <% header.branding { config.project_name } %>
16
+ <%= lookbook_render :header, id: "app-header", debug_menu: @config.debug_menu do |header| %>
17
+ <% header.branding { @config.project_name } %>
18
18
  <% end %>
19
19
 
20
20
  <%= content_for?(:shell) ? yield(:shell) : yield %>
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
7
7
 
8
8
  <link href="<%= asset_path("/css/lookbook.css") %>" rel="stylesheet">
9
- <link href="<%= asset_path("/css/themes/#{config.ui_theme}.css") %>" rel="stylesheet">
9
+ <link href="<%= asset_path("/css/themes/#{@config.ui_theme}.css") %>" rel="stylesheet">
10
10
 
11
11
  <%- if @theme_overrides.present? %>
12
12
  <style media="all">
@@ -14,7 +14,7 @@
14
14
  </style>
15
15
  <% end %>
16
16
 
17
- <% if config.ui_favicon == true %>
17
+ <% if @config.ui_favicon == true %>
18
18
  <link
19
19
  rel="icon"
20
20
  href="data:image/svg+xml,%3Csvg width='96' height='84' viewBox='0 0 96 84' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath d='M96 0v84H0V0h96Z'/%3E%3C/clipPath%3E%3CclipPath id='b'%3E%3Cpath d='M71.897 0a1.5 1.5 0 0 1 1.31.769l22.605 40.5a1.5 1.5 0 0 1 0 1.462l-22.605 40.5a1.5 1.5 0 0 1-1.31.769H49.481a1 1 0 0 1-.873-1.487L70.812 42.73a1.5 1.5 0 0 0 0-1.462L48.608 1.487A1 1 0 0 1 49.481 0ZM24.655.564l22.72 40.705a1.5 1.5 0 0 1 0 1.462l-22.72 40.705a1 1 0 0 1-1.746 0L.19 42.73a1.5 1.5 0 0 1 0-1.462L22.91.564a1 1 0 0 1 1.746 0Z'/%3E%3C/clipPath%3E%3C/defs%3E%3Cg clip-path='url(%23a)'%3E%3Cg clip-path='url(%23b)'%3E%3Cpath fill='<%= url_encode theme.favicon_dark_mode %>' d='M0 0h96v84H0V0z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"
@@ -26,10 +26,10 @@
26
26
  <% end %>
27
27
 
28
28
  <script>
29
- window.LOG_LEVEL = <%= config.log_level %>;
30
- window.APP_NAME = "<%= Lookbook::Engine.app_name %>";
31
- <% if Lookbook::Engine.websocket.mounted? %>
32
- window.SOCKET_PATH = "<%= Lookbook::Engine.websocket.full_mount_path %>";
29
+ window.LOG_LEVEL = <%= @config.log_level %>;
30
+ window.APP_NAME = "<%= @engine.app_name %>";
31
+ <% if @engine.websocket.mounted? %>
32
+ window.SOCKET_PATH = "<%= @engine.websocket.full_mount_path %>";
33
33
  <% end %>
34
34
  </script>
35
35
 
@@ -38,7 +38,7 @@
38
38
  <script src="<%= asset_path("/js/embed.js") %>" defer></script>
39
39
  <% end %>
40
40
 
41
- <title><%= [@title, config.project_name || "Lookbook"].compact.join(" :: ") %></title>
41
+ <title><%= [@title, @config.project_name || "Lookbook"].compact.join(" :: ") %></title>
42
42
  </head>
43
43
  <body>
44
44
  <%= yield :body %>
@@ -1,6 +1,6 @@
1
1
  <div id="landing" class="flex flex-col items-center justify-center h-full w-full">
2
2
  <div class="text-center" id="landing-<%= @previews.any? ? "with" : "no" %>-content">
3
- <% if config.project_name.downcase == "lookbook" %>
3
+ <% if @config.project_name.downcase == "lookbook" %>
4
4
  <div class="flex justify-center pb-4">
5
5
  <svg class="flex-none mx-auto w-[140px]" viewBox="0 0 275 36">
6
6
  <g fill-rule="nonzero" fill="none">
@@ -9,9 +9,9 @@
9
9
  </g>
10
10
  </svg>
11
11
  </div>
12
- <% elsif config.project_name != false %>
12
+ <% elsif @config.project_name != false %>
13
13
  <h5 class="text-base text-lookbook-blank-slate-title truncate uppercase font-black tracking-wide mb-2">
14
- <%= config.project_name %>
14
+ <%= @config.project_name %>
15
15
  </h5>
16
16
  <% end %>
17
17
  <div class="opacity-60">
@@ -1,11 +1,11 @@
1
1
  <% if @params.none? %>
2
- <div class="p-4 w-full h-full bg-lookbook-prose-bg">
2
+ <div class="p-4 w-full h-full bg-lookbook-prose-bg" id="params-editor-blank-slate">
3
3
  <%= lookbook_render :prose do %>
4
4
  <em class='opacity-50'>No params configured.</em>
5
5
  <% end %>
6
6
  </div>
7
7
  <% else %>
8
- <div class="p-4 w-full h-full overflow-x-hidden">
8
+ <div class="p-4 w-full h-full overflow-x-hidden" id="params-editor-<%= @target.id %>">
9
9
  <%= lookbook_render "params/editor" do |editor| %>
10
10
  <% @params.each do |param| %>
11
11
  <% editor.field param: param %>
@@ -0,0 +1,41 @@
1
+ shared:
2
+ definitions:
3
+ - name: ruby
4
+ ext: .rb
5
+ label: Ruby
6
+ comment: "# %s"
7
+
8
+ - name: html
9
+ ext: .html
10
+ label: HTML
11
+ comment: "<!-- %s -->"
12
+
13
+ - name: erb
14
+ ext: .erb
15
+ label: ERB
16
+ comment: "<%%# %s %%>"
17
+
18
+ - name: haml
19
+ ext: .haml
20
+ label: Haml
21
+ comment: "<!-- %s -->"
22
+
23
+ - name: slim
24
+ ext: .slim
25
+ label: Slim
26
+ comment: "<!-- %s -->"
27
+
28
+ - name: tsx
29
+ ext: .tsx
30
+ label: TypeScript
31
+ comment: "// %s"
32
+
33
+ - name: js
34
+ ext: .js
35
+ label: JavasScript
36
+ comment: "// %s"
37
+
38
+ - name: css
39
+ ext: .css
40
+ label: CSS
41
+ comment: "/* %s */"
data/config/panels.yml CHANGED
@@ -27,4 +27,4 @@ shared:
27
27
  partial: lookbook/inspector/panels/params
28
28
  label: Params
29
29
  hotkey: p
30
- disabled: ->(data) { data.preview.params.none? }
30
+ disabled: ->(data) { data.context.params.none? }
data/config/tags.yml CHANGED
@@ -30,3 +30,7 @@ shared:
30
30
  logical_path:
31
31
  label: Logical Path
32
32
  opts: {}
33
+
34
+ source:
35
+ label: Source file
36
+ opts: {}
@@ -42,15 +42,16 @@ module Lookbook
42
42
 
43
43
  initializer "lookbook.file_watcher.pages" do
44
44
  file_watcher.watch(opts.page_paths, opts.page_extensions) do |changes|
45
- self.class.websocket.broadcast(:reload)
45
+ Engine.pages.load(Engine.page_paths)
46
+ Engine.websocket.broadcast(:reload)
46
47
  run_hooks(:after_change, changes)
47
48
  end
48
49
  end
49
50
 
50
- initializer "lookbook.parser.preview_load_callback" do
51
- parser.after_parse do |registry|
52
- Preview.load!(registry.all(:class))
53
- self.class.websocket.broadcast(:reload)
51
+ initializer "lookbook.parser.previews_load_callback" do
52
+ parser.after_parse do |code_objects|
53
+ Engine.previews.load(code_objects.all(:class))
54
+ Engine.websocket.broadcast(:reload)
54
55
  end
55
56
  end
56
57
 
@@ -74,6 +75,7 @@ module Lookbook
74
75
  end
75
76
 
76
77
  config.after_initialize do
78
+ Engine.pages.load(Engine.page_paths)
77
79
  parser.parse { run_hooks(:after_initialize) }
78
80
  end
79
81
 
@@ -82,71 +84,91 @@ module Lookbook
82
84
  end
83
85
 
84
86
  def run_hooks(event_name, *args)
85
- self.class.hooks.for_event(event_name).each do |hook|
87
+ Engine.hooks.for_event(event_name).each do |hook|
86
88
  hook.call(Lookbook, *args)
87
89
  end
88
90
  end
89
91
 
90
92
  def parser
91
- @parser ||= PreviewParser.new(opts.preview_paths, Engine.tags)
93
+ @_parser ||= PreviewParser.new(opts.preview_paths, Engine.tags)
92
94
  end
93
95
 
94
96
  def file_watcher
95
- @file_watcher ||= FileWatcher.new(force_polling: opts.listen_use_polling)
97
+ @_file_watcher ||= FileWatcher.new(force_polling: opts.listen_use_polling)
96
98
  end
97
99
 
98
100
  def process
99
- @process ||= Process.new(env: Rails.env)
101
+ @_process ||= Process.new(env: Rails.env)
100
102
  end
101
103
 
102
104
  def listen?
103
105
  opts.listen && process.supports_listening?
104
106
  end
105
107
 
106
- def self.mount_path
107
- routes.find_script_name({})
108
- end
108
+ class << self
109
+ def mount_path
110
+ routes.find_script_name({})
111
+ end
109
112
 
110
- def self.mounted?
111
- mount_path.present?
112
- end
113
+ def mounted?
114
+ mount_path.present?
115
+ end
113
116
 
114
- def self.app_name
115
- name = if Rails.application.class.respond_to?(:module_parent_name)
116
- Rails.application.class.module_parent_name
117
- else
118
- Rails.application.class.parent_name
117
+ def app_name
118
+ name = if Rails.application.class.respond_to?(:module_parent_name)
119
+ Rails.application.class.module_parent_name
120
+ else
121
+ Rails.application.class.parent_name
122
+ end
123
+ name.underscore
119
124
  end
120
- name.underscore
121
- end
122
125
 
123
- def self.websocket
124
- if mounted?
125
- use_websocket = opts.auto_refresh && opts.listen && process.supports_listening?
126
- @websocket ||= use_websocket ? Websocket.new(mount_path, logger: Lookbook.logger) : Websocket.noop
127
- else
128
- Websocket.noop
126
+ def websocket
127
+ if mounted?
128
+ use_websocket = opts.auto_refresh && opts.listen && process.supports_listening?
129
+ @websocket ||= use_websocket ? Websocket.new(mount_path, logger: Lookbook.logger) : Websocket.noop
130
+ else
131
+ Websocket.noop
132
+ end
129
133
  end
130
- end
131
134
 
132
- def self.panels
133
- @panels ||= PanelStore.init_from_config
134
- end
135
+ def panels
136
+ @_panels ||= PanelStore.init_from_config
137
+ end
135
138
 
136
- def self.inputs
137
- @inputs ||= InputStore.init_from_config
138
- end
139
+ def inputs
140
+ @_inputs ||= InputStore.init_from_config
141
+ end
139
142
 
140
- def self.tags
141
- @tags ||= TagStore.init_from_config
142
- end
143
+ def tags
144
+ @_tags ||= TagStore.init_from_config
145
+ end
143
146
 
144
- def self.hooks
145
- @hooks ||= HookStore.init_from_config
146
- end
147
+ def hooks
148
+ @_hooks ||= HookStore.init_from_config
149
+ end
150
+
151
+ def component_paths
152
+ @_component_paths ||= Array(PathUtils.to_absolute(opts.components_path))
153
+ end
154
+
155
+ def page_paths
156
+ @_page_paths ||= PathUtils.normalize_paths(opts.page_paths)
157
+ end
158
+
159
+ def preview_paths
160
+ @_preview_paths ||= PathUtils.normalize_paths(opts.preview_paths)
161
+ end
162
+
163
+ def pages
164
+ @_pages ||= PageCollection.new
165
+ end
166
+
167
+ def previews
168
+ @_previews ||= PreviewCollection.new
169
+ end
147
170
 
148
- def self.preview_controller
149
- @preview_controller
171
+ attr_reader :preview_controller
150
172
  end
151
173
 
152
174
  at_exit do
@@ -0,0 +1,4 @@
1
+ module Lookbook
2
+ class ComponentCollection < EntityCollection
3
+ end
4
+ end
@@ -0,0 +1,27 @@
1
+ module Lookbook
2
+ module HierarchicalCollection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def entities
7
+ @_cache[:entities] ||= collect_ordered_entities(to_tree(include_hidden: true))
8
+ end
9
+
10
+ def to_tree(include_hidden: false)
11
+ @_cache[include_hidden ? :tree_with_hidden : :tree] ||= EntityTreeBuilder.call(@entities, include_hidden: include_hidden)
12
+ end
13
+
14
+ protected
15
+
16
+ def collect_ordered_entities(start_node)
17
+ start_node.inject([]) do |entities, node|
18
+ entities.append(node.content? ? node.content : collect_ordered_entities(node))
19
+ end.flatten
20
+ end
21
+
22
+ def sort_entities
23
+ @entities.sort_by! { |entity| [entity.depth, entity.position, entity.label] }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ module Lookbook
2
+ class EntityCollection
3
+ include Enumerable
4
+
5
+ delegate_missing_to :entities
6
+
7
+ attr_reader :entities
8
+
9
+ def initialize(entities = nil)
10
+ @_cache = {}
11
+ @entities = []
12
+ add(entities)
13
+ end
14
+
15
+ def add(entities = nil)
16
+ Array(entities).each do |entity|
17
+ unless find_by_path(entity.path)
18
+ clear_cache
19
+ @entities.push(entity)
20
+ end
21
+ end
22
+ sort_entities
23
+ end
24
+
25
+ def find_by_id(id)
26
+ id = Utils.id(id)
27
+ find { |entity| entity.id == id }
28
+ end
29
+
30
+ def find_by_path(path)
31
+ find { |entity| entity.path.to_s == path.to_s }
32
+ end
33
+
34
+ def next(entity)
35
+ index = find_index { |i| i.path == entity.path }
36
+ entities[index + 1] unless index.nil?
37
+ end
38
+
39
+ def previous(entity)
40
+ index = find_index { |i| i.path == entity.path }
41
+ entities[index - 1] if !index.nil? && index > 0
42
+ end
43
+
44
+ def each(&block)
45
+ if block
46
+ entities.each { |entity| yield entity }
47
+ else
48
+ to_enum(:each)
49
+ end
50
+ end
51
+
52
+ def flat_map(...)
53
+ map(...).map(&:to_a).flatten
54
+ end
55
+
56
+ protected
57
+
58
+ def sort_entities
59
+ @entities.sort_by! { |entity| [entity.label] }
60
+ end
61
+
62
+ def clear_cache
63
+ @_cache = {}
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ module Lookbook
2
+ class PageCollection < EntityCollection
3
+ include HierarchicalCollection
4
+
5
+ def load(page_paths)
6
+ @entities = []
7
+ clear_cache
8
+
9
+ file_paths = page_paths.flat_map do |dir|
10
+ PathUtils.normalize_paths(Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort)
11
+ end
12
+
13
+ entities = file_paths.map { |path| PageCollection.entity(path) }
14
+ pages, sections = entities.partition { |page| page.type == :page }
15
+
16
+ page_dict = pages.index_by(&:lookup_path)
17
+ sections.each do |section|
18
+ parent = page_dict[section.lookup_path]
19
+ section.parent = parent
20
+ parent.add_section(section)
21
+ end
22
+
23
+ add(pages)
24
+ end
25
+
26
+ def self.entity(file_path)
27
+ File.basename(file_path).match?(%r{\[(.*?\w+)\]}) ? PageSection.new(file_path) : Page.new(file_path)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module Lookbook
2
+ class PreviewCollection < EntityCollection
3
+ include HierarchicalCollection
4
+
5
+ def find_example_by_path(lookup_path)
6
+ examples.find_by_path(lookup_path)
7
+ end
8
+
9
+ def find_by_preview_class(klass)
10
+ find { |preview| preview.preview_class.name == klass.to_s }
11
+ end
12
+
13
+ def load(code_objects)
14
+ @entities = []
15
+ clear_cache
16
+
17
+ previews = Array(code_objects).map { |obj| PreviewCollection.preview_from_code_object(obj) }.compact
18
+ add(previews)
19
+ end
20
+
21
+ def self.preview_from_code_object(code_object)
22
+ klass = code_object.path.constantize
23
+ Preview.new(code_object) if klass.ancestors.include?(ViewComponent::Preview)
24
+ rescue => exception
25
+ Lookbook.logger.error LookbookError.new(exception)
26
+ nil
27
+ end
28
+
29
+ protected
30
+
31
+ def examples
32
+ @_cache[:examples] ||= PreviewExampleCollection.new(flat_map(&:examples))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ module Lookbook
2
+ class PreviewExampleCollection < EntityCollection
3
+ protected
4
+
5
+ def sort_entities
6
+ @entities.sort_by!(&:label) if Lookbook.config.sort_examples
7
+ end
8
+ end
9
+ end