tiny_admin 0.4.0 → 0.5.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: 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