tiny_admin 0.4.0 → 0.5.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: 6eb4a218cc1a4ff6acc940c402d74202582d2092b39f920e6551e5b395da752e
4
- data.tar.gz: fb137625f9f5faa5b33dd5c7633a68eec7a4a2d2bb31db6f70758d6ed9ec3750
3
+ metadata.gz: c3ac2be067ca35c35f40c677c5f9461c5f83e366488a48e6ab50a888f0f22317
4
+ data.tar.gz: 2289dd33940174e8e614260f96273967ade51f75dc67a04d5e639415c984e65b
5
5
  SHA512:
6
- metadata.gz: fd7800ab65bd0e92ce5646210f08823d031fdd217f5230bc99207a0e00375b068ad2337805ddce196ca317fe6f0df60ad3e209fc885e31c765c68c125f83d900
7
- data.tar.gz: f1df2bd938781a04c4af36c9653a7efffcc075cf72accd8f9fbaedbcc74286d860a1953bab159ec833c6a2cf5851a00633f555966b915fedec625af9ef2f85fa
6
+ metadata.gz: f4f80d86ac6b30bc29cf8e2f1efe480edefe84907bf5e9b7a3a8c3b3c3750ff59a1f9e609c87090105c0cd31fa22df7aa913858911a455d918633269c27317e7
7
+ data.tar.gz: 4fe8a75d9e3c0cc3bd756ab247fecb7575049dcec58559bdb7cdb1c58ecb4b6e7de3f8be96c396b0b304134cc75c6058b3cbeafce92cd027413971a99a812c02
data/README.md CHANGED
@@ -16,10 +16,10 @@ Please ⭐ if you like it.
16
16
 
17
17
  ## Install
18
18
 
19
- - Add to your Gemfile: `gem 'tiny_admin', '~> 0.4'`
19
+ - Add to your Gemfile: `gem 'tiny_admin', '~> 0.5'`
20
20
  - Mount the app in a route (check some examples with: Hanami, Rails, Roda and standalone in [extra](extra))
21
21
  + in Rails, update _config/routes.rb_: `mount TinyAdmin::Router => '/admin'`
22
- - Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` (see [configuration](#configuration) below):
22
+ - Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` with a YAML config file (see [configuration](#configuration) below):
23
23
 
24
24
  ```rb
25
25
  TinyAdmin.configure do |settings|
@@ -35,32 +35,37 @@ end
35
35
  ### Authentication
36
36
 
37
37
  Plugins available:
38
- - _SimpleAuth_: session authentication based on Warden (`warden` gem must be included in the Gemfile) using a password hash provided via config or via environment variable (`ADMIN_PASSWORD_HASH`);
39
- - _NoAuth_: no authentication.
38
+
39
+ - **SimpleAuth**: a session authentication based on Warden (`warden` gem must be included in the Gemfile) using a password hash provided via config or via environment variable (`ADMIN_PASSWORD_HASH`). _Disclaimer: this plugin is provided as example, if you need a secure authentication I suggest to create your own._
40
+
41
+ - **NoAuth**: no authentication.
40
42
 
41
43
  ### Repository
42
44
 
43
45
  Plugin available:
44
- - _ActiveRecordRepository_: isolates the query layer to expose the resources in the admin interface.
46
+
47
+ - **ActiveRecordRepository**: isolates the query layer to expose the resources in the admin interface.
45
48
 
46
49
  ### View pages
47
50
 
48
51
  Pages available:
49
- - _Root_: define how to present the content in the main page of the interface;
50
- - _PageNotFound_: define how to present pages not found;
51
- - _RecordNotFound_: define how to present record not found page;
52
- - _SimpleAuthLogin_: define how to present the login form for SimpleAuth plugin;
53
- - _Index_: define how to present a collection of items;
54
- - _Show_: define how to present the details of an item.
52
+
53
+ - **Root**: define how to present the content in the main page of the interface;
54
+ - **PageNotFound**: define how to present pages not found;
55
+ - **RecordNotFound**: define how to present record not found page;
56
+ - **SimpleAuthLogin**: define how to present the login form for SimpleAuth plugin;
57
+ - **Index**: define how to present a collection of items;
58
+ - **Show**: define how to present the details of an item.
55
59
 
56
60
  ### View components
57
61
 
58
62
  Components available:
59
- - _FiltersForm_: define how to present the filters form in the resource collection pages;
60
- - _Flash_: define how to present the flash messages;
61
- - _Head_: define how to present the Head tag;
62
- - _Navbar_: define how to present the navbar (the default one uses the Bootstrap structure);
63
- - _Pagination_: define how to present the pagination of a collection.
63
+
64
+ - **FiltersForm**: define how to present the filters form in the resource collection pages;
65
+ - **Flash**: define how to present the flash messages;
66
+ - **Head**: define how to present the Head tag;
67
+ - **Navbar**: define how to present the navbar (the default one uses the Bootstrap structure);
68
+ - **Pagination**: define how to present the pagination of a collection.
64
69
 
65
70
  ## Configuration
66
71
 
@@ -70,9 +75,10 @@ See [extra](extra) folder for some usage examples.
70
75
  The following options are supported:
71
76
 
72
77
  `root` (Hash): define the root section of the admin, properties:
73
- - `title` (String): root section's title;
74
- - `page` (String): a view object to render;
75
- - `redirect` (String): alternative to _page_ option - redirects to a specific slug;
78
+
79
+ - `title` (String): root section's title;
80
+ - `page` (String): a view object to render;
81
+ - `redirect` (String): alternative to _page_ option - redirects to a specific slug;
76
82
 
77
83
  Example:
78
84
 
@@ -88,9 +94,21 @@ root:
88
94
 
89
95
  `record_not_found` (String): a view object to render when a missing record is requested.
90
96
 
97
+ `style_links` (Array of hashes): list of styles files to include, properties:
98
+
99
+ - `href` (String): URL for the style file;
100
+ - `rel` (String): type of style file.
101
+
102
+ `scripts` (Array of hashes): list of scripts to include, properties:
103
+
104
+ - `src` (String): source URL for the script.
105
+
106
+ `extra_styles` (String): inline CSS styles.
107
+
91
108
  `authentication` (Hash): define the authentication method, properties:
92
- - `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
93
- - `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
109
+
110
+ - `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
111
+ - `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
94
112
 
95
113
  Example:
96
114
 
@@ -101,15 +119,17 @@ authentication:
101
119
  ```
102
120
 
103
121
  `sections` (Array of hashes): define the admin sections, properties:
104
- - `slug` (String): section reference identifier;
105
- - `name` (String): section's title;
106
- - `type` (String): the type of section: `url`, `page` or `resource`;
107
- - other properties depends on the section's type.
122
+
123
+ - `slug` (String): section reference identifier;
124
+ - `name` (String): section's title;
125
+ - `type` (String): the type of section: `url`, `page` or `resource`;
126
+ - other properties depends on the section's type.
108
127
 
109
128
  For _url_ sections:
110
- - `url` (String): the URL to load when clicking on the section's menu item;
111
- - `options` (Hash): properties:
112
- + `target` (String): link _target_ attributes (ex. `_blank`).
129
+
130
+ - `url` (String): the URL to load when clicking on the section's menu item;
131
+ - `options` (Hash): properties:
132
+ + `target` (String): link _target_ attributes (ex. `_blank`).
113
133
 
114
134
  Example:
115
135
 
@@ -123,7 +143,8 @@ options:
123
143
  ```
124
144
 
125
145
  For _page_ sections:
126
- - `page` (String): a view object to render.
146
+
147
+ - `page` (String): a view object to render.
127
148
 
128
149
  Example:
129
150
 
@@ -135,14 +156,15 @@ page: Admin::Stats
135
156
  ```
136
157
 
137
158
  For _resource_ sections:
138
- - `model` (String): the class to use to fetch the data on an item of a collection;
139
- - `repository` (String): the class to get the properties related to the model;
140
- - `index` (Hash): collection's action options;
141
- - `show` (Hash): detail's action options;
142
- - `collection_actions` (Array of hashes): custom collection's actions;
143
- - `member_actions` (Array of hashes): custom details's actions;
144
- - `only` (Array of strings): list of supported actions (ex. `index`);
145
- - `options` (Array of strings): resource options (ex. `hidden`).
159
+
160
+ - `model` (String): the class to use to fetch the data on an item of a collection;
161
+ - `repository` (String): the class to get the properties related to the model;
162
+ - `index` (Hash): collection's action options (see below);
163
+ - `show` (Hash): detail's action options (see below);
164
+ - `collection_actions` (Array of hashes): custom collection's actions;
165
+ - `member_actions` (Array of hashes): custom details's actions;
166
+ - `only` (Array of strings): list of supported actions (ex. `index`);
167
+ - `options` (Array of strings): resource options (ex. `hidden`).
146
168
 
147
169
  Example:
148
170
 
@@ -153,14 +175,72 @@ type: resource
153
175
  model: Post
154
176
  ```
155
177
 
156
- `style_links` (Array of hashes): list of styles files to include, properties:
157
- - `href` (String): URL for the style file;
158
- - `rel` (String): type of style file.
178
+ #### Resource index options
159
179
 
160
- `scripts` (Array of hashes): list of scripts to include, properties:
161
- - `src` (String): source URL for the script.
180
+ The Index hash supports the following options:
162
181
 
163
- `extra_styles` (String): inline CSS styles.
182
+ - `attributes` (Array): fields to expose in the resource list page;
183
+ - `filters` (Array): filter the current listing;
184
+ - `links` (Array): custom member actions to expose for each list's entry (defined in _member_actions_);
185
+ - `pagination` (Integer): max pages size;
186
+ - `sort` (Array): sort options to pass to the listing query.
187
+
188
+ Example:
189
+
190
+ ```yml
191
+ index:
192
+ sort:
193
+ - id DESC
194
+ pagination: 10
195
+ attributes:
196
+ - id
197
+ - author: call, name
198
+ - position: round, 1
199
+ - field: author_id
200
+ header: The author
201
+ link_to: authors
202
+ call: author, name
203
+ filters:
204
+ - title
205
+ - field: state
206
+ type: select
207
+ values:
208
+ - published
209
+ - draft
210
+ - archived
211
+ links:
212
+ - show
213
+ - author_posts
214
+ - csv_export
215
+ ```
216
+
217
+ #### Resource show options
218
+
219
+ The Show hash supports the following options:
220
+
221
+ - `attributes` (Array): fields to expose in the resource details page.
222
+
223
+ Example:
224
+
225
+ ```yml
226
+ show:
227
+ attributes:
228
+ # Expose the id column
229
+ - id
230
+ # Expose the title column, calling `downcase` support method
231
+ - title: downcase
232
+ # Expose the category column, calling `upcase` support method
233
+ - category: upcase
234
+ # Expose the position column, calling `format` support method with argument %f
235
+ - position: format, %f
236
+ # Expose the position created_at, calling `strftime` support method with argument %Y%m%d %H:%M
237
+ - created_at: strftime, %Y%m%d %H:%M
238
+ # Expose the author_id column, with a custom header label, linked to authors section and calling author.name to get the value
239
+ - field: author_id
240
+ header: The author
241
+ link_to: authors
242
+ call: author, name
243
+ ```
164
244
 
165
245
  ### Sample
166
246
 
@@ -6,6 +6,7 @@ module TinyAdmin
6
6
  attr_reader :current_page,
7
7
  :fields_options,
8
8
  :filters_list,
9
+ :links,
9
10
  :pagination,
10
11
  :pages,
11
12
  :params,
@@ -13,7 +14,7 @@ module TinyAdmin
13
14
  :repository,
14
15
  :sort
15
16
 
16
- def call(app:, context:, options:, actions:)
17
+ def call(app:, context:, options:)
17
18
  evaluate_options(options)
18
19
  fields = repository.fields(options: fields_options)
19
20
  filters = prepare_filters(fields, filters_list)
@@ -22,9 +23,10 @@ module TinyAdmin
22
23
  prepare_page(Views::Actions::Index) do |page|
23
24
  setup_pagination(page, settings.components[:pagination], total_count: total_count)
24
25
  page.update_attributes(
25
- actions: actions,
26
+ actions: context.actions,
26
27
  fields: fields,
27
28
  filters: filters,
29
+ links: links,
28
30
  prepare_record: ->(record) { repository.index_record_attrs(record, fields: fields_options) },
29
31
  records: records,
30
32
  title: repository.index_title
@@ -40,7 +42,8 @@ module TinyAdmin
40
42
  @repository = context.repository
41
43
  @filters_list = options[:filters]
42
44
  @pagination = options[:pagination] || 10
43
- @sort = options[:sort] || ['id']
45
+ @sort = options[:sort]
46
+ @links = options[:links]
44
47
 
45
48
  @current_page = (params['p'] || 1).to_i
46
49
  @query_string = params_to_s(params.except('p'))
@@ -60,7 +63,7 @@ module TinyAdmin
60
63
  return if pages <= 1 || !pagination_component_class
61
64
 
62
65
  page.pagination_component = pagination_component_class.new
63
- page.pagination_component.update(current: current_page, pages: pages, query_string: query_string)
66
+ page.pagination_component.update_attributes(current: current_page, pages: pages, query_string: query_string)
64
67
  end
65
68
  end
66
69
  end
@@ -3,18 +3,17 @@
3
3
  module TinyAdmin
4
4
  module Actions
5
5
  class Show < BasicAction
6
- attr_reader :repository
7
-
8
- def call(app:, context:, options:, actions:)
9
- @repository = context.repository
6
+ def call(app:, context:, options:)
10
7
  fields_options = attribute_options(options[:attributes])
8
+ repository = context.repository
11
9
  record = repository.find(context.reference)
10
+ prepare_record = ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) }
12
11
 
13
12
  prepare_page(Views::Actions::Show) do |page|
14
13
  page.update_attributes(
15
- actions: actions,
14
+ actions: context.actions,
16
15
  fields: repository.fields(options: fields_options),
17
- prepare_record: ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) },
16
+ prepare_record: prepare_record,
18
17
  record: record,
19
18
  title: repository.show_title(record)
20
19
  )
@@ -4,6 +4,15 @@ module TinyAdmin
4
4
  class Context
5
5
  include Singleton
6
6
 
7
- attr_accessor :reference, :repository, :request, :router, :settings, :slug
7
+ attr_accessor :actions,
8
+ :navbar,
9
+ :pages,
10
+ :reference,
11
+ :repository,
12
+ :request,
13
+ :resources,
14
+ :router,
15
+ :settings,
16
+ :slug
8
17
  end
9
18
  end
@@ -7,19 +7,20 @@ module TinyAdmin
7
7
  def initialize(name:, title:, type:, options: {})
8
8
  @type = type
9
9
  @name = name
10
- @title = title || name
10
+ @title = title
11
11
  @options = options
12
12
  end
13
13
 
14
+ def apply_call_option(target)
15
+ messages = (options[:call] || '').split(',').map(&:strip)
16
+ messages.inject(target) { |result, msg| result&.send(msg) } if messages.any?
17
+ end
18
+
14
19
  class << self
15
20
  def create_field(name:, title: nil, type: nil, options: {})
16
21
  field_name = name.to_s
17
- new(
18
- name: field_name,
19
- title: title || field_name.respond_to?(:humanize) ? field_name.humanize : field_name.tr('_', ' ').capitalize,
20
- type: type || :string,
21
- options: options
22
- )
22
+ field_title = field_name.respond_to?(:humanize) ? field_name.humanize : field_name.tr('_', ' ').capitalize
23
+ new(name: field_name, title: title || field_title, type: type || :string, options: options || {})
23
24
  end
24
25
  end
25
26
  end
@@ -42,8 +42,8 @@ module TinyAdmin
42
42
  raise BaseRepository::RecordNotFound, e.message
43
43
  end
44
44
 
45
- def list(page: 1, limit: 10, sort: ['id'], filters: nil)
46
- query = model.all.order(sort)
45
+ def list(page: 1, limit: 10, sort: nil, filters: nil)
46
+ query = sort ? model.all.order(sort) : model.all
47
47
  query = apply_filters(query, filters) if filters
48
48
  page_offset = page.positive? ? (page - 1) * limit : 0
49
49
  records = query.offset(page_offset).limit(limit).to_a
@@ -29,11 +29,11 @@ module TinyAdmin
29
29
  r.redirect settings.root_path
30
30
  end
31
31
 
32
- context.settings.pages.each do |slug, data|
32
+ context.pages.each do |slug, data|
33
33
  setup_page_route(r, slug, data)
34
34
  end
35
35
 
36
- context.settings.resources.each do |slug, options|
36
+ context.resources.each do |slug, options|
37
37
  setup_resource_routes(r, slug, options: options || {})
38
38
  end
39
39
 
@@ -88,12 +88,12 @@ module TinyAdmin
88
88
  )
89
89
 
90
90
  # Index
91
- actions = options[:only]
92
- if !actions || actions.include?(:index) || actions.include?('index')
91
+ if options[:only].include?(:index) || options[:only].include?('index')
93
92
  router.is do
93
+ context.actions = custom_actions
94
94
  context.request = request
95
95
  index_action = TinyAdmin::Actions::Index.new
96
- render_page index_action.call(app: self, context: context, options: action_options, actions: custom_actions)
96
+ render_page index_action.call(app: self, context: context, options: action_options)
97
97
  end
98
98
  end
99
99
  end
@@ -114,12 +114,12 @@ module TinyAdmin
114
114
  )
115
115
 
116
116
  # Show
117
- actions = options[:only]
118
- if !actions || actions.include?(:show) || actions.include?('show')
117
+ if options[:only].include?(:show) || options[:only].include?('show')
119
118
  router.is do
119
+ context.actions = custom_actions
120
120
  context.request = request
121
121
  show_action = TinyAdmin::Actions::Show.new
122
- render_page show_action.call(app: self, context: context, options: action_options, actions: custom_actions)
122
+ render_page show_action.call(app: self, context: context, options: action_options)
123
123
  end
124
124
  end
125
125
  end
@@ -132,6 +132,7 @@ module TinyAdmin
132
132
  action_class = action.is_a?(String) ? Object.const_get(action) : action
133
133
 
134
134
  router.get action_slug.to_s do
135
+ context.actions = {}
135
136
  context.request = request
136
137
  custom_action = action_class.new
137
138
  render_page custom_action.call(app: self, context: context, options: options)
@@ -25,7 +25,6 @@ module TinyAdmin
25
25
  :components,
26
26
  :extra_styles,
27
27
  :helper_class,
28
- :navbar,
29
28
  :page_not_found,
30
29
  :record_not_found,
31
30
  :repository,
@@ -35,8 +34,6 @@ module TinyAdmin
35
34
  :scripts,
36
35
  :style_links
37
36
 
38
- attr_reader :pages, :resources
39
-
40
37
  def [](key)
41
38
  send(key)
42
39
  end
@@ -57,39 +54,15 @@ module TinyAdmin
57
54
  end
58
55
  end
59
56
 
60
- @pages ||= {}
61
- @resources ||= {}
57
+ context.pages ||= {}
58
+ context.resources ||= {}
62
59
  @sections ||= []
63
60
  @root_path = '/' if @root_path == ''
64
- if @authentication[:plugin] == Plugins::SimpleAuth
65
- @authentication[:logout] ||= ['logout', "#{root_path}/auth/logout"]
66
- end
67
- @navbar = prepare_navbar(sections, logout: authentication[:logout])
68
- end
69
61
 
70
- def prepare_navbar(sections, logout:)
71
- items = sections.each_with_object({}) do |section, list|
72
- slug = section[:slug]
73
- case section[:type]&.to_sym
74
- when :url
75
- list[slug] = [section[:name], section[:url], section[:options]]
76
- when :page
77
- page = section[:page]
78
- pages[slug] = page.is_a?(String) ? Object.const_get(page) : page
79
- list[slug] = [section[:name], route_for(slug)]
80
- when :resource
81
- repository = section[:repository] || settings.repository
82
- resources[slug] = {
83
- model: section[:model].is_a?(String) ? Object.const_get(section[:model]) : section[:model],
84
- repository: repository.is_a?(String) ? Object.const_get(repository) : repository
85
- }
86
- resources[slug].merge! section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
87
- hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
88
- list[slug] = [section[:name], route_for(slug)] unless hidden
89
- end
62
+ if @authentication[:plugin] <= Plugins::SimpleAuth
63
+ @authentication[:logout] ||= { name: 'logout', path: "#{root_path}/auth/logout" }
90
64
  end
91
- items['auth/logout'] = logout if logout
92
- items
65
+ context.navbar = prepare_navbar(sections, logout: authentication[:logout])
93
66
  end
94
67
 
95
68
  private
@@ -106,5 +79,51 @@ module TinyAdmin
106
79
  self[key] = Object.const_get(self[key])
107
80
  end
108
81
  end
82
+
83
+ def prepare_navbar(sections, logout:)
84
+ items = sections.each_with_object({}) do |section, list|
85
+ unless section.is_a?(Hash)
86
+ section_class = Object.const_get(section)
87
+ next unless section_class.respond_to?(:to_h)
88
+
89
+ section = section_class.to_h
90
+ end
91
+
92
+ slug = section[:slug].to_s
93
+ case section[:type]&.to_sym
94
+ when :url
95
+ list[slug] = add_url_section(slug, section)
96
+ when :page
97
+ list[slug] = add_page_section(slug, section)
98
+ when :resource
99
+ list[slug] = add_resource_section(slug, section)
100
+ end
101
+ end
102
+ items['auth/logout'] = logout if logout
103
+ items
104
+ end
105
+
106
+ def add_url_section(_slug, section)
107
+ section.slice(:name, :options).tap { _1[:path] = section[:url] }
108
+ end
109
+
110
+ def add_page_section(slug, section)
111
+ page = section[:page]
112
+ context.pages[slug] = page.is_a?(String) ? Object.const_get(page) : page
113
+ { name: section[:name], path: route_for(slug), class: context.pages[slug] }
114
+ end
115
+
116
+ def add_resource_section(slug, section)
117
+ repository = section[:repository] || settings.repository
118
+ context.resources[slug] = {
119
+ model: section[:model].is_a?(String) ? Object.const_get(section[:model]) : section[:model],
120
+ repository: repository.is_a?(String) ? Object.const_get(repository) : repository
121
+ }
122
+ resource_options = section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
123
+ resource_options[:only] ||= %i[index show]
124
+ context.resources[slug].merge!(resource_options)
125
+ hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
126
+ { name: section[:name], path: route_for(slug) } unless hidden
127
+ end
109
128
  end
110
129
  end
@@ -4,7 +4,7 @@ module TinyAdmin
4
4
  class Support
5
5
  class << self
6
6
  def call(value, options: [])
7
- value && options&.any? ? options.inject(value) { |result, message| result&.send(message) } : value
7
+ options.inject(value) { |result, message| result&.send(message) } if value && options&.any?
8
8
  end
9
9
 
10
10
  def downcase(value, options: [])
@@ -12,7 +12,7 @@ module TinyAdmin
12
12
  end
13
13
 
14
14
  def format(value, options: [])
15
- value && options&.any? ? Kernel.format(options.first, value) : value
15
+ Kernel.format(options.first, value) if value && options&.any?
16
16
  end
17
17
 
18
18
  def round(value, options: [])
@@ -20,11 +20,11 @@ module TinyAdmin
20
20
  end
21
21
 
22
22
  def strftime(value, options: [])
23
- value ? value.strftime(options&.first || '%Y-%m-%d %H:%M') : ''
23
+ value&.strftime(options&.first || '%Y-%m-%d %H:%M')
24
24
  end
25
25
 
26
26
  def to_date(value, options: [])
27
- value ? value.to_date.to_s : value
27
+ value.to_date.to_s if value
28
28
  end
29
29
 
30
30
  def upcase(value, options: [])
@@ -5,7 +5,8 @@ module TinyAdmin
5
5
  def params_to_s(params)
6
6
  list = params.each_with_object([]) do |(param, value), result|
7
7
  if value.is_a?(Hash)
8
- result.concat(value.map { |k, v| "#{param}[#{k}]=#{v}" })
8
+ values = value.map { |key, val| "#{param}[#{key}]=#{val}" }
9
+ result.concat(values)
9
10
  else
10
11
  result.push(["#{param}=#{value}"])
11
12
  end
@@ -18,13 +19,13 @@ module TinyAdmin
18
19
  page.options = options
19
20
  page.head_component = settings.components[:head]&.new
20
21
  page.flash_component = settings.components[:flash]&.new
21
- page.navbar_component = settings.components[:navbar]&.new(
22
+ page.navbar_component = settings.components[:navbar]&.new
23
+ page.navbar_component&.update_attributes(
22
24
  current_slug: context&.slug,
23
25
  root_path: settings.root_path,
24
26
  root_title: settings.root[:title],
25
- items: options&.include?(:no_menu) ? [] : settings.navbar
27
+ items: options&.include?(:no_menu) ? [] : context&.navbar
26
28
  )
27
-
28
29
  yield(page) if block_given?
29
30
  end
30
31
  end
@@ -36,6 +37,12 @@ module TinyAdmin
36
37
  route[0] == '/' ? route : route.prepend('/')
37
38
  end
38
39
 
40
+ def to_label(string)
41
+ return '' unless string
42
+
43
+ string.respond_to?(:humanize) ? string.humanize : string.tr('_', ' ').capitalize
44
+ end
45
+
39
46
  def context
40
47
  TinyAdmin::Context.instance
41
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -4,7 +4,7 @@ module TinyAdmin
4
4
  module Views
5
5
  module Actions
6
6
  class Index < DefaultLayout
7
- attr_accessor :actions, :fields, :filters, :pagination_component, :prepare_record, :records
7
+ attr_accessor :actions, :fields, :filters, :links, :pagination_component, :prepare_record, :records
8
8
 
9
9
  def template
10
10
  super do
@@ -30,8 +30,9 @@ module TinyAdmin
30
30
 
31
31
  if filters&.any?
32
32
  div(class: 'col-3') {
33
- filters_form_attrs = { section_path: route_for(context.slug), filters: filters }
34
- render TinyAdmin::Views::Components::FiltersForm.new(**filters_form_attrs)
33
+ filters_form = TinyAdmin::Views::Components::FiltersForm.new
34
+ filters_form.update_attributes(section_path: route_for(context.slug), filters: filters)
35
+ render filters_form
35
36
  }
36
37
  end
37
38
  }
@@ -65,16 +66,33 @@ module TinyAdmin
65
66
  field = fields[key]
66
67
  td(class: "field-value-#{field.name} field-value-type-#{field.type}") {
67
68
  if field.options && field.options[:link_to]
68
- messages = (field.options[:call] || '').split(',').map(&:strip)
69
- label = messages.any? ? messages.inject(record) { |result, msg| result&.send(msg) } : value
70
- a(href: route_for(field.options[:link_to], reference: value)) { label }
69
+ a(href: route_for(field.options[:link_to], reference: value)) {
70
+ field.apply_call_option(record) || value
71
+ }
71
72
  else
72
73
  value
73
74
  end
74
75
  }
75
76
  end
76
- td(class: 'actions') {
77
- a(href: route_for(context.slug, reference: record.id)) { 'show' }
77
+
78
+ td(class: 'actions p-1') {
79
+ div(class: 'btn-group btn-group-sm') {
80
+ link_class = 'btn btn-outline-secondary'
81
+ if links
82
+ links.each do |link|
83
+ whitespace
84
+ if link == 'show'
85
+ a(href: route_for(context.slug, reference: record.id), class: link_class) { 'show' }
86
+ else
87
+ a(href: route_for(context.slug, reference: record.id, action: link), class: link_class) {
88
+ to_label(link)
89
+ }
90
+ end
91
+ end
92
+ else
93
+ a(href: route_for(context.slug, reference: record.id), class: link_class) { 'show' }
94
+ end
95
+ }
78
96
  }
79
97
  }
80
98
  end
@@ -86,8 +104,9 @@ module TinyAdmin
86
104
  (actions || {}).each do |action, action_class|
87
105
  li(class: 'nav-item mx-1') {
88
106
  href = route_for(context.slug, action: action)
89
- title = action_class.respond_to?(:title) ? action_class.title : action
90
- a(href: href, class: 'nav-link btn btn-outline-secondary') { title }
107
+ a(href: href, class: 'nav-link btn btn-outline-secondary') {
108
+ action_class.respond_to?(:title) ? action_class.title : action
109
+ }
91
110
  }
92
111
  end
93
112
  }
@@ -25,10 +25,10 @@ module TinyAdmin
25
25
  div(class: 'field-header col-2') { field.options[:header] || field.title }
26
26
  end
27
27
  div(class: 'field-value col-10') {
28
- if field.options && field.options[:link_to]
29
- messages = (field.options[:call] || '').split(',').map(&:strip)
30
- label = messages.any? ? messages.inject(record) { |result, msg| result&.send(msg) } : value
31
- a(href: route_for(field.options[:link_to], reference: value)) { label }
28
+ if field.options[:link_to]
29
+ a(href: route_for(field.options[:link_to], reference: value)) {
30
+ field.apply_call_option(record) || value
31
+ }
32
32
  else
33
33
  value
34
34
  end
@@ -46,8 +46,9 @@ module TinyAdmin
46
46
  (actions || {}).each do |action, action_class|
47
47
  li(class: 'nav-item mx-1') {
48
48
  href = route_for(context.slug, reference: context.reference, action: action)
49
- title = action_class.respond_to?(:title) ? action_class.title : action
50
- a(href: href, class: 'nav-link btn btn-outline-secondary') { title }
49
+ a(href: href, class: 'nav-link btn btn-outline-secondary') {
50
+ action_class.respond_to?(:title) ? action_class.title : action
51
+ }
51
52
  }
52
53
  end
53
54
  }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ module Views
5
+ module Components
6
+ class BasicComponent < Phlex::HTML
7
+ def update_attributes(attributes)
8
+ attributes.each do |key, value|
9
+ send("#{key}=", value)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,13 +3,8 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  module Components
6
- class FiltersForm < Phlex::HTML
7
- attr_reader :filters, :section_path
8
-
9
- def initialize(section_path:, filters:)
10
- @section_path = section_path
11
- @filters = filters
12
- end
6
+ class FiltersForm < BasicComponent
7
+ attr_accessor :filters, :section_path
13
8
 
14
9
  def template
15
10
  form(class: 'form_filters', method: 'get') {
@@ -3,24 +3,21 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  module Components
6
- class Flash < Phlex::HTML
7
- attr_reader :errors, :notices, :warnings
6
+ class Flash < BasicComponent
7
+ attr_accessor :messages
8
8
 
9
9
  def template
10
+ @messages ||= {}
11
+ notices = messages[:notices]
12
+ warnings = messages[:warnings]
13
+ errors = messages[:errors]
14
+
10
15
  div(class: 'flash') {
11
16
  div(class: 'notices alert alert-success', role: 'alert') { notices.join(', ') } if notices&.any?
12
17
  div(class: 'notices alert alert-warning', role: 'alert') { warnings.join(', ') } if warnings&.any?
13
18
  div(class: 'notices alert alert-danger', role: 'alert') { errors.join(', ') } if errors&.any?
14
19
  }
15
20
  end
16
-
17
- def update(messages:)
18
- return unless messages
19
-
20
- @notices = messages[:notices]
21
- @warnings = messages[:warnings]
22
- @errors = messages[:errors]
23
- end
24
21
  end
25
22
  end
26
23
  end
@@ -3,8 +3,8 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  module Components
6
- class Head < Phlex::HTML
7
- attr_reader :extra_styles, :page_title, :style_links
6
+ class Head < BasicComponent
7
+ attr_accessor :extra_styles, :page_title, :style_links
8
8
 
9
9
  def template
10
10
  head {
@@ -19,12 +19,6 @@ module TinyAdmin
19
19
  style { extra_styles } if extra_styles
20
20
  }
21
21
  end
22
-
23
- def update(page_title, style_links: [], extra_styles: nil)
24
- @page_title = page_title
25
- @style_links = style_links
26
- @extra_styles = extra_styles
27
- end
28
22
  end
29
23
  end
30
24
  end
@@ -3,33 +3,34 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  module Components
6
- class Navbar < Phlex::HTML
7
- attr_reader :current_slug, :items, :root_path, :root_title
8
-
9
- def initialize(root_path:, root_title:, items: [], current_slug: nil)
10
- @root_path = root_path
11
- @root_title = root_title
12
- @items = items || []
13
- @current_slug = current_slug
14
- end
6
+ class Navbar < BasicComponent
7
+ attr_accessor :current_slug, :items, :root_path, :root_title
15
8
 
16
9
  def template
17
10
  nav(class: 'navbar navbar-expand-lg') {
18
11
  div(class: 'container') {
19
12
  a(class: 'navbar-brand', href: root_path) { root_title }
20
- button(class: 'navbar-toggler', type: 'button', 'data-bs-toggle' => 'collapse', 'data-bs-target' => '#navbarNav', 'aria-controls' => 'navbarNav', 'aria-expanded' => 'false', 'aria-label' => 'Toggle navigation') {
13
+ button(
14
+ class: 'navbar-toggler',
15
+ type: 'button',
16
+ 'data-bs-toggle' => 'collapse',
17
+ 'data-bs-target' => '#navbarNav',
18
+ 'aria-controls' => 'navbarNav',
19
+ 'aria-expanded' => 'false',
20
+ 'aria-label' => 'Toggle navigation'
21
+ ) {
21
22
  span(class: 'navbar-toggler-icon')
22
23
  }
23
24
  div(class: 'collapse navbar-collapse', id: 'navbarNav') {
24
25
  ul(class: 'navbar-nav') {
25
- items.each do |slug, (name, path, options)|
26
+ items.each do |slug, item|
26
27
  classes = %w[nav-link]
27
28
  classes << 'active' if slug == current_slug
28
- link_attributes = { class: classes.join(' '), href: path, 'aria-current' => 'page' }
29
- link_attributes.merge!(options) if options
29
+ link_attributes = { class: classes.join(' '), href: item[:path], 'aria-current' => 'page' }
30
+ link_attributes.merge!(item[:options]) if item[:options]
30
31
 
31
32
  li(class: 'nav-item') {
32
- a(**link_attributes) { name }
33
+ a(**link_attributes) { item[:name] }
33
34
  }
34
35
  end
35
36
  }
@@ -3,8 +3,8 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  module Components
6
- class Pagination < Phlex::HTML
7
- attr_reader :current, :pages, :query_string
6
+ class Pagination < BasicComponent
7
+ attr_accessor :current, :pages, :query_string
8
8
 
9
9
  def template
10
10
  div(class: 'pagination-div') {
@@ -25,12 +25,6 @@ module TinyAdmin
25
25
  }
26
26
  end
27
27
 
28
- def update(current:, pages:, query_string:)
29
- @current = current
30
- @pages = pages
31
- @query_string = query_string
32
- end
33
-
34
28
  private
35
29
 
36
30
  def pages_range(range, with_dots: false)
@@ -6,8 +6,9 @@ module TinyAdmin
6
6
  attr_accessor :flash_component, :head_component, :messages, :navbar_component, :options, :title
7
7
 
8
8
  def template(&block)
9
- flash_component&.update(messages: messages)
10
- head_component&.update(title, style_links: style_links, extra_styles: settings.extra_styles)
9
+ extra_styles = settings.extra_styles
10
+ flash_component&.messages = messages
11
+ head_component&.update_attributes(page_title: title, style_links: style_links, extra_styles: extra_styles)
11
12
 
12
13
  doctype
13
14
  html {
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.4.0
4
+ version: 0.5.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-18 00:00:00.000000000 Z
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -94,6 +94,7 @@ files:
94
94
  - lib/tiny_admin/views/actions/index.rb
95
95
  - lib/tiny_admin/views/actions/show.rb
96
96
  - lib/tiny_admin/views/basic_layout.rb
97
+ - lib/tiny_admin/views/components/basic_component.rb
97
98
  - lib/tiny_admin/views/components/filters_form.rb
98
99
  - lib/tiny_admin/views/components/flash.rb
99
100
  - lib/tiny_admin/views/components/head.rb