tiny_admin 0.2.1 → 0.4.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: aa8eb3987f51f28c4f9a55c62fddfd521accf075aac9be4d0b4fab7e88145770
4
- data.tar.gz: 98e2f4af557280ca167816fa325f824d5eda99cde96d35407d472ab1e37b370d
3
+ metadata.gz: 6eb4a218cc1a4ff6acc940c402d74202582d2092b39f920e6551e5b395da752e
4
+ data.tar.gz: fb137625f9f5faa5b33dd5c7633a68eec7a4a2d2bb31db6f70758d6ed9ec3750
5
5
  SHA512:
6
- metadata.gz: 501e766d535a08b80597c7e5e9c5c40c075a74d1ddf347ad6d0858600c37fb945e39616a7d3b62d91a9486484a6a7d5edbd59fdfaca7e373fa099ecb8092edef
7
- data.tar.gz: cfd95059d66a9f5d83b60086b5b59d01840fc654e929546778d02457605d6bca1b22cf3f587a92db93575464281ef0f1cd7993a2082bd234ef91c124e84b5ad7
6
+ metadata.gz: fd7800ab65bd0e92ce5646210f08823d031fdd217f5230bc99207a0e00375b068ad2337805ddce196ca317fe6f0df60ad3e209fc885e31c765c68c125f83d900
7
+ data.tar.gz: f1df2bd938781a04c4af36c9653a7efffcc075cf72accd8f9fbaedbcc74286d860a1953bab159ec833c6a2cf5851a00633f555966b915fedec625af9ef2f85fa
data/README.md CHANGED
@@ -16,7 +16,7 @@ Please ⭐ if you like it.
16
16
 
17
17
  ## Install
18
18
 
19
- - Add to your Gemfile: `gem 'tiny_admin', '~> 0.2'`
19
+ - Add to your Gemfile: `gem 'tiny_admin', '~> 0.4'`
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
22
  - Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` (see [configuration](#configuration) below):
@@ -82,6 +82,12 @@ root:
82
82
  redirect: posts
83
83
  ```
84
84
 
85
+ `helper_class` (String): class or module with helper methods, used for attributes' formatters.
86
+
87
+ `page_not_found` (String): a view object to render when a missing page is requested.
88
+
89
+ `record_not_found` (String): a view object to render when a missing record is requested.
90
+
85
91
  `authentication` (Hash): define the authentication method, properties:
86
92
  - `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
87
93
  - `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
@@ -175,65 +181,64 @@ end
175
181
  ---
176
182
  authentication:
177
183
  plugin: TinyAdmin::Plugins::SimpleAuth
178
- # password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395df...' <= SHA512
179
- page_not_found: Admin::PageNotFound
180
- record_not_found: Admin::RecordNotFound
184
+ # password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
181
185
  root:
182
- title: 'Tiny Admin'
183
- page: Admin::PageRoot
184
- # redirect: posts
186
+ title: Test Admin
187
+ # page: RootPage
188
+ helper_class: AdminHelper
189
+ page_not_found: PageNotFound
190
+ record_not_found: RecordNotFound
185
191
  sections:
186
192
  - slug: google
187
193
  name: Google.it
188
194
  type: url
189
195
  url: https://www.google.it
190
196
  options:
191
- target: '_blank'
192
- - slug: stats
193
- name: Stats
197
+ target: _blank
198
+ - slug: sample
199
+ name: Sample page
194
200
  type: page
195
- page: Admin::Stats
201
+ page: SamplePage
196
202
  - slug: authors
197
203
  name: Authors
198
204
  type: resource
199
205
  model: Author
200
- repository: Admin::AuthorsRepo
201
206
  collection_actions:
202
- - latests: Admin::LatestAuthorsAction
207
+ - sample_col: SampleCollectionAction
203
208
  member_actions:
204
- - csv_export: Admin::CsvExportAuthorAction
205
- # only:
206
- # - index
207
- # options:
208
- # - hidden
209
+ - sample_mem: SampleMemberAction
209
210
  - slug: posts
210
211
  name: Posts
211
212
  type: resource
212
213
  model: Post
213
214
  index:
214
- sort:
215
- - author_id DESC
216
- pagination: 15
215
+ pagination: 5
217
216
  attributes:
218
217
  - id
219
218
  - title
220
219
  - field: author_id
221
220
  link_to: authors
222
- - state
221
+ - category: upcase
222
+ - state: downcase
223
223
  - published
224
- - dt
224
+ - position: round, 1
225
+ - dt: to_date
225
226
  - field: created_at
226
- converter: Admin::Utils
227
+ converter: AdminUtils
227
228
  method: datetime_formatter
229
+ - updated_at: strftime, %Y%m%d %H:%M
228
230
  filters:
229
231
  - title
230
- - field: state
232
+ - author_id
233
+ - field: category
231
234
  type: select
232
235
  values:
233
- - available
234
- - unavailable
235
- - arriving
236
+ - news
237
+ - sport
238
+ - tech
236
239
  - published
240
+ - dt
241
+ - created_at
237
242
  show:
238
243
  attributes:
239
244
  - id
@@ -243,7 +248,8 @@ sections:
243
248
  link_to: authors
244
249
  - category
245
250
  - published
246
- - state
251
+ - position: format, %f
252
+ - dt
247
253
  - created_at
248
254
  style_links:
249
255
  - href: /bootstrap.min.css
@@ -5,12 +5,21 @@ module TinyAdmin
5
5
  class BasicAction
6
6
  include Utils
7
7
 
8
- attr_reader :params, :path, :repository
9
-
10
- def initialize(repository, path:, params:)
11
- @repository = repository
12
- @path = path
13
- @params = params
8
+ def attribute_options(options)
9
+ options&.each_with_object({}) do |field, result|
10
+ field_data =
11
+ if field.is_a?(Hash)
12
+ if field.one?
13
+ field, method = field.first
14
+ { field.to_s => { field: field.to_s, method: method } }
15
+ else
16
+ { field[:field] => field }
17
+ end
18
+ else
19
+ { field => { field: field } }
20
+ end
21
+ result.merge!(field_data)
22
+ end
14
23
  end
15
24
  end
16
25
  end
@@ -3,30 +3,41 @@
3
3
  module TinyAdmin
4
4
  module Actions
5
5
  class Index < BasicAction
6
- attr_reader :current_page, :fields_options, :filters_list, :pagination, :query_string, :sort
6
+ attr_reader :current_page,
7
+ :fields_options,
8
+ :filters_list,
9
+ :pagination,
10
+ :pages,
11
+ :params,
12
+ :query_string,
13
+ :repository,
14
+ :sort
7
15
 
8
16
  def call(app:, context:, options:, actions:)
9
17
  evaluate_options(options)
10
18
  fields = repository.fields(options: fields_options)
11
19
  filters = prepare_filters(fields, filters_list)
12
20
  records, total_count = repository.list(page: current_page, limit: pagination, filters: filters, sort: sort)
13
- prepare_record = ->(record) { repository.index_record_attrs(record, fields: fields_options) }
14
- title = repository.index_title
15
- pages = (total_count / pagination.to_f).ceil
16
-
17
- prepare_page(Views::Actions::Index, context: context) do |page|
18
- page.setup_pagination(current_page: current_page, pages: pages > 1 ? pages : false)
19
- page.setup_records(records: records, fields: fields, prepare_record: prepare_record)
20
- page.update_attributes(actions: actions, filters: filters, query_string: query_string, title: title)
21
+
22
+ prepare_page(Views::Actions::Index) do |page|
23
+ setup_pagination(page, settings.components[:pagination], total_count: total_count)
24
+ page.update_attributes(
25
+ actions: actions,
26
+ fields: fields,
27
+ filters: filters,
28
+ prepare_record: ->(record) { repository.index_record_attrs(record, fields: fields_options) },
29
+ records: records,
30
+ title: repository.index_title
31
+ )
21
32
  end
22
33
  end
23
34
 
24
35
  private
25
36
 
26
37
  def evaluate_options(options)
27
- @fields_options = options[:attributes]&.each_with_object({}) do |field, result|
28
- result.merge!(field.is_a?(Hash) ? { field[:field] => field } : { field => { field: field } })
29
- end
38
+ @fields_options = attribute_options(options[:attributes])
39
+ @params = context.request.params
40
+ @repository = context.repository
30
41
  @filters_list = options[:filters]
31
42
  @pagination = options[:pagination] || 10
32
43
  @sort = options[:sort] || ['id']
@@ -39,10 +50,18 @@ module TinyAdmin
39
50
  filters = (filters_list || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
40
51
  filters = filters.each_with_object({}) { |filter, result| result[filter[:field]] = filter }
41
52
  values = (params['q'] || {})
42
- fields.each_with_object({}) do |field, result|
43
- result[field] = { value: values[field.name], filter: filters[field.name] } if filters.key?(field.name)
53
+ fields.each_with_object({}) do |(name, field), result|
54
+ result[field] = { value: values[name], filter: filters[name] } if filters.key?(name)
44
55
  end
45
56
  end
57
+
58
+ def setup_pagination(page, pagination_component_class, total_count:)
59
+ @pages = (total_count / pagination.to_f).ceil
60
+ return if pages <= 1 || !pagination_component_class
61
+
62
+ page.pagination_component = pagination_component_class.new
63
+ page.pagination_component.update(current: current_page, pages: pages, query_string: query_string)
64
+ end
46
65
  end
47
66
  end
48
67
  end
@@ -3,17 +3,21 @@
3
3
  module TinyAdmin
4
4
  module Actions
5
5
  class Show < BasicAction
6
+ attr_reader :repository
7
+
6
8
  def call(app:, context:, options:, actions:)
7
- fields_options = (options[:attributes] || []).each_with_object({}) do |field, result|
8
- result.merge!(field.is_a?(Hash) ? { field[:field] => field } : { field => { field: field } })
9
- end
9
+ @repository = context.repository
10
+ fields_options = attribute_options(options[:attributes])
10
11
  record = repository.find(context.reference)
11
- prepare_record = ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) }
12
- fields = repository.fields(options: fields_options)
13
12
 
14
- prepare_page(Views::Actions::Show, context: context) do |page|
15
- page.setup_record(record: record, fields: fields, prepare_record: prepare_record)
16
- page.update_attributes(actions: actions, title: repository.show_title(record))
13
+ prepare_page(Views::Actions::Show) do |page|
14
+ page.update_attributes(
15
+ actions: actions,
16
+ fields: repository.fields(options: fields_options),
17
+ prepare_record: ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) },
18
+ record: record,
19
+ title: repository.show_title(record)
20
+ )
17
21
  end
18
22
  rescue Plugins::BaseRepository::RecordNotFound => _e
19
23
  prepare_page(options[:record_not_found_page] || Views::Pages::RecordNotFound)
@@ -4,6 +4,6 @@ module TinyAdmin
4
4
  class Context
5
5
  include Singleton
6
6
 
7
- attr_accessor :reference, :slug
7
+ attr_accessor :reference, :repository, :request, :router, :settings, :slug
8
8
  end
9
9
  end
@@ -4,7 +4,7 @@ module TinyAdmin
4
4
  class Field
5
5
  attr_reader :name, :options, :title, :type
6
6
 
7
- def initialize(type:, name:, title:, options: {})
7
+ def initialize(name:, title:, type:, options: {})
8
8
  @type = type
9
9
  @name = name
10
10
  @title = title || name
@@ -12,8 +12,14 @@ module TinyAdmin
12
12
  end
13
13
 
14
14
  class << self
15
- def create_field(name:, title:, type: nil, options: {})
16
- new(type: type, name: name, title: title, options: options)
15
+ def create_field(name:, title: nil, type: nil, options: {})
16
+ 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
+ )
17
23
  end
18
24
  end
19
25
  end
@@ -4,17 +4,11 @@ module TinyAdmin
4
4
  module Plugins
5
5
  class ActiveRecordRepository < BaseRepository
6
6
  def index_record_attrs(record, fields: nil)
7
- return record.attributes.transform_values(&:to_s) if !fields || fields.empty?
7
+ return record.attributes.transform_values(&:to_s) unless fields
8
8
 
9
- record.attributes.slice(*fields.keys).each_with_object({}) do |(key, value), result|
10
- field_data = fields[key]
11
- result[key] =
12
- if field_data[:converter] && field_data[:method]
13
- converter = Object.const_get(field_data[:converter])
14
- converter.send(field_data[:method], value)
15
- else
16
- value&.to_s
17
- end
9
+ fields.to_h do |name, field|
10
+ value = record.send(name)
11
+ [name, translate_value(value, field)]
18
12
  end
19
13
  end
20
14
 
@@ -24,25 +18,19 @@ module TinyAdmin
24
18
  end
25
19
 
26
20
  def fields(options: nil)
27
- opts = options || {}
28
- columns = model.columns
29
- if !opts.empty?
30
- extra_fields = opts.keys - model.column_names
31
- raise "Some requested fields are not available: #{extra_fields.join(', ')}" if extra_fields.any?
32
-
33
- columns = opts.keys.map { |field| columns.find { _1.name == field } }
34
- end
35
- columns.map do |column|
36
- name = column.name
37
- type = opts.dig(column.name, :type) || column.type
38
- TinyAdmin::Field.create_field(name: name, title: name.humanize, type: type, options: opts[name])
21
+ if options
22
+ types = model.columns.to_h { [_1.name, _1.type] }
23
+ options.each_with_object({}) do |(name, field_options), result|
24
+ result[name] = TinyAdmin::Field.create_field(name: name, type: types[name], options: field_options)
25
+ end
26
+ else
27
+ model.columns.each_with_object({}) do |column, result|
28
+ result[column.name] = TinyAdmin::Field.create_field(name: column.name, type: column.type)
29
+ end
39
30
  end
40
31
  end
41
32
 
42
- def show_record_attrs(record, fields: nil)
43
- attrs = !fields || fields.empty? ? record.attributes : record.attributes.slice(*fields.keys)
44
- attrs.transform_values(&:to_s)
45
- end
33
+ alias show_record_attrs index_record_attrs
46
34
 
47
35
  def show_title(record)
48
36
  "#{model} ##{record.id}"
@@ -54,10 +42,10 @@ module TinyAdmin
54
42
  raise BaseRepository::RecordNotFound, e.message
55
43
  end
56
44
 
57
- def list(page: 1, limit: 10, filters: nil, sort: ['id'])
58
- page_offset = page.positive? ? (page - 1) * limit : 0
45
+ def list(page: 1, limit: 10, sort: ['id'], filters: nil)
59
46
  query = model.all.order(sort)
60
47
  query = apply_filters(query, filters) if filters
48
+ page_offset = page.positive? ? (page - 1) * limit : 0
61
49
  records = query.offset(page_offset).limit(limit).to_a
62
50
  [records, query.count]
63
51
  end
@@ -10,6 +10,20 @@ module TinyAdmin
10
10
  def initialize(model)
11
11
  @model = model
12
12
  end
13
+
14
+ def translate_value(value, field)
15
+ if field[:method]
16
+ method, *options = field[:method].split(',').map(&:strip)
17
+ if field[:converter]
18
+ converter = Object.const_get(field[:converter])
19
+ converter.send(method, value, options: options || [])
20
+ else
21
+ Settings.instance.helper_class.send(method, value, options: options || [])
22
+ end
23
+ else
24
+ value&.to_s
25
+ end
26
+ end
13
27
  end
14
28
  end
15
29
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module TinyAdmin
4
4
  class Router < BasicApp
5
- TinyAdmin::Settings.instance.load_settings
6
-
7
5
  route do |r|
6
+ context.settings = TinyAdmin::Settings.instance
7
+ context.settings.load_settings
8
+ context.router = r
9
+
8
10
  r.on 'auth' do
9
11
  context.slug = nil
10
12
  r.run Authentication
@@ -27,11 +29,11 @@ module TinyAdmin
27
29
  r.redirect settings.root_path
28
30
  end
29
31
 
30
- TinyAdmin::Settings.instance.pages.each do |slug, data|
32
+ context.settings.pages.each do |slug, data|
31
33
  setup_page_route(r, slug, data)
32
34
  end
33
35
 
34
- TinyAdmin::Settings.instance.resources.each do |slug, options|
36
+ context.settings.resources.each do |slug, options|
35
37
  setup_resource_routes(r, slug, options: options || {})
36
38
  end
37
39
 
@@ -54,14 +56,14 @@ module TinyAdmin
54
56
  else
55
57
  page = settings.root[:page]
56
58
  page_class = page.is_a?(String) ? Object.const_get(page) : page
57
- render_page prepare_page(page_class, context: context)
59
+ render_page prepare_page(page_class)
58
60
  end
59
61
  end
60
62
 
61
63
  def setup_page_route(router, slug, page_class)
62
64
  router.get slug do
63
65
  context.slug = slug
64
- render_page prepare_page(page_class, context: context)
66
+ render_page prepare_page(page_class)
65
67
  end
66
68
  end
67
69
 
@@ -74,14 +76,14 @@ module TinyAdmin
74
76
  end
75
77
 
76
78
  def setup_collection_routes(router, options:)
77
- repository = options[:repository].new(options[:model])
79
+ context.repository = options[:repository].new(options[:model])
78
80
  action_options = options[:index] || {}
79
81
 
80
82
  # Custom actions
81
83
  custom_actions = setup_custom_actions(
82
84
  router,
83
85
  options[:collection_actions],
84
- repository: repository,
86
+ repository: context.repository,
85
87
  options: action_options
86
88
  )
87
89
 
@@ -89,14 +91,15 @@ module TinyAdmin
89
91
  actions = options[:only]
90
92
  if !actions || actions.include?(:index) || actions.include?('index')
91
93
  router.is do
92
- index_action = TinyAdmin::Actions::Index.new(repository, path: request.path, params: request.params)
94
+ context.request = request
95
+ index_action = TinyAdmin::Actions::Index.new
93
96
  render_page index_action.call(app: self, context: context, options: action_options, actions: custom_actions)
94
97
  end
95
98
  end
96
99
  end
97
100
 
98
101
  def setup_member_routes(router, options:)
99
- repository = options[:repository].new(options[:model])
102
+ context.repository = options[:repository].new(options[:model])
100
103
  action_options = (options[:show] || {}).merge(record_not_found_page: settings.record_not_found)
101
104
 
102
105
  router.on String do |reference|
@@ -106,7 +109,7 @@ module TinyAdmin
106
109
  custom_actions = setup_custom_actions(
107
110
  router,
108
111
  options[:member_actions],
109
- repository: repository,
112
+ repository: context.repository,
110
113
  options: action_options
111
114
  )
112
115
 
@@ -114,7 +117,8 @@ module TinyAdmin
114
117
  actions = options[:only]
115
118
  if !actions || actions.include?(:show) || actions.include?('show')
116
119
  router.is do
117
- show_action = TinyAdmin::Actions::Show.new(repository, path: request.path, params: request.params)
120
+ context.request = request
121
+ show_action = TinyAdmin::Actions::Show.new
118
122
  render_page show_action.call(app: self, context: context, options: action_options, actions: custom_actions)
119
123
  end
120
124
  end
@@ -122,16 +126,18 @@ module TinyAdmin
122
126
  end
123
127
 
124
128
  def setup_custom_actions(router, custom_actions, repository:, options:)
125
- (custom_actions || []).map do |custom_action|
129
+ context.repository = repository
130
+ (custom_actions || []).each_with_object({}) do |custom_action, result|
126
131
  action_slug, action = custom_action.first
127
132
  action_class = action.is_a?(String) ? Object.const_get(action) : action
128
133
 
129
134
  router.get action_slug.to_s do
130
- custom_action = action_class.new(repository, path: request.path, params: request.params)
135
+ context.request = request
136
+ custom_action = action_class.new
131
137
  render_page custom_action.call(app: self, context: context, options: options)
132
138
  end
133
139
 
134
- action_slug.to_s
140
+ result[action_slug.to_s] = action_class
135
141
  end
136
142
  end
137
143
  end
@@ -5,9 +5,26 @@ module TinyAdmin
5
5
  include Singleton
6
6
  include Utils
7
7
 
8
+ DEFAULTS = {
9
+ %i[authentication plugin] => Plugins::NoAuth,
10
+ %i[authentication login] => Views::Pages::SimpleAuthLogin,
11
+ %i[components flash] => Views::Components::Flash,
12
+ %i[components head] => Views::Components::Head,
13
+ %i[components navbar] => Views::Components::Navbar,
14
+ %i[components pagination] => Views::Components::Pagination,
15
+ %i[helper_class] => Support,
16
+ %i[page_not_found] => Views::Pages::PageNotFound,
17
+ %i[record_not_found] => Views::Pages::RecordNotFound,
18
+ %i[repository] => Plugins::ActiveRecordRepository,
19
+ %i[root_path] => '/admin',
20
+ %i[root page] => Views::Pages::Root,
21
+ %i[root title] => 'TinyAdmin'
22
+ }.freeze
23
+
8
24
  attr_accessor :authentication,
9
25
  :components,
10
26
  :extra_styles,
27
+ :helper_class,
11
28
  :navbar,
12
29
  :page_not_found,
13
30
  :record_not_found,
@@ -20,38 +37,33 @@ module TinyAdmin
20
37
 
21
38
  attr_reader :pages, :resources
22
39
 
23
- def load_settings
24
- @authentication ||= {}
25
- @authentication[:plugin] ||= Plugins::NoAuth
26
- @authentication[:login] ||= Views::Pages::SimpleAuthLogin
27
- @authentication[:plugin] = Object.const_get(authentication[:plugin]) if authentication[:plugin].is_a?(String)
40
+ def [](key)
41
+ send(key)
42
+ end
43
+
44
+ def []=(key, value)
45
+ send("#{key}=", value)
46
+ convert_value(key, value)
47
+ end
28
48
 
29
- @page_not_found ||= Views::Pages::PageNotFound
30
- @page_not_found = Object.const_get(@page_not_found) if @page_not_found.is_a?(String)
31
- @record_not_found ||= Views::Pages::RecordNotFound
32
- @record_not_found = Object.const_get(@record_not_found) if @record_not_found.is_a?(String)
49
+ def load_settings
50
+ # default values
51
+ DEFAULTS.each do |(option, param), default|
52
+ if param
53
+ self[option] ||= {}
54
+ self[option][param] ||= default
55
+ else
56
+ self[option] ||= default
57
+ end
58
+ end
33
59
 
34
60
  @pages ||= {}
35
- @repository ||= Plugins::ActiveRecordRepository
36
61
  @resources ||= {}
37
- @root_path ||= '/admin'
38
- @root_path = '/' if @root_path == ''
39
62
  @sections ||= []
40
-
41
- @root ||= {}
42
- @root[:title] ||= 'TinyAdmin'
43
- @root[:page] ||= Views::Pages::Root
44
-
63
+ @root_path = '/' if @root_path == ''
45
64
  if @authentication[:plugin] == Plugins::SimpleAuth
46
65
  @authentication[:logout] ||= ['logout', "#{root_path}/auth/logout"]
47
66
  end
48
-
49
- @components ||= {}
50
- @components[:flash] ||= Views::Components::Flash
51
- @components[:head] ||= Views::Components::Head
52
- @components[:navbar] ||= Views::Components::Navbar
53
- @components[:pagination] ||= Views::Components::Pagination
54
-
55
67
  @navbar = prepare_navbar(sections, logout: authentication[:logout])
56
68
  end
57
69
 
@@ -79,5 +91,20 @@ module TinyAdmin
79
91
  items['auth/logout'] = logout if logout
80
92
  items
81
93
  end
94
+
95
+ private
96
+
97
+ def convert_value(key, value)
98
+ if value.is_a?(Hash)
99
+ value.each_key do |key2|
100
+ path = [key, key2]
101
+ if DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)
102
+ self[key][key2] = Object.const_get(self[key][key2])
103
+ end
104
+ end
105
+ elsif value.is_a?(String) && (DEFAULTS[[key]].is_a?(Class) || DEFAULTS[[key]].is_a?(Module))
106
+ self[key] = Object.const_get(self[key])
107
+ end
108
+ end
82
109
  end
83
110
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ class Support
5
+ class << self
6
+ def call(value, options: [])
7
+ value && options&.any? ? options.inject(value) { |result, message| result&.send(message) } : value
8
+ end
9
+
10
+ def downcase(value, options: [])
11
+ value&.downcase
12
+ end
13
+
14
+ def format(value, options: [])
15
+ value && options&.any? ? Kernel.format(options.first, value) : value
16
+ end
17
+
18
+ def round(value, options: [])
19
+ value&.round(options&.first&.to_i || 2)
20
+ end
21
+
22
+ def strftime(value, options: [])
23
+ value ? value.strftime(options&.first || '%Y-%m-%d %H:%M') : ''
24
+ end
25
+
26
+ def to_date(value, options: [])
27
+ value ? value.to_date.to_s : value
28
+ end
29
+
30
+ def upcase(value, options: [])
31
+ value&.upcase
32
+ end
33
+ end
34
+ end
35
+ end
@@ -13,17 +13,26 @@ module TinyAdmin
13
13
  list.join('&')
14
14
  end
15
15
 
16
- def prepare_page(page_class, context: nil, options: nil)
16
+ def prepare_page(page_class, options: nil)
17
17
  page_class.new.tap do |page|
18
- page.context = context
19
18
  page.options = options
19
+ page.head_component = settings.components[:head]&.new
20
+ page.flash_component = settings.components[:flash]&.new
21
+ page.navbar_component = settings.components[:navbar]&.new(
22
+ current_slug: context&.slug,
23
+ root_path: settings.root_path,
24
+ root_title: settings.root[:title],
25
+ items: options&.include?(:no_menu) ? [] : settings.navbar
26
+ )
27
+
20
28
  yield(page) if block_given?
21
29
  end
22
30
  end
23
31
 
24
- def route_for(section, reference: nil, action: nil)
32
+ def route_for(section, reference: nil, action: nil, query: nil)
25
33
  root_path = settings.root_path == '/' ? nil : settings.root_path
26
34
  route = [root_path, section, reference, action].compact.join("/")
35
+ route << "?#{query}" if query
27
36
  route[0] == '/' ? route : route.prepend('/')
28
37
  end
29
38
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- VERSION = '0.2.1'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -4,23 +4,9 @@ module TinyAdmin
4
4
  module Views
5
5
  module Actions
6
6
  class Index < DefaultLayout
7
- attr_reader :current_page, :fields, :pages, :prepare_record, :records
8
- attr_accessor :actions, :filters, :query_string
9
-
10
- def setup_pagination(current_page:, pages:)
11
- @current_page = current_page
12
- @pages = pages
13
- end
14
-
15
- def setup_records(records:, fields:, prepare_record:)
16
- @records = records
17
- @fields = fields.each_with_object({}) { |field, result| result[field.name] = field }
18
- @prepare_record = prepare_record
19
- end
7
+ attr_accessor :actions, :fields, :filters, :pagination_component, :prepare_record, :records
20
8
 
21
9
  def template
22
- @filters ||= {}
23
-
24
10
  super do
25
11
  div(class: 'index') {
26
12
  div(class: 'row') {
@@ -28,19 +14,12 @@ module TinyAdmin
28
14
  h1(class: 'title') { title }
29
15
  }
30
16
  div(class: 'col-8') {
31
- ul(class: 'nav justify-content-end') {
32
- (actions || []).each do |action|
33
- li(class: 'nav-item') {
34
- href = route_for(context.slug, action: action)
35
- a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
36
- }
37
- end
38
- }
17
+ actions_buttons
39
18
  }
40
19
  }
41
20
 
42
21
  div(class: 'row') {
43
- div_class = filters.any? ? 'col-9' : 'col-12'
22
+ div_class = filters&.any? ? 'col-9' : 'col-12'
44
23
  div(class: div_class) {
45
24
  table(class: 'table') {
46
25
  table_header if fields.any?
@@ -49,7 +28,7 @@ module TinyAdmin
49
28
  }
50
29
  }
51
30
 
52
- if filters.any?
31
+ if filters&.any?
53
32
  div(class: 'col-3') {
54
33
  filters_form_attrs = { section_path: route_for(context.slug), filters: filters }
55
34
  render TinyAdmin::Views::Components::FiltersForm.new(**filters_form_attrs)
@@ -57,9 +36,7 @@ module TinyAdmin
57
36
  end
58
37
  }
59
38
 
60
- if pages
61
- render components[:pagination].new(current: current_page, pages: pages, query_string: query_string)
62
- end
39
+ render pagination_component if pagination_component
63
40
  }
64
41
  end
65
42
  end
@@ -70,7 +47,9 @@ module TinyAdmin
70
47
  thead {
71
48
  tr {
72
49
  fields.each_value do |field|
73
- td(class: "field-header-#{field.name} field-header-type-#{field.type}") { field.title }
50
+ td(class: "field-header-#{field.name} field-header-type-#{field.type}") {
51
+ field.options[:header] || field.title
52
+ }
74
53
  end
75
54
  td { whitespace }
76
55
  }
@@ -86,8 +65,9 @@ module TinyAdmin
86
65
  field = fields[key]
87
66
  td(class: "field-value-#{field.name} field-value-type-#{field.type}") {
88
67
  if field.options && field.options[:link_to]
89
- reference = record.send(field.options[:field])
90
- a(href: route_for(field.options[:link_to], reference: reference)) { value }
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 }
91
71
  else
92
72
  value
93
73
  end
@@ -100,6 +80,18 @@ module TinyAdmin
100
80
  end
101
81
  }
102
82
  end
83
+
84
+ def actions_buttons
85
+ ul(class: 'nav justify-content-end') {
86
+ (actions || {}).each do |action, action_class|
87
+ li(class: 'nav-item mx-1') {
88
+ 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 }
91
+ }
92
+ end
93
+ }
94
+ end
103
95
  end
104
96
  end
105
97
  end
@@ -4,14 +4,7 @@ module TinyAdmin
4
4
  module Views
5
5
  module Actions
6
6
  class Show < DefaultLayout
7
- attr_reader :fields, :prepare_record, :record
8
- attr_accessor :actions
9
-
10
- def setup_record(record:, fields:, prepare_record:)
11
- @record = record
12
- @fields = fields
13
- @prepare_record = prepare_record
14
- end
7
+ attr_accessor :actions, :fields, :prepare_record, :record
15
8
 
16
9
  def template
17
10
  super do
@@ -21,27 +14,21 @@ module TinyAdmin
21
14
  h1(class: 'title') { title }
22
15
  }
23
16
  div(class: 'col-8') {
24
- ul(class: 'nav justify-content-end') {
25
- (actions || []).each do |action|
26
- li(class: 'nav-item') {
27
- href = route_for(context.slug, reference: context.reference, action: action)
28
- a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
29
- }
30
- end
31
- }
17
+ actions_buttons
32
18
  }
33
19
  }
34
20
 
35
- prepare_record.call(record).each_with_index do |(_key, value), index|
36
- field = fields[index]
21
+ prepare_record.call(record).each do |key, value|
22
+ field = fields[key]
37
23
  div(class: "field-#{field.name} row lh-lg") {
38
24
  if field
39
- div(class: 'field-header col-2') { field.title }
25
+ div(class: 'field-header col-2') { field.options[:header] || field.title }
40
26
  end
41
27
  div(class: 'field-value col-10') {
42
28
  if field.options && field.options[:link_to]
43
- reference = record.send(field.options[:field])
44
- a(href: route_for(field.options[:link_to], reference: reference)) { value }
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 }
45
32
  else
46
33
  value
47
34
  end
@@ -51,6 +38,20 @@ module TinyAdmin
51
38
  }
52
39
  end
53
40
  end
41
+
42
+ private
43
+
44
+ def actions_buttons
45
+ ul(class: 'nav justify-content-end') {
46
+ (actions || {}).each do |action, action_class|
47
+ li(class: 'nav-item mx-1') {
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 }
51
+ }
52
+ end
53
+ }
54
+ end
54
55
  end
55
56
  end
56
57
  end
@@ -5,10 +5,6 @@ module TinyAdmin
5
5
  class BasicLayout < Phlex::HTML
6
6
  include Utils
7
7
 
8
- def components
9
- settings.components
10
- end
11
-
12
8
  def update_attributes(attributes)
13
9
  attributes.each do |key, value|
14
10
  send("#{key}=", value)
@@ -6,14 +6,6 @@ module TinyAdmin
6
6
  class Flash < Phlex::HTML
7
7
  attr_reader :errors, :notices, :warnings
8
8
 
9
- def initialize(messages:)
10
- return unless messages
11
-
12
- @notices = messages[:notices]
13
- @warnings = messages[:warnings]
14
- @errors = messages[:errors]
15
- end
16
-
17
9
  def template
18
10
  div(class: 'flash') {
19
11
  div(class: 'notices alert alert-success', role: 'alert') { notices.join(', ') } if notices&.any?
@@ -21,6 +13,14 @@ module TinyAdmin
21
13
  div(class: 'notices alert alert-danger', role: 'alert') { errors.join(', ') } if errors&.any?
22
14
  }
23
15
  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
24
  end
25
25
  end
26
26
  end
@@ -6,12 +6,6 @@ module TinyAdmin
6
6
  class Head < Phlex::HTML
7
7
  attr_reader :extra_styles, :page_title, :style_links
8
8
 
9
- def initialize(page_title, style_links: [], extra_styles: nil)
10
- @page_title = page_title
11
- @style_links = style_links
12
- @extra_styles = extra_styles
13
- end
14
-
15
9
  def template
16
10
  head {
17
11
  meta charset: 'utf-8'
@@ -25,6 +19,12 @@ module TinyAdmin
25
19
  style { extra_styles } if extra_styles
26
20
  }
27
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
28
  end
29
29
  end
30
30
  end
@@ -6,27 +6,48 @@ module TinyAdmin
6
6
  class Pagination < Phlex::HTML
7
7
  attr_reader :current, :pages, :query_string
8
8
 
9
- def initialize(current:, pages:, query_string:)
10
- @current = current
11
- @pages = pages
12
- @query_string = query_string
13
- end
14
-
15
9
  def template
16
10
  div(class: 'pagination-div') {
17
11
  nav('aria-label' => 'Pagination') {
18
12
  ul(class: 'pagination justify-content-center') {
19
- 1.upto(pages) do |i|
20
- li_class = (i == current ? 'page-item active' : 'page-item')
21
- li(class: li_class) {
22
- href = query_string.empty? ? "?p=#{i}" : "?#{query_string}&p=#{i}"
23
- a(class: 'page-link', href: href) { i }
24
- }
13
+ if pages <= 10
14
+ pages_range(1..pages)
15
+ elsif current <= 4 || current >= pages - 3
16
+ pages_range(1..(current <= 4 ? current + 2 : 4), with_dots: true)
17
+ pages_range((current > pages - 4 ? current - 2 : pages - 2)..pages)
18
+ else
19
+ pages_range(1..1, with_dots: true)
20
+ pages_range(current - 2..current + 2, with_dots: true)
21
+ pages_range(pages..pages)
25
22
  end
26
23
  }
27
24
  }
28
25
  }
29
26
  end
27
+
28
+ def update(current:, pages:, query_string:)
29
+ @current = current
30
+ @pages = pages
31
+ @query_string = query_string
32
+ end
33
+
34
+ private
35
+
36
+ def pages_range(range, with_dots: false)
37
+ range.each do |page|
38
+ li(class: page == current ? 'page-item active' : 'page-item') {
39
+ href = query_string.empty? ? "?p=#{page}" : "?#{query_string}&p=#{page}"
40
+ a(class: 'page-link', href: href) { page }
41
+ }
42
+ end
43
+ dots if with_dots
44
+ end
45
+
46
+ def dots
47
+ li(class: 'page-item disabled') {
48
+ a(class: 'page-link') { '...' }
49
+ }
50
+ end
30
51
  end
31
52
  end
32
53
  end
@@ -3,24 +3,22 @@
3
3
  module TinyAdmin
4
4
  module Views
5
5
  class DefaultLayout < BasicLayout
6
- attr_accessor :context, :messages, :options, :title
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)
11
+
9
12
  doctype
10
13
  html {
11
- render components[:head].new(title, style_links: style_links, extra_styles: settings.extra_styles)
14
+ render head_component if head_component
12
15
 
13
16
  body(class: body_class) {
14
- navbar_attrs = {
15
- current_slug: context&.slug,
16
- root_path: settings.root_path,
17
- root_title: settings.root[:title],
18
- items: navbar_items
19
- }
20
- render components[:navbar].new(**navbar_attrs)
17
+ render navbar_component if navbar_component
21
18
 
22
19
  main_content {
23
- render components[:flash].new(messages: messages || {})
20
+ render flash_component if flash_component
21
+
24
22
  yield_content(&block)
25
23
  }
26
24
 
@@ -35,10 +33,6 @@ module TinyAdmin
35
33
  "module-#{self.class.to_s.split('::').last.downcase}"
36
34
  end
37
35
 
38
- def navbar_items
39
- options&.include?(:no_menu) ? [] : settings.navbar
40
- end
41
-
42
36
  def main_content
43
37
  div(class: 'container main-content py-4') do
44
38
  if options&.include?(:compact_layout)
data/lib/tiny_admin.rb CHANGED
@@ -18,7 +18,7 @@ module TinyAdmin
18
18
  def configure_from_file(file)
19
19
  config = YAML.load_file(file, symbolize_names: true)
20
20
  config.each do |key, value|
21
- TinyAdmin::Settings.instance.send("#{key}=", value)
21
+ TinyAdmin::Settings.instance[key] = value
22
22
  end
23
23
  end
24
24
 
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.2.1
4
+ version: 0.4.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-11 00:00:00.000000000 Z
11
+ date: 2023-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -88,6 +88,7 @@ files:
88
88
  - lib/tiny_admin/plugins/simple_auth.rb
89
89
  - lib/tiny_admin/router.rb
90
90
  - lib/tiny_admin/settings.rb
91
+ - lib/tiny_admin/support.rb
91
92
  - lib/tiny_admin/utils.rb
92
93
  - lib/tiny_admin/version.rb
93
94
  - lib/tiny_admin/views/actions/index.rb