tiny_admin 0.2.1 → 0.4.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: 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