tiny_admin 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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