tiny_admin 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dfe9b2fef29073201e228daf3a7d89003902f8d98108e58cb8245fdb9a533e3
4
- data.tar.gz: 6634891a9bbc2585549a759cd061a25234b834ebdcc0b77fbbb9bd2297b0a626
3
+ metadata.gz: d8059ea24a18bfb14f8e868a70203db8045099e20196f5098f1c63b121421e49
4
+ data.tar.gz: d0da80d718362c58a50e963b2e79dff550e77a763d7872a0ba96efea43f408b3
5
5
  SHA512:
6
- metadata.gz: 78b49e3f8b7efe941e24734692db4e4baaa2159f58a10f49ce0563f8c4cfa5950f3e155184016faff12c415ff1a6632d4ee06cd61659a466d170d4b7954becf7
7
- data.tar.gz: 16c4fe68dca49b84c4c8ca834c9e3c2ce2304f5a78dc378cfdde837462a96a4e00030e5098083d025ad9ab28060ea2aa581725434ffeec1582715e5fa725ff28
6
+ metadata.gz: 3067f890f10f10f5dc7379c7b147f01f5cb62b8d59f06ad7b4387c685cd2e32cdeb1f87fbfe36b0e2c388c854ce461f1a18b2659ee479e401ba74ed3c5559a3a
7
+ data.tar.gz: 0a4b4129b33e0dbf2ab2d01df14ead0ec3971a098dbed39a5b50c287e2a00c5c8448011de81a233c7bb1dda58bb42b648d44069684a65cdb4e42e3faf2a453bb
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Tiny Admin
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/tiny_admin.svg)](https://badge.fury.io/rb/tiny_admin) [![Linters](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml) [![Specs](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml)
3
+ [![Gem Version](https://badge.fury.io/rb/tiny_admin.svg)](https://badge.fury.io/rb/tiny_admin)
4
+ [![Gem Downloads](https://badgen.net/rubygems/dt/tiny_admin)](https://rubygems.org/gems/tiny_admin)
5
+ [![Linters](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml)
6
+ [![Specs](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml)
4
7
 
5
8
  A compact and composable dashboard component for Ruby.
6
9
 
@@ -16,7 +19,7 @@ Please ⭐ if you like it.
16
19
 
17
20
  ## Install
18
21
 
19
- - Add to your Gemfile: `gem 'tiny_admin', '~> 0.6'`
22
+ - Add to your Gemfile: `gem 'tiny_admin', '~> 0.7'`
20
23
  - Mount the app in a route (check some examples with: Hanami, Rails, Roda and standalone in [extra](extra))
21
24
  + in Rails, update _config/routes.rb_: `mount TinyAdmin::Router => '/admin'`
22
25
  - Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` with a YAML config file (see [configuration](#configuration) below):
@@ -80,6 +83,7 @@ The following options are supported:
80
83
  - `title` (String): root section's title;
81
84
  - `page` (String): a view object to render;
82
85
  - `redirect` (String): alternative to _page_ option - redirects to a specific slug;
86
+ - `widgets` (Array): list of widgets (as View components) to present.
83
87
 
84
88
  Example:
85
89
 
@@ -87,6 +91,9 @@ Example:
87
91
  root:
88
92
  title: MyAdmin
89
93
  redirect: posts
94
+ widgets:
95
+ - LatestAuthorsWidget
96
+ - LatestPostsWidget
90
97
  ```
91
98
 
92
99
  `helper_class` (String): class or module with helper methods, used for attributes' formatters.
@@ -124,6 +131,7 @@ authentication:
124
131
  - `slug` (String): section reference identifier;
125
132
  - `name` (String): section's title;
126
133
  - `type` (String): the type of section: `content`, `page`, `resource` or `url`;
134
+ - `widgets` (Array): list of widgets (as View components) to present;
127
135
  - other properties depends on the section's type.
128
136
 
129
137
  For _content_ sections:
@@ -139,6 +147,9 @@ type: content
139
147
  content: >
140
148
  <h1>Test content!</h1>
141
149
  <p>Some test content</p>
150
+ widgets:
151
+ - LatestAuthorsWidget
152
+ - LatestPostsWidget
142
153
  ```
143
154
 
144
155
  For _url_ sections:
@@ -179,6 +190,7 @@ For _resource_ sections:
179
190
  - `show` (Hash): detail's action options (see below);
180
191
  - `collection_actions` (Array of hashes): custom collection's actions;
181
192
  - `member_actions` (Array of hashes): custom details's actions;
193
+ - `widgets` (Array): list of widgets (as View components) to present;
182
194
  - `only` (Array of strings): list of supported actions (ex. `index`);
183
195
  - `options` (Array of strings): resource options (ex. `hidden`).
184
196
 
@@ -256,6 +268,9 @@ Example:
256
268
  header: The author
257
269
  link_to: authors
258
270
  call: author, name
271
+ widgets:
272
+ - LatestAuthorsWidget
273
+ - LatestPostsWidget
259
274
  ```
260
275
 
261
276
  ### Sample
@@ -280,6 +295,9 @@ authentication:
280
295
  # password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
281
296
  root:
282
297
  title: Test Admin
298
+ widgets:
299
+ - LatestAuthorsWidget
300
+ - LatestPostsWidget
283
301
  # page: RootPage
284
302
  helper_class: AdminHelper
285
303
  page_not_found: PageNotFound
@@ -3,34 +3,38 @@
3
3
  module TinyAdmin
4
4
  module Actions
5
5
  class Index < BasicAction
6
- attr_reader :current_page,
6
+ attr_reader :context,
7
+ :current_page,
7
8
  :fields_options,
8
- :filters_list,
9
9
  :links,
10
+ :options,
10
11
  :pagination,
11
12
  :pages,
12
13
  :params,
13
14
  :query_string,
14
- :repository,
15
- :sort
15
+ :repository
16
16
 
17
17
  def call(app:, context:, options:)
18
+ @context = context
19
+ @options = options || {}
18
20
  evaluate_options(options)
19
21
  fields = repository.fields(options: fields_options)
20
- filters = prepare_filters(fields, filters_list)
21
- records, total_count = repository.list(page: current_page, limit: pagination, filters: filters, sort: sort)
22
+ filters = prepare_filters(fields)
23
+ records, count = repository.list(page: current_page, limit: pagination, filters: filters, sort: options[:sort])
24
+ attributes = {
25
+ actions: context.actions,
26
+ fields: fields,
27
+ filters: filters,
28
+ links: options[:links],
29
+ prepare_record: ->(record) { repository.index_record_attrs(record, fields: fields_options) },
30
+ records: records,
31
+ slug: context.slug,
32
+ title: repository.index_title,
33
+ widgets: options[:widgets]
34
+ }
22
35
 
23
- prepare_page(Views::Actions::Index) do |page|
24
- setup_pagination(page, TinyAdmin.settings.components[:pagination], total_count: total_count)
25
- page.update_attributes(
26
- actions: context.actions,
27
- fields: fields,
28
- filters: filters,
29
- links: links,
30
- prepare_record: ->(record) { repository.index_record_attrs(record, fields: fields_options) },
31
- records: records,
32
- title: repository.index_title
33
- )
36
+ prepare_page(Views::Actions::Index, slug: context.slug, attributes: attributes) do |page|
37
+ setup_pagination(page, TinyAdmin.settings.components[:pagination], total_count: count)
34
38
  end
35
39
  end
36
40
 
@@ -40,17 +44,13 @@ module TinyAdmin
40
44
  @fields_options = attribute_options(options[:attributes])
41
45
  @params = context.request.params
42
46
  @repository = context.repository
43
- @filters_list = options[:filters]
44
47
  @pagination = options[:pagination] || 10
45
- @sort = options[:sort]
46
- @links = options[:links]
47
-
48
48
  @current_page = (params['p'] || 1).to_i
49
49
  @query_string = params_to_s(params.except('p'))
50
50
  end
51
51
 
52
- def prepare_filters(fields, filters_list)
53
- filters = (filters_list || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
52
+ def prepare_filters(fields)
53
+ filters = (options[:filters] || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
54
54
  filters = filters.each_with_object({}) { |filter, result| result[filter[:field]] = filter }
55
55
  values = (params['q'] || {})
56
56
  fields.each_with_object({}) do |(name, field), result|
@@ -8,16 +8,18 @@ module TinyAdmin
8
8
  repository = context.repository
9
9
  record = repository.find(context.reference)
10
10
  prepare_record = ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) }
11
+ attributes = {
12
+ actions: context.actions,
13
+ fields: repository.fields(options: fields_options),
14
+ prepare_record: prepare_record,
15
+ record: record,
16
+ reference: context.reference,
17
+ slug: context.slug,
18
+ title: repository.show_title(record),
19
+ widgets: options[:widgets]
20
+ }
11
21
 
12
- prepare_page(Views::Actions::Show) do |page|
13
- page.update_attributes(
14
- actions: context.actions,
15
- fields: repository.fields(options: fields_options),
16
- prepare_record: prepare_record,
17
- record: record,
18
- title: repository.show_title(record)
19
- )
20
- end
22
+ prepare_page(Views::Actions::Show, slug: context.slug, attributes: attributes)
21
23
  rescue Plugins::BaseRepository::RecordNotFound => _e
22
24
  prepare_page(options[:record_not_found_page] || Views::Pages::RecordNotFound)
23
25
  end
@@ -24,7 +24,10 @@ module TinyAdmin
24
24
  private
25
25
 
26
26
  def render_login(notices: nil, warnings: nil, errors: nil)
27
- page = prepare_page(TinyAdmin.settings.authentication[:login], options: %i[no_menu compact_layout])
27
+ login = TinyAdmin.settings.authentication[:login]
28
+ return unless login
29
+
30
+ page = prepare_page(login, options: %i[no_menu compact_layout])
28
31
  page.messages = {
29
32
  notices: notices || flash['notices'],
30
33
  warnings: warnings || flash['warnings'],
@@ -1,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- class Context
5
- include Singleton
6
-
7
- attr_accessor :actions,
8
- :navbar,
9
- :pages,
10
- :reference,
11
- :repository,
12
- :request,
13
- :resources,
14
- :router,
15
- :settings,
16
- :slug
17
- end
4
+ Context = Struct.new(
5
+ :actions,
6
+ :reference,
7
+ :repository,
8
+ :request,
9
+ :router,
10
+ :slug,
11
+ keyword_init: true
12
+ )
18
13
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module TinyAdmin
4
4
  class Router < BasicApp
5
- TinyAdmin.settings.load_settings
5
+ extend Forwardable
6
+
7
+ def_delegator TinyAdmin, :route_for
6
8
 
7
9
  route do |r|
8
- context.router = r
9
- context.settings = TinyAdmin.settings
10
+ TinyAdmin.settings.load_settings
10
11
 
11
12
  r.on 'auth' do
12
- context.slug = nil
13
13
  r.run Authentication
14
14
  end
15
15
 
@@ -26,15 +26,14 @@ module TinyAdmin
26
26
  end
27
27
 
28
28
  r.post '' do
29
- context.slug = nil
30
29
  r.redirect TinyAdmin.settings.root_path
31
30
  end
32
31
 
33
- context.pages.each do |slug, page_data|
32
+ store.pages.each do |slug, page_data|
34
33
  setup_page_route(r, slug, page_data)
35
34
  end
36
35
 
37
- context.resources.each do |slug, options|
36
+ store.resources.each do |slug, options|
38
37
  setup_resource_routes(r, slug, options: options || {})
39
38
  end
40
39
 
@@ -43,6 +42,10 @@ module TinyAdmin
43
42
 
44
43
  private
45
44
 
45
+ def store
46
+ @store ||= TinyAdmin.settings.store
47
+ end
48
+
46
49
  def render_page(page)
47
50
  if page.respond_to?(:messages=)
48
51
  page.messages = { notices: flash['notices'], warnings: flash['warnings'], errors: flash['errors'] }
@@ -51,76 +54,83 @@ module TinyAdmin
51
54
  end
52
55
 
53
56
  def root_route(router)
54
- context.slug = nil
55
57
  if TinyAdmin.settings.root[:redirect]
56
58
  router.redirect route_for(TinyAdmin.settings.root[:redirect])
57
59
  else
58
- page = TinyAdmin.settings.root[:page]
59
- page_class = page.is_a?(String) ? Object.const_get(page) : page
60
- render_page prepare_page(page_class)
60
+ page_class = to_class(TinyAdmin.settings.root[:page])
61
+ render_page prepare_page(page_class, attributes: TinyAdmin.settings.root.slice(:content, :title, :widgets))
61
62
  end
62
63
  end
63
64
 
64
65
  def setup_page_route(router, slug, page_data)
65
66
  router.get slug do
66
- context.slug = slug
67
- page = prepare_page(page_data[:class])
68
- page.update_attributes(content: page_data[:content]) if page_data[:content]
69
- render_page page
67
+ attributes = page_data.slice(:content, :title, :widgets)
68
+ render_page prepare_page(page_data[:class], slug: slug, attributes: attributes)
70
69
  end
71
70
  end
72
71
 
73
72
  def setup_resource_routes(router, slug, options:)
74
73
  router.on slug do
75
- context.slug = slug
76
- setup_collection_routes(router, options: options)
77
- setup_member_routes(router, options: options)
74
+ setup_collection_routes(router, slug, options: options)
75
+ setup_member_routes(router, slug, options: options)
78
76
  end
79
77
  end
80
78
 
81
- def setup_collection_routes(router, options:)
82
- context.repository = options[:repository].new(options[:model])
79
+ def setup_collection_routes(router, slug, options:)
80
+ repository = options[:repository].new(options[:model])
83
81
  action_options = options[:index] || {}
84
82
 
85
83
  # Custom actions
86
84
  custom_actions = setup_custom_actions(
87
85
  router,
88
86
  options[:collection_actions],
89
- repository: context.repository,
90
- options: action_options
87
+ options: action_options,
88
+ repository: repository,
89
+ slug: slug
91
90
  )
92
91
 
93
92
  # Index
94
93
  if options[:only].include?(:index) || options[:only].include?('index')
95
94
  router.is do
96
- context.actions = custom_actions
97
- context.request = request
95
+ context = Context.new(
96
+ actions: custom_actions,
97
+ repository: repository,
98
+ request: request,
99
+ router: router,
100
+ slug: slug
101
+ )
98
102
  index_action = TinyAdmin::Actions::Index.new
99
103
  render_page index_action.call(app: self, context: context, options: action_options)
100
104
  end
101
105
  end
102
106
  end
103
107
 
104
- def setup_member_routes(router, options:)
105
- context.repository = options[:repository].new(options[:model])
108
+ def setup_member_routes(router, slug, options:)
109
+ repository = options[:repository].new(options[:model])
106
110
  action_options = (options[:show] || {}).merge(record_not_found_page: TinyAdmin.settings.record_not_found)
107
111
 
108
112
  router.on String do |reference|
109
- context.reference = reference
110
-
111
113
  # Custom actions
112
114
  custom_actions = setup_custom_actions(
113
115
  router,
114
116
  options[:member_actions],
115
- repository: context.repository,
116
- options: action_options
117
+ options: action_options,
118
+ repository: repository,
119
+ slug: slug,
120
+ reference: reference
117
121
  )
118
122
 
119
123
  # Show
120
124
  if options[:only].include?(:show) || options[:only].include?('show')
121
125
  router.is do
122
- context.actions = custom_actions
123
- context.request = request
126
+ context = Context.new(
127
+ actions: custom_actions,
128
+ reference: reference,
129
+ repository: repository,
130
+ request: request,
131
+ router: router,
132
+ slug: slug
133
+ )
124
134
  show_action = TinyAdmin::Actions::Show.new
125
135
  render_page show_action.call(app: self, context: context, options: action_options)
126
136
  end
@@ -128,15 +138,20 @@ module TinyAdmin
128
138
  end
129
139
  end
130
140
 
131
- def setup_custom_actions(router, custom_actions, repository:, options:)
132
- context.repository = repository
141
+ def setup_custom_actions(router, custom_actions, options:, repository:, slug:, reference: nil)
133
142
  (custom_actions || []).each_with_object({}) do |custom_action, result|
134
143
  action_slug, action = custom_action.first
135
- action_class = action.is_a?(String) ? Object.const_get(action) : action
144
+ action_class = to_class(action)
136
145
 
137
146
  router.get action_slug.to_s do
138
- context.actions = {}
139
- context.request = request
147
+ context = Context.new(
148
+ actions: {},
149
+ reference: reference,
150
+ repository: repository,
151
+ request: request,
152
+ router: router,
153
+ slug: slug
154
+ )
140
155
  custom_action = action_class.new
141
156
  render_page custom_action.call(app: self, context: context, options: options)
142
157
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ class Section
5
+ attr_reader :name, :options, :path, :slug
6
+
7
+ def initialize(name:, slug: nil, path: nil, options: {})
8
+ @name = name
9
+ @options = options
10
+ @path = path || TinyAdmin.route_for(slug)
11
+ @slug = slug
12
+ end
13
+ end
14
+ end
@@ -3,7 +3,6 @@
3
3
  module TinyAdmin
4
4
  class Settings
5
5
  include Singleton
6
- include Utils
7
6
 
8
7
  DEFAULTS = {
9
8
  %i[authentication plugin] => Plugins::NoAuth,
@@ -19,7 +18,8 @@ module TinyAdmin
19
18
  %i[repository] => Plugins::ActiveRecordRepository,
20
19
  %i[root_path] => '/admin',
21
20
  %i[root page] => Views::Pages::Root,
22
- %i[root title] => 'TinyAdmin'
21
+ %i[root title] => 'TinyAdmin',
22
+ %i[sections] => []
23
23
  }.freeze
24
24
 
25
25
  OPTIONS = %i[
@@ -38,6 +38,8 @@ module TinyAdmin
38
38
  style_links
39
39
  ].freeze
40
40
 
41
+ attr_reader :store
42
+
41
43
  OPTIONS.each do |option|
42
44
  define_method(option) do
43
45
  self[option]
@@ -70,15 +72,14 @@ module TinyAdmin
70
72
  end
71
73
  end
72
74
 
73
- context.pages ||= {}
74
- context.resources ||= {}
75
- self.sections ||= []
75
+ @store ||= TinyAdmin::Store.new(self)
76
76
  self.root_path = '/' if root_path == ''
77
77
 
78
78
  if authentication[:plugin] <= Plugins::SimpleAuth
79
- authentication[:logout] ||= { name: 'logout', path: "#{root_path}/auth/logout" }
79
+ logout_path = "#{root_path}/auth/logout"
80
+ authentication[:logout] ||= TinyAdmin::Section.new(name: 'logout', slug: 'logout', path: logout_path)
80
81
  end
81
- context.navbar = prepare_navbar(sections, logout: authentication[:logout])
82
+ store.prepare_sections(sections, logout: authentication[:logout])
82
83
  end
83
84
 
84
85
  def reset!
@@ -97,7 +98,7 @@ module TinyAdmin
97
98
  if value.is_a?(Hash)
98
99
  value.each_key do |key2|
99
100
  path = [key, key2]
100
- if DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)
101
+ if (DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)) && self[key][key2].is_a?(String)
101
102
  self[key][key2] = Object.const_get(self[key][key2])
102
103
  end
103
104
  end
@@ -105,59 +106,5 @@ module TinyAdmin
105
106
  self[key] = Object.const_get(self[key])
106
107
  end
107
108
  end
108
-
109
- def prepare_navbar(sections, logout:)
110
- items = sections.each_with_object({}) do |section, list|
111
- unless section.is_a?(Hash)
112
- section_class = section.is_a?(String) ? Object.const_get(section) : section
113
- next unless section_class.respond_to?(:to_h)
114
-
115
- section = section_class.to_h
116
- end
117
-
118
- slug = section[:slug].to_s
119
- case section[:type]&.to_sym
120
- when :content
121
- list[slug] = add_content_section(slug, section)
122
- when :page
123
- list[slug] = add_page_section(slug, section)
124
- when :resource
125
- list[slug] = add_resource_section(slug, section)
126
- when :url
127
- list[slug] = add_url_section(slug, section)
128
- end
129
- end
130
- items['auth/logout'] = logout if logout
131
- items
132
- end
133
-
134
- def add_content_section(slug, section)
135
- context.pages[slug] = { class: content_page, content: section[:content] }
136
- { name: section[:name], path: route_for(slug), class: content_page }
137
- end
138
-
139
- def add_page_section(slug, section)
140
- page = section[:page]
141
- page_class = page.is_a?(String) ? Object.const_get(page) : page
142
- context.pages[slug] = { class: page_class }
143
- { name: section[:name], path: route_for(slug), class: page_class }
144
- end
145
-
146
- def add_resource_section(slug, section)
147
- repo = section[:repository] || repository
148
- context.resources[slug] = {
149
- model: section[:model].is_a?(String) ? Object.const_get(section[:model]) : section[:model],
150
- repository: repo.is_a?(String) ? Object.const_get(repo) : repo
151
- }
152
- resource_options = section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
153
- resource_options[:only] ||= %i[index show]
154
- context.resources[slug].merge!(resource_options)
155
- hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
156
- { name: section[:name], path: route_for(slug) } unless hidden
157
- end
158
-
159
- def add_url_section(_slug, section)
160
- section.slice(:name, :options).tap { _1[:path] = section[:url] }
161
- end
162
109
  end
163
110
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ class Store
5
+ include Utils
6
+
7
+ attr_reader :navbar, :pages, :resources, :settings
8
+
9
+ def initialize(settings)
10
+ @pages = {}
11
+ @resources = {}
12
+ @settings = settings
13
+ end
14
+
15
+ def prepare_sections(sections, logout:)
16
+ @navbar = sections.each_with_object([]) do |section, list|
17
+ unless section.is_a?(Hash)
18
+ section_class = to_class(section)
19
+ next unless section_class.respond_to?(:to_h)
20
+
21
+ section = section_class.to_h
22
+ end
23
+
24
+ slug = section[:slug].to_s
25
+ case section[:type]&.to_sym
26
+ when :content
27
+ list << add_content_section(slug, section)
28
+ when :page
29
+ list << add_page_section(slug, section)
30
+ when :resource
31
+ list << add_resource_section(slug, section)
32
+ when :url
33
+ list << add_url_section(slug, section)
34
+ end
35
+ end
36
+ navbar << logout if logout
37
+ end
38
+
39
+ private
40
+
41
+ def add_content_section(slug, section)
42
+ pages[slug] = { class: settings.content_page, content: section[:content], widgets: section[:widgets] }
43
+ TinyAdmin::Section.new(name: section[:name], slug: slug)
44
+ end
45
+
46
+ def add_page_section(slug, section)
47
+ pages[slug] = { class: to_class(section[:page]) }
48
+ TinyAdmin::Section.new(name: section[:name], slug: slug)
49
+ end
50
+
51
+ def add_resource_section(slug, section)
52
+ resource = section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
53
+ resource[:only] ||= %i[index show]
54
+ resources[slug] = resource.merge(
55
+ model: to_class(section[:model]),
56
+ repository: to_class(section[:repository] || settings.repository)
57
+ )
58
+
59
+ hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
60
+ TinyAdmin::Section.new(name: section[:name], slug: slug) unless hidden
61
+ end
62
+
63
+ def add_url_section(slug, section)
64
+ TinyAdmin::Section.new(name: section[:name], options: section[:options], path: section[:url], slug: slug)
65
+ end
66
+ end
67
+ end
@@ -14,27 +14,27 @@ module TinyAdmin
14
14
  list.join('&')
15
15
  end
16
16
 
17
- def prepare_page(page_class, options: nil)
17
+ def prepare_page(page_class, slug: nil, attributes: nil, options: nil)
18
18
  page_class.new.tap do |page|
19
19
  page.options = options
20
20
  page.head_component = TinyAdmin.settings.components[:head]&.new
21
21
  page.flash_component = TinyAdmin.settings.components[:flash]&.new
22
22
  page.navbar_component = TinyAdmin.settings.components[:navbar]&.new
23
23
  page.navbar_component&.update_attributes(
24
- current_slug: context&.slug,
24
+ current_slug: slug,
25
25
  root_path: TinyAdmin.settings.root_path,
26
26
  root_title: TinyAdmin.settings.root[:title],
27
- items: options&.include?(:no_menu) ? [] : context&.navbar
27
+ items: options&.include?(:no_menu) ? [] : TinyAdmin.settings.store&.navbar
28
28
  )
29
+ attrs = attributes || {}
30
+ attrs[:widgets] = attrs[:widgets].map { to_class(_1) } if attrs[:widgets]
31
+ page.update_attributes(attrs) unless attrs.empty?
29
32
  yield(page) if block_given?
30
33
  end
31
34
  end
32
35
 
33
- def route_for(section, reference: nil, action: nil, query: nil)
34
- root_path = TinyAdmin.settings.root_path == '/' ? nil : TinyAdmin.settings.root_path
35
- route = [root_path, section, reference, action].compact.join("/")
36
- route << "?#{query}" if query
37
- route[0] == '/' ? route : route.prepend('/')
36
+ def to_class(klass)
37
+ klass.is_a?(String) ? Object.const_get(klass) : klass
38
38
  end
39
39
 
40
40
  def to_label(string)
@@ -42,9 +42,5 @@ module TinyAdmin
42
42
 
43
43
  string.respond_to?(:humanize) ? string.humanize : string.tr('_', ' ').capitalize
44
44
  end
45
-
46
- def context
47
- TinyAdmin::Context.instance
48
- end
49
45
  end
50
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -4,14 +4,16 @@ module TinyAdmin
4
4
  module Views
5
5
  module Actions
6
6
  class Index < DefaultLayout
7
- attr_accessor :actions, :fields, :filters, :links, :pagination_component, :prepare_record, :records
7
+ attr_accessor :actions, :fields, :filters, :links, :pagination_component, :prepare_record, :records, :slug
8
8
 
9
9
  def template
10
10
  super do
11
11
  div(class: 'index') {
12
12
  div(class: 'row') {
13
13
  div(class: 'col-4') {
14
- h1(class: 'title') { title }
14
+ h1(class: 'title') {
15
+ title
16
+ }
15
17
  }
16
18
  div(class: 'col-8') {
17
19
  actions_buttons
@@ -31,13 +33,15 @@ module TinyAdmin
31
33
  if filters&.any?
32
34
  div(class: 'col-3') {
33
35
  filters_form = TinyAdmin::Views::Components::FiltersForm.new
34
- filters_form.update_attributes(section_path: route_for(context.slug), filters: filters)
36
+ filters_form.update_attributes(section_path: TinyAdmin.route_for(slug), filters: filters)
35
37
  render filters_form
36
38
  }
37
39
  end
38
40
  }
39
41
 
40
42
  render pagination_component if pagination_component
43
+
44
+ render TinyAdmin::Views::Components::Widgets.new(widgets)
41
45
  }
42
46
  end
43
47
  end
@@ -66,7 +70,7 @@ module TinyAdmin
66
70
  field = fields[key]
67
71
  td(class: "field-value-#{field.name} field-value-type-#{field.type}") {
68
72
  if field.options && field.options[:link_to]
69
- a(href: route_for(field.options[:link_to], reference: value)) {
73
+ a(href: TinyAdmin.route_for(field.options[:link_to], reference: value)) {
70
74
  field.apply_call_option(record) || value
71
75
  }
72
76
  else
@@ -82,15 +86,15 @@ module TinyAdmin
82
86
  links.each do |link|
83
87
  whitespace
84
88
  if link == 'show'
85
- a(href: route_for(context.slug, reference: record.id), class: link_class) { 'show' }
89
+ a(href: TinyAdmin.route_for(slug, reference: record.id), class: link_class) { 'show' }
86
90
  else
87
- a(href: route_for(context.slug, reference: record.id, action: link), class: link_class) {
91
+ a(href: TinyAdmin.route_for(slug, reference: record.id, action: link), class: link_class) {
88
92
  to_label(link)
89
93
  }
90
94
  end
91
95
  end
92
96
  else
93
- a(href: route_for(context.slug, reference: record.id), class: link_class) { 'show' }
97
+ a(href: TinyAdmin.route_for(slug, reference: record.id), class: link_class) { 'show' }
94
98
  end
95
99
  }
96
100
  }
@@ -103,7 +107,7 @@ module TinyAdmin
103
107
  ul(class: 'nav justify-content-end') {
104
108
  (actions || {}).each do |action, action_class|
105
109
  li(class: 'nav-item mx-1') {
106
- href = route_for(context.slug, action: action)
110
+ href = TinyAdmin.route_for(slug, action: action)
107
111
  a(href: href, class: 'nav-link btn btn-outline-secondary') {
108
112
  action_class.respond_to?(:title) ? action_class.title : action
109
113
  }
@@ -4,7 +4,7 @@ module TinyAdmin
4
4
  module Views
5
5
  module Actions
6
6
  class Show < DefaultLayout
7
- attr_accessor :actions, :fields, :prepare_record, :record
7
+ attr_accessor :actions, :fields, :prepare_record, :record, :reference, :slug
8
8
 
9
9
  def template
10
10
  super do
@@ -26,7 +26,7 @@ module TinyAdmin
26
26
  end
27
27
  div(class: 'field-value col-10') {
28
28
  if field.options[:link_to]
29
- a(href: route_for(field.options[:link_to], reference: value)) {
29
+ a(href: TinyAdmin.route_for(field.options[:link_to], reference: value)) {
30
30
  field.apply_call_option(record) || value
31
31
  }
32
32
  else
@@ -35,6 +35,8 @@ module TinyAdmin
35
35
  }
36
36
  }
37
37
  end
38
+
39
+ render TinyAdmin::Views::Components::Widgets.new(widgets)
38
40
  }
39
41
  end
40
42
  end
@@ -45,7 +47,7 @@ module TinyAdmin
45
47
  ul(class: 'nav justify-content-end') {
46
48
  (actions || {}).each do |action, action_class|
47
49
  li(class: 'nav-item mx-1') {
48
- href = route_for(context.slug, reference: context.reference, action: action)
50
+ href = TinyAdmin.route_for(slug, reference: reference, action: action)
49
51
  a(href: href, class: 'nav-link btn btn-outline-secondary') {
50
52
  action_class.respond_to?(:title) ? action_class.title : action
51
53
  }
@@ -5,7 +5,7 @@ module TinyAdmin
5
5
  class BasicLayout < Phlex::HTML
6
6
  include Utils
7
7
 
8
- attr_accessor :content
8
+ attr_accessor :content, :widgets
9
9
 
10
10
  def update_attributes(attributes)
11
11
  attributes.each do |key, value|
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ module Views
5
+ class BasicWidget < Phlex::HTML
6
+ end
7
+ end
8
+ end
@@ -23,14 +23,14 @@ module TinyAdmin
23
23
  }
24
24
  div(class: 'collapse navbar-collapse', id: 'navbarNav') {
25
25
  ul(class: 'navbar-nav') {
26
- items.each do |slug, item|
26
+ items.each do |item|
27
27
  classes = %w[nav-link]
28
- classes << 'active' if slug == current_slug
29
- link_attributes = { class: classes.join(' '), href: item[:path], 'aria-current' => 'page' }
30
- link_attributes.merge!(item[:options]) if item[:options]
28
+ classes << 'active' if item.slug == current_slug
29
+ link_attributes = { class: classes.join(' '), href: item.path, 'aria-current' => 'page' }
30
+ link_attributes.merge!(item.options) if item.options
31
31
 
32
32
  li(class: 'nav-item') {
33
- a(**link_attributes) { item[:name] }
33
+ a(**link_attributes) { item.name }
34
34
  }
35
35
  end
36
36
  }
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ module Views
5
+ module Components
6
+ class Widgets < BasicComponent
7
+ def initialize(widgets)
8
+ @widgets = widgets
9
+ end
10
+
11
+ def template
12
+ return if @widgets.nil? || @widgets.empty?
13
+
14
+ div(class: 'container widgets') {
15
+ @widgets.each_slice(2).each do |row|
16
+ div(class: 'row') {
17
+ row.each do |widget|
18
+ next unless widget < Phlex::HTML
19
+
20
+ div(class: 'col') {
21
+ div(class: 'card') {
22
+ div(class: 'card-body') {
23
+ render widget.new
24
+ }
25
+ }
26
+ }
27
+ end
28
+ }
29
+ end
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,7 +7,11 @@ module TinyAdmin
7
7
  def template
8
8
  super do
9
9
  div(class: 'content') {
10
- unsafe_raw(content)
10
+ div(class: 'content-data') {
11
+ unsafe_raw(content)
12
+ }
13
+
14
+ render TinyAdmin::Views::Components::Widgets.new(widgets)
11
15
  }
12
16
  end
13
17
  end
@@ -7,7 +7,7 @@ module TinyAdmin
7
7
  def template
8
8
  super do
9
9
  div(class: 'root') {
10
- h1(class: 'title') { 'Tiny Admin' }
10
+ render TinyAdmin::Views::Components::Widgets.new(widgets)
11
11
  }
12
12
  end
13
13
  end
data/lib/tiny_admin.rb CHANGED
@@ -4,6 +4,7 @@ require 'phlex'
4
4
  require 'roda'
5
5
  require 'zeitwerk'
6
6
 
7
+ require 'forwardable'
7
8
  require 'singleton'
8
9
  require 'yaml'
9
10
 
@@ -23,9 +24,16 @@ module TinyAdmin
23
24
  end
24
25
  end
25
26
 
27
+ def route_for(section, reference: nil, action: nil, query: nil)
28
+ root_path = settings.root_path == '/' ? nil : settings.root_path
29
+ route = [root_path, section, reference, action].compact.join("/")
30
+ route << "?#{query}" if query
31
+ route[0] == '/' ? route : route.prepend('/')
32
+ end
33
+
26
34
  def settings
27
35
  TinyAdmin::Settings.instance
28
36
  end
29
37
 
30
- module_function :configure, :configure_from_file, :settings
38
+ module_function :configure, :configure_from_file, :route_for, :settings
31
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiny_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattia Roccoberton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-28 00:00:00.000000000 Z
11
+ date: 2023-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -87,19 +87,23 @@ files:
87
87
  - lib/tiny_admin/plugins/no_auth.rb
88
88
  - lib/tiny_admin/plugins/simple_auth.rb
89
89
  - lib/tiny_admin/router.rb
90
+ - lib/tiny_admin/section.rb
90
91
  - lib/tiny_admin/settings.rb
92
+ - lib/tiny_admin/store.rb
91
93
  - lib/tiny_admin/support.rb
92
94
  - lib/tiny_admin/utils.rb
93
95
  - lib/tiny_admin/version.rb
94
96
  - lib/tiny_admin/views/actions/index.rb
95
97
  - lib/tiny_admin/views/actions/show.rb
96
98
  - lib/tiny_admin/views/basic_layout.rb
99
+ - lib/tiny_admin/views/basic_widget.rb
97
100
  - lib/tiny_admin/views/components/basic_component.rb
98
101
  - lib/tiny_admin/views/components/filters_form.rb
99
102
  - lib/tiny_admin/views/components/flash.rb
100
103
  - lib/tiny_admin/views/components/head.rb
101
104
  - lib/tiny_admin/views/components/navbar.rb
102
105
  - lib/tiny_admin/views/components/pagination.rb
106
+ - lib/tiny_admin/views/components/widgets.rb
103
107
  - lib/tiny_admin/views/default_layout.rb
104
108
  - lib/tiny_admin/views/pages/content.rb
105
109
  - lib/tiny_admin/views/pages/page_not_found.rb