oversee 0.3.0 → 0.3.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/app/components/oversee/dashboard/actions.rb +58 -0
  4. data/app/components/oversee/dashboard/filter.rb +12 -0
  5. data/app/components/oversee/dashboard/filters.rb +5 -55
  6. data/app/components/oversee/field/display.rb +6 -24
  7. data/app/components/oversee/field/form.rb +4 -17
  8. data/app/components/oversee/field/input/belongs_to.rb +1 -18
  9. data/app/components/oversee/field/input/boolean.rb +2 -7
  10. data/app/components/oversee/field/input/date.rb +5 -0
  11. data/app/components/oversee/field/input/datetime.rb +1 -6
  12. data/app/components/oversee/field/input/integer.rb +1 -6
  13. data/app/components/oversee/field/input/json.rb +5 -0
  14. data/app/components/oversee/field/input/rich_text.rb +0 -8
  15. data/app/components/oversee/field/input/string.rb +1 -6
  16. data/app/components/oversee/field/input.rb +5 -11
  17. data/app/components/oversee/field/label.rb +16 -16
  18. data/app/components/oversee/field/set.rb +36 -0
  19. data/app/components/oversee/field/value/belongs_to.rb +4 -10
  20. data/app/components/oversee/field/value/boolean.rb +2 -47
  21. data/app/components/oversee/field/value/date.rb +5 -0
  22. data/app/components/oversee/field/value/datetime.rb +2 -6
  23. data/app/components/oversee/field/value/enum.rb +2 -6
  24. data/app/components/oversee/field/value/integer.rb +2 -6
  25. data/app/components/oversee/field/value/json.rb +6 -0
  26. data/app/components/oversee/field/value/rich_text.rb +2 -6
  27. data/app/components/oversee/field/value/string.rb +3 -10
  28. data/app/components/oversee/field/value/text.rb +2 -6
  29. data/app/components/oversee/field/value.rb +3 -0
  30. data/app/components/oversee/field.rb +42 -0
  31. data/app/components/oversee/flash.rb +39 -0
  32. data/app/components/oversee/layout/application.rb +1 -1
  33. data/app/components/oversee/resources/index.rb +1 -1
  34. data/app/components/oversee/resources/show.rb +7 -17
  35. data/app/components/oversee/resources/table.rb +1 -1
  36. data/app/controllers/oversee/resources/fields_controller.rb +19 -15
  37. data/app/controllers/oversee/resources_controller.rb +7 -2
  38. data/app/javascript/oversee/controllers/clipboard_controller.js +21 -0
  39. data/app/javascript/oversee/controllers/index.js +1 -0
  40. data/app/javascript/oversee/controllers/notification_controller.js +17 -0
  41. data/app/javascript/oversee/controllers/reveal_controller.js +13 -1
  42. data/app/models/oversee/resource.rb +10 -7
  43. data/lib/oversee/version.rb +1 -1
  44. data/lib/oversee.rb +1 -0
  45. metadata +42 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c31a8c277449dfaec238f66cfc8e404a64c51dff4beef1a944fee85c5935991c
4
- data.tar.gz: f3fcd62d4ff3184f5b9f97273c2269e986b5f03736a9961440aca242b38a1292
3
+ metadata.gz: 82a5bb3d0823ed6874fe8b7bb09934ed3b72b7b9a2964da6c6d8cdd7b82ac28a
4
+ data.tar.gz: 5ddf616584c7319a2ef611227fb4f5e0cf876c1dc20b1228c47330411715054c
5
5
  SHA512:
6
- metadata.gz: ad42840ab6b72667e5e16853cea4d9028fc1356fd4ff2ec7d3909b6102c2fc80dca9287245ad4ab62021181c238beae371f77b473f1f9464bf0b1162a29946c5
7
- data.tar.gz: 229f928f44cfe6d6535cd8ef6682472a9f5ecf59e29bc65714ed71e1c6147ab48b582e5c065fc1e9233d5e536155e16d8750a1fc92f3e74bd97e3720cbc58e42
6
+ metadata.gz: 16cf0583966c0f07deae2be76299544a4fb5678efc5345a292acc0ad628d263a979b44e2d60f8749cc87bbfd5471eb85cfe5619fdc58af5bedb8b9e5a0efe755
7
+ data.tar.gz: f0d8ae31255ca03aecb010393356d55a9ac63b99699b50b8c9b6bae8d6c11c8f1d3b876dcd6fe9c9118e19c6015989f45f3e7ed74eadb259e885007eb215e764
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
-
2
1
  # Oversee
3
2
 
4
- Plug & play admin dashboard for Rails applications.
3
+ Plug & play content management system (CMS) for Rails applications. Some may call it an admin dashboard too.
5
4
 
5
+ <a href="https://rubygems.org/gems/oversee">
6
+ <img alt="Oversee GEM Version" src="https://img.shields.io/gem/v/oversee?color=10b981&include_prereleases&logo=ruby&logoColor=f43f5e">
7
+ </a>
6
8
 
7
- [![Gem Version](https://badge.fury.io/rb/oversee.svg)](https://badge.fury.io/rb/oversee)
9
+ <a href="https://rubygems.org/gems/oversee">
10
+ <img alt="Oversee GEM Version" src="https://img.shields.io/gem/dt/oversee?color=10b981&include_prereleases&logo=ruby&logoColor=f43f5e">
11
+ </a>
8
12
 
9
13
  ---
10
14
 
@@ -35,6 +39,7 @@ $ bundle add oversee
35
39
 
36
40
  > [!TIP]
37
41
  > Currently, we don't release new gem versions too often due to the fragile nature of the gem. However, you can use the edge version of the gem by pointing directly to the git repository.
42
+ >
38
43
  > ```ruby
39
44
  > gem "oversee", git: "https://github.com/primevise/oversee", branch: :main
40
45
  > ```
@@ -0,0 +1,58 @@
1
+ class Oversee::Dashboard::Actions < Oversee::Base
2
+ def initialize(params: nil)
3
+ @params = params
4
+ end
5
+
6
+ def view_template(&)
7
+ div(class: "flex items-center justify-between") do
8
+ div(class: "flex items-center gap-2") do
9
+ if show_action_section?
10
+ button(
11
+ class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200",
12
+ data: { controller: "reveal", action: "reveal#toggle", reveal_revealable_id_value: "oversee-filters" }
13
+ ) do
14
+ render Phlex::Icons::Iconoir::FilterAlt.new(class: "size-3")
15
+ plain "Filters"
16
+ end
17
+ end
18
+ if false
19
+ a(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
20
+ render Phlex::Icons::Iconoir::XMark.new(class: "size-4 text-gray-500")
21
+ plain "Clear sorting"
22
+ end
23
+ end
24
+ end
25
+ div(class: "flex items-center gap-4") do
26
+ form(action: "", class: "flex items-center gap-2") do
27
+ input(
28
+ type: :search,
29
+ name: :query,
30
+ value: @params[:query],
31
+ placeholder: search_placeholder,
32
+ class: "flex bg-gray-100 min-w-64 w-64 focus:w-96 transition-all h-10 items-center pl-4 py-2 placeholder:text-gray-500 rounded-sm text-sm"
33
+ )
34
+ button(class: "size-10 inline-flex items-center justify-center bg-gray-100 hover:bg-gray-200 transition-colors") { render Phlex::Icons::Iconoir::Search.new(class: "size-4 text-gray-600") }
35
+ end
36
+ end
37
+ end
38
+ render Oversee::Dashboard::Filters.new if show_action_section?
39
+ end
40
+
41
+ private
42
+
43
+ def show_action_section?
44
+ Rails.env.development? || @params[:experimental] == "true"
45
+ end
46
+
47
+ def sortless_path
48
+ sortless_query_params = @params.except(:sort_attribute, :sort_direction, :controller, :action)
49
+ # helpers.resources_path(sortless_query_params)
50
+ end
51
+
52
+ def search_placeholder
53
+ context = Oversee::Search.new(collection: nil, resource_class: @params[:resource_class_name].constantize)
54
+ attr = context.default_searchable_attribute
55
+
56
+ "Search by #{attr}"
57
+ end
58
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ class Oversee::Dashboard::Filter < Oversee::Base
3
+ def view_template
4
+ div(class: "flex items-center gap-2") do
5
+ render Phlex::Icons::Iconoir::LongArrowDownRight.new(class: "size-4 text-gray-500")
6
+ form(action: "", class: "flex items-center gap-2") do
7
+ input(type: :text, placeholder: "Title [EQ]", name: "filters[title][eq][]", class: "bg-gray-100 px-4 py-2 text-xs")
8
+ button(type: :submit, class: "button", class: "bg-gray-900 text-white px-4 py-2 text-xs font-medium") { plain "Apply" }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,58 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  class Oversee::Dashboard::Filters < Oversee::Base
2
- def initialize(params: nil)
3
-
4
- @params = params
5
-
6
- puts "params: #{params}"
7
- puts "sortless_path: #{sortless_path}"
8
- end
9
-
10
- def view_template(&)
11
- div(class: "flex items-center justify-between") do
12
- div(class: "flex items-center gap-2") do
13
- if show_action_section?
14
- button(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
15
- render Phlex::Icons::Iconoir::FilterAlt.new(class: "size-3")
16
- plain "Filters"
17
- end
18
- end
19
- if false
20
- a(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
21
- render Phlex::Icons::Iconoir::XMark.new(class: "size-4 text-gray-500")
22
- plain "Clear sorting"
23
- end
24
- end
25
- end
26
- div(class: "flex items-center gap-4") do
27
- form(action: "", class: "flex items-center gap-2") do
28
- input(
29
- type: :search,
30
- name: :query,
31
- value: @params[:query],
32
- placeholder: search_placeholder,
33
- class: "flex bg-gray-100 min-w-64 w-64 focus:w-96 transition-all h-10 items-center pl-4 py-2 placeholder:text-gray-500 rounded-sm text-sm"
34
- )
35
- button(class: "size-10 inline-flex items-center justify-center bg-gray-100 hover:bg-gray-200 transition-colors") { render Phlex::Icons::Iconoir::Search.new(class: "size-4 text-gray-600") }
36
- end
37
- end
3
+ def view_template
4
+ div(id: :oversee_filters, class: "hidden pt-4 mt-4 border-t flex flex-col gap-2") do
5
+ render Oversee::Dashboard::Filter.new
38
6
  end
39
7
  end
40
-
41
- private
42
-
43
- def show_action_section?
44
- Rails.env.development? || @params[:experimental] == "true"
45
- end
46
-
47
- def sortless_path
48
- sortless_query_params = @params.except(:sort_attribute, :sort_direction, :controller, :action)
49
- # helpers.resources_path(sortless_query_params)
50
- end
51
-
52
- def search_placeholder
53
- context = Oversee::Search.new(collection: nil, resource_class: @params[:resource_class_name].constantize)
54
- attr = context.default_searchable_attribute
55
-
56
- "Search by #{attr}"
57
- end
58
- end
8
+ end
@@ -1,38 +1,20 @@
1
- class Oversee::Field::Display < Oversee::Base
2
-
3
- attr_reader :resource
4
- attr_reader :key
5
- attr_reader :value
6
- attr_reader :datatype
7
-
8
- def initialize(resource:, key: nil, value: nil, datatype: :string, **options)
9
- @resource = resource
10
- @key = key
11
- @value = value
12
- @datatype = datatype
13
- @options = options
14
- end
15
-
1
+ class Oversee::Field::Display < Oversee::Field
16
2
  def view_template
17
3
  html_tag(
18
4
  id: dom_id(resource, key),
19
5
  href: helpers.resource_input_path(resource_class_name:, key:, datatype:),
20
- class: "bg-gray-100 h-10 flex items-center px-4 py-2 hover:bg-gray-200 transition-colors w-full cursor-pointer",
6
+ title: key.to_s.titleize,
7
+ class: "bg-gray-100 h-10 flex items-center justify-between px-4 py-2 hover:bg-gray-200 transition-colors w-full cursor-pointer group",
21
8
  data: { turbo_stream: true }
22
9
  ) do
23
10
  render Oversee::Field::Value.new(key:, value:, datatype:, **@options)
11
+ render Phlex::Icons::Iconoir::Edit.new(class: "text-gray-500 size-4 opacity-0 group-hover:opacity-100 transition-opacity", stroke_width: 1.75)
24
12
  end
25
13
  end
26
14
 
27
15
  private
28
16
 
29
- def resource_class_name
30
- @resource_class_name ||= @resource.class.name
31
- end
32
-
33
- def html_tag(...)
34
- edittable? ? a(...) : div(...)
35
- end
36
-
37
17
  def edittable? = @options[:edittable] || true
18
+
19
+ def html_tag(...) = edittable? ? a(...) : div(...)
38
20
  end
@@ -1,30 +1,17 @@
1
- class Oversee::Field::Form < Oversee::Base
1
+ class Oversee::Field::Form < Oversee::Field
2
2
  include Phlex::Rails::Helpers::FormWith
3
3
 
4
- def initialize(resource:, key: nil, value: nil, datatype: :string, method: :patch, **options)
5
- @resource = resource
6
- @key = key
7
- @value = value
8
- @datatype = datatype
9
- @method = method
10
- @options = options
11
- end
12
-
13
4
  def view_template
14
- form_with(id: dom_id(@resource, @key), model: @resource, url: helpers.update_resource_path(resource_class_name: resource_class_name, id: @resource.id), method: @method, class: "flex items-center w-full gap-4") do |form|
5
+ form_with(id: field_form_id, model: resource, url: helpers.update_resource_path(resource_class_name: resource_class_name, id: @resource.id), method: @method, class: "flex items-center w-full gap-4") do |form|
15
6
  input type: :hidden, id: "oversee_key", name: "oversee_key", value: @key.to_s
16
7
  input type: :hidden, id: "oversee_datatype", name: "oversee_datatype", value: @datatype
17
8
  render Oversee::Field::Input.new(key: @key, value: @value, datatype: @datatype, **@options)
18
- button(class:"h-9 bg-gray-900 hover:bg-gray-700 text-white inline-flex items-center cursor-pointer gap-2 px-4 rounded-full text-xs font-medium") {
19
- render Phlex::Icons::Iconoir::Check.new(class: "size-4")
20
- plain "Save"
21
- }
22
9
  end
23
10
  end
24
11
 
25
12
  private
26
13
 
27
- def resource_class_name
28
- @resource.class.name
14
+ def method
15
+ @method ||= @options[:method] || :patch
29
16
  end
30
17
  end
@@ -1,22 +1,5 @@
1
1
  class Oversee::Field::Input::BelongsTo < Oversee::Field::Input
2
- def initialize(key:, value:, **options)
3
- @key = key
4
- @value = value
5
- end
6
-
7
2
  def view_template
8
- input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
9
-
10
- # select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
11
-
12
- # option(value: 1, selected: @value) { "True" }
13
- # option(value: 0, selected: !@value) { "False" }
14
- # end
15
- end
16
-
17
- private
18
-
19
- def select_values
20
-
3
+ input(type: "text", id: field_id, name: field_name, value:, class: "flex border px-4 py-2 text-sm w-full rounded-sm")
21
4
  end
22
5
  end
@@ -1,13 +1,8 @@
1
1
  class Oversee::Field::Input::Boolean < Oversee::Field::Input
2
- def initialize(key:, value:)
3
- @key = key
4
- @value = value
5
- end
6
-
7
2
  def view_template
8
3
  select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
9
- option(value: 1, selected: @value) { "True" }
10
- option(value: 0, selected: !@value) { "False" }
4
+ option(value: 1, selected: value) { "True" }
5
+ option(value: 0, selected: !value) { "False" }
11
6
  end
12
7
  end
13
8
  end
@@ -0,0 +1,5 @@
1
+ class Oversee::Field::Input::Date < Oversee::Field::Input
2
+ def view_template
3
+ input(type: "datetime-local", id: field_id, name: field_name, value: value&.strftime("%Y-%m-%dT%T"), class: "flex w-full border rounded-sm px-4 py-2 text-sm")
4
+ end
5
+ end
@@ -1,10 +1,5 @@
1
1
  class Oversee::Field::Input::Datetime < Oversee::Field::Input
2
- def initialize(key:, value:)
3
- @key = key
4
- @value = value
5
- end
6
-
7
2
  def view_template
8
- input type: "datetime-local", id: field_id, name: field_name, value: @value&.strftime("%Y-%m-%dT%T"), class: "flex w-full border rounded-sm px-4 py-2 text-sm"
3
+ input(type: "datetime-local", id: field_id, name: field_name, value: value&.strftime("%Y-%m-%dT%T"), class: "flex w-full border rounded-sm px-4 py-2 text-sm")
9
4
  end
10
5
  end
@@ -1,10 +1,5 @@
1
1
  class Oversee::Field::Input::Integer < Oversee::Field::Input
2
- def initialize(key:, value:)
3
- @key = key
4
- @value = value
5
- end
6
-
7
2
  def view_template
8
- input type: "number", id: field_id, name: field_name, value: @value, class: "flex w-full border rounded-sm px-4 py-2 text-sm"
3
+ input(type: "number", id: field_id, name: field_name, value:, class: "flex w-full border rounded-sm px-4 py-2 text-sm")
9
4
  end
10
5
  end
@@ -0,0 +1,5 @@
1
+ class Oversee::Field::Input::Json < Oversee::Field::Input
2
+ def view_template
3
+ textarea(type: "text", id: field_id, name: field_name, class: "flex border px-4 py-2 text-sm w-full rounded-sm") { value.to_s }
4
+ end
5
+ end
@@ -1,14 +1,6 @@
1
1
  class Oversee::Field::Input::RichText < Oversee::Field::Input
2
2
  register_element :trix_editor
3
3
 
4
- attr_reader :key
5
- attr_reader :value
6
-
7
- def initialize(key:, value:)
8
- @key = key
9
- @value = value
10
- end
11
-
12
4
  def view_template
13
5
  div do
14
6
  input(type: "hidden", id: field_id, name: field_name, value:)
@@ -1,10 +1,5 @@
1
1
  class Oversee::Field::Input::String < Oversee::Field::Input
2
- def initialize(key:, value:)
3
- @key = key
4
- @value = value
5
- end
6
-
7
2
  def view_template
8
- input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
3
+ input(type: "text", id: field_id, name: field_name, value:, class: "flex border px-4 py-2 text-sm w-full rounded-sm")
9
4
  end
10
5
  end
@@ -1,24 +1,20 @@
1
- class Oversee::Field::Input < Oversee::Base
1
+ class Oversee::Field::Input < Oversee::Field
2
2
  MAP = {
3
3
  belongs_to: Oversee::Field::Input::BelongsTo,
4
4
  boolean: Oversee::Field::Input::Boolean,
5
+ date: Oversee::Field::Input::Date,
5
6
  datetime: Oversee::Field::Input::Datetime,
6
7
  enum: Oversee::Field::Input::String,
7
8
  integer: Oversee::Field::Input::Integer,
9
+ json: Oversee::Field::Input::Json,
10
+ jsonb: Oversee::Field::Input::Json,
8
11
  rich_text: Oversee::Field::Input::RichText,
9
12
  string: Oversee::Field::Input::String,
10
13
  text: Oversee::Field::Input::String,
11
14
  }
12
15
 
13
- def initialize(key: nil, value: nil, datatype: :string, **options)
14
- @key = key
15
- @value = value
16
- @datatype = datatype
17
- @options = options
18
- end
19
-
20
16
  def view_template
21
- render component_class.new(key: @key, value: @value, **@options)
17
+ render component_class.new(key:, value:, **@options)
22
18
  end
23
19
 
24
20
  private
@@ -29,6 +25,4 @@ class Oversee::Field::Input < Oversee::Base
29
25
 
30
26
  private
31
27
 
32
- def field_id = "resource_#{@key.to_s}"
33
- def field_name = "resource[#{@key.to_s}]"
34
28
  end
@@ -1,25 +1,25 @@
1
- class Oversee::Field::Label < Oversee::Base
1
+ class Oversee::Field::Label < Oversee::Field
2
2
  ICON_MAP = {
3
- string: Phlex::Icons::Iconoir::Text,
4
- text: Phlex::Icons::Iconoir::TextSquare,
5
- rich_text: Phlex::Icons::Iconoir::TextSquare,
6
- integer: Phlex::Icons::Iconoir::Number0Square,
7
- datetime: Phlex::Icons::Iconoir::Calendar,
8
3
  boolean: Phlex::Icons::Iconoir::SwitchOn,
9
4
  data: Phlex::Icons::Iconoir::Page,
5
+ date: Phlex::Icons::Iconoir::Calendar,
6
+ datetime: Phlex::Icons::Iconoir::Calendar,
7
+ integer: Phlex::Icons::Iconoir::Number0Square,
8
+ json: Phlex::Icons::Iconoir::CodeBracketsSquare,
9
+ jsonb: Phlex::Icons::Iconoir::CodeBracketsSquare,
10
+ rich_text: Phlex::Icons::Iconoir::TextSquare,
11
+ string: Phlex::Icons::Iconoir::Text,
12
+ text: Phlex::Icons::Iconoir::TextSquare,
10
13
  }
11
14
 
12
- def initialize(datatype: :string, key: nil)
13
- @datatype = datatype
14
- @key = key
15
- end
16
-
17
15
  def view_template
18
- div(id: "#{@key}_label", class:"inline-flex items-center space-x-2") do
19
- div(class: "size-5 bg-gray-100 inline-flex items-center justify-center") do
20
- render ICON_MAP[@datatype] ? ICON_MAP[@datatype].new(class: "size-3") : ICON_MAP[:data].new(class: "size-3")
21
- end
22
- label(class: "uppercase text-xs text-gray-700 font-medium block cursor-auto") { @key.to_s.humanize(keep_id_suffix: true) }
16
+ div(id: "#{key}_label", class:"inline-flex items-center space-x-2") do
17
+ div(class: "size-5 bg-gray-100 inline-flex items-center justify-center") { render icon.new(class: "size-3") }
18
+ label(class: "uppercase text-xs text-gray-700 font-medium block cursor-auto", for: key) { key.to_s.humanize(keep_id_suffix: true) }
23
19
  end
24
20
  end
21
+
22
+ def icon
23
+ ICON_MAP[datatype&.to_sym] || ICON_MAP[:data]
24
+ end
25
25
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Field::Set < Oversee::Field
4
+ def view_template
5
+ div(id: dom_id(resource, key), class:"flex flex-col gap-2") do
6
+ div(class:"flex items-center justify-between h-10 gap-4") do
7
+ render Oversee::Field::Label.new(key:, datatype:)
8
+ button(type: :submit, class: "bg-black text-white text-xs font-medium px-6 py-2 hover:opacity-75 transition-opacity", form: field_form_id) { "Save" } if state == :input
9
+ end
10
+ div(class: "flex items-center justify-between gap-2") { send("#{state}_state") }
11
+ end
12
+ end
13
+
14
+ def value_state
15
+ render Oversee::Field::Display.new(resource:, key:, value:, datatype:, **@options)
16
+ div(id: dom_id(@resource, :"#{key}_actions")) do
17
+ button(
18
+ class: "bg-gray-100 hover:bg-gray-200 text-gray-400 hover:text-blue-500 size-10 aspect-square inline-flex items-center justify-center transition-colors",
19
+ data: { controller: "clipboard", action: "click->clipboard#copy", clipboard_content_value: value.to_s }
20
+ ) do
21
+ span(class: "hidden", data: { clipboard_target: "successIcon"}) { render Phlex::Icons::Iconoir::Check.new(class: "size-4 text-emerald-500", stroke_width: 1.75) }
22
+ span(data: { clipboard_target: "copyIcon"}) { render Phlex::Icons::Iconoir::Copy.new(class: "size-4", stroke_width: 1.75) }
23
+ end
24
+ end
25
+ end
26
+
27
+ def input_state
28
+ render Oversee::Field::Form.new(resource: @resource, datatype:, key:, value:)
29
+ end
30
+
31
+ private
32
+
33
+ def state
34
+ @state ||= @options[:state] || :value
35
+ end
36
+ end
@@ -1,17 +1,11 @@
1
- class Oversee::Field::Value::BelongsTo < Phlex::HTML
2
- def initialize(key: nil, value: nil, **options)
3
- @key = key
4
- @value = value
5
- @options = options
6
- end
7
-
1
+ class Oversee::Field::Value::BelongsTo < Oversee::Field::Value
8
2
  def view_template
9
- p(title: @value, class:"inline-flex items-center gap-1") do
3
+ p(title: value, class:"inline-flex items-center gap-1") do
10
4
  if display_key?
11
- span { @key.humanize }
5
+ span { key.humanize }
12
6
  span {"|"}
13
7
  end
14
- span { @value }
8
+ span { value }
15
9
  end
16
10
  end
17
11
 
@@ -1,50 +1,5 @@
1
- class Oversee::Field::Value::Boolean < Phlex::HTML
2
- def initialize(key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::Boolean < Oversee::Field::Value
6
2
  def view_template
7
- @value ? check_icon : x_icon
8
- end
9
-
10
- private
11
-
12
- def check_icon
13
- svg(
14
- stroke_width: "2",
15
- viewbox: "0 0 24 24",
16
- fill: "none",
17
- xmlns: "http://www.w3.org/2000/svg",
18
- color: "currentColor",
19
- class: "size-4 text-emerald-500"
20
- ) do |s|
21
- s.path(
22
- d: "M5 13L9 17L19 7",
23
- stroke: "currentColor",
24
- stroke_width: "2",
25
- stroke_linecap: "round",
26
- stroke_linejoin: "round"
27
- )
28
- end
29
- end
30
-
31
- def x_icon
32
- svg(
33
- stroke_width: "1.5",
34
- viewbox: "0 0 24 24",
35
- fill: "none",
36
- xmlns: "http://www.w3.org/2000/svg",
37
- color: "currentColor",
38
- class: "size-4 text-rose-500"
39
- ) do |s|
40
- s.path(
41
- d:
42
- "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426",
43
- stroke: "currentColor",
44
- stroke_width: "1.5",
45
- stroke_linecap: "round",
46
- stroke_linejoin: "round"
47
- )
48
- end
3
+ render value ? Phlex::Icons::Iconoir::Check.new(class: "size-4 text-emerald-500") : Phlex::Icons::Iconoir::Xmark.new(class: "size-4 text-rose-500")
49
4
  end
50
5
  end
@@ -0,0 +1,5 @@
1
+ class Oversee::Field::Value::Date < Oversee::Field::Value
2
+ def view_template
3
+ time(title: value.to_s) { value&.to_fs(:long) }
4
+ end
5
+ end
@@ -1,9 +1,5 @@
1
- class Oversee::Field::Value::Datetime < Phlex::HTML
2
- def initialize(key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::Datetime < Oversee::Field::Value
6
2
  def view_template
7
- time(title: @value.to_s) { @value&.to_fs(:long) || "N/A" }
3
+ time(title: value.to_s) { value&.to_fs(:long) || "N/A" }
8
4
  end
9
5
  end
@@ -1,9 +1,5 @@
1
- class Oversee::Field::Value::Enum < Phlex::HTML
2
- def initialize(key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::Enum < Oversee::Field::Value
6
2
  def view_template
7
- p(class:"inline-flex px-2 py-1 text-red-500") { @value.to_s }
3
+ p(class:"inline-flex px-2 py-1 text-red-500") { value.to_s }
8
4
  end
9
5
  end
@@ -1,9 +1,5 @@
1
- class Oversee::Field::Value::Integer < Phlex::HTML
2
- def initialize(key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::Integer < Oversee::Field::Value
6
2
  def view_template
7
- p { @value }
3
+ p { value }
8
4
  end
9
5
  end
@@ -0,0 +1,6 @@
1
+ class Oversee::Field::Value::Json < Oversee::Field::Value
2
+ def view_template
3
+ return p(class: "text-gray-400 text-xs uppercase") { "JSON" } if for_table?
4
+ p(class: "truncate") { value.to_s }
5
+ end
6
+ end
@@ -1,9 +1,5 @@
1
- class Oversee::Field::Value::RichText < Phlex::HTML
2
- def initialize(datatype: :string, key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::RichText < Oversee::Field::Value
6
2
  def view_template
7
- p(class: "truncate") { @value }
3
+ p(class: "truncate") { value }
8
4
  end
9
5
  end
@@ -1,20 +1,13 @@
1
- class Oversee::Field::Value::String < Phlex::HTML
2
- def initialize(key: nil, value: nil, kind: :value)
3
- @key = key
4
- @value = value
5
- @kind = kind
6
- end
7
-
1
+ class Oversee::Field::Value::String < Oversee::Field::Value
8
2
  def view_template
9
3
  return p(class: "text-gray-400 text-xs uppercase") { "Empty" } if @value == ""
10
4
  return p(class: "text-gray-400 text-xs uppercase") { "Redacted" } if sensitive?
11
-
12
- p(class: "truncate") { @value }
5
+ p(class: "truncate") { value }
13
6
  end
14
7
 
15
8
  private
16
9
 
17
10
  def sensitive?
18
- @key&.downcase&.include?("password") || @key&.downcase&.include?("token")
11
+ key&.downcase&.include?("password") || key&.downcase&.include?("token")
19
12
  end
20
13
  end
@@ -1,9 +1,5 @@
1
- class Oversee::Field::Value::Text < Phlex::HTML
2
- def initialize(datatype: :string, key: nil, value: nil, kind: :value)
3
- @value = value
4
- end
5
-
1
+ class Oversee::Field::Value::Text < Oversee::Field::Value
6
2
  def view_template
7
- p(class: "truncate") { @value }
3
+ p(class: "truncate") { value }
8
4
  end
9
5
  end
@@ -3,9 +3,12 @@ class Oversee::Field::Value < Oversee::Base
3
3
  string: Oversee::Field::Value::String,
4
4
  belongs_to: Oversee::Field::Value::BelongsTo,
5
5
  boolean: Oversee::Field::Value::Boolean,
6
+ date: Oversee::Field::Value::Date,
6
7
  datetime: Oversee::Field::Value::Datetime,
7
8
  enum: Oversee::Field::Value::Enum,
8
9
  integer: Oversee::Field::Value::Integer,
10
+ json: Oversee::Field::Value::Json,
11
+ jsonb: Oversee::Field::Value::Json,
9
12
  rich_text: Oversee::Field::Value::RichText,
10
13
  text: Oversee::Field::Value::Text,
11
14
  }
@@ -0,0 +1,42 @@
1
+ class Oversee::Field < Oversee::Base
2
+ attr_reader :resource
3
+ attr_reader :key
4
+ attr_reader :value
5
+ attr_reader :datatype
6
+
7
+ def initialize(resource: nil, key: nil, value: nil, datatype: nil, **options)
8
+ @resource = resource
9
+ @key = key
10
+ @value = value
11
+ @datatype = datatype
12
+ @options = options
13
+ end
14
+
15
+ def view_template
16
+ plain "Oversee::Field"
17
+ end
18
+
19
+ # Sub-components
20
+ def __LABEL__ = Oversee::Field::Label.new(resource:, key:, value:, datatype:, **@options)
21
+ def __VALUE__ = Oversee::Field::Value.new(resource:, key:, value:, datatype:, **@options)
22
+ def __INPUT__ = Oversee::Field::Input.new(resource:, key:, value:, datatype:, **@options)
23
+ def __FORM__ = Oversee::Field::Form.new(resource:, key:, value:, datatype:, **@options)
24
+ def __SET__ = Oversee::Field::Set.new(resource:, key:, value:, datatype:, **@options)
25
+
26
+ # Helpers & Decorators
27
+ def field_id
28
+ @field_id ||= "resource_#{key}"
29
+ end
30
+
31
+ def field_name
32
+ @field_name ||= "resource[#{key}]"
33
+ end
34
+
35
+ def field_form_id
36
+ @form_id ||= dom_id(resource, "#{key}_form")
37
+ end
38
+
39
+ def resource_class_name
40
+ @resource_class_name ||= @resource.class.name
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Flash < Phlex::HTML
4
+ include Phlex::Rails::Helpers::Flash
5
+
6
+ def view_template
7
+ div(id: :flash, class: "fixed bottom-0 right-0 mb-8 mr-8 w-80 z-50") do
8
+ if !flash.empty?
9
+ div(
10
+ class: "flex flex-col gap-2 p-4 bg-white border border-gray-200/75 border-b-2 rounded-lg transition-all opacity-0 transform duration-300",
11
+ data: { controller: :notification }
12
+ ) do
13
+ notice if notice?
14
+ alert if alert?
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def notice?
23
+ flash[:notice].present?
24
+ end
25
+
26
+ def notice
27
+ render Phlex::Icons::Iconoir::CheckCircle.new(class: "size-5 text-emerald-500", stroke_width: 2)
28
+ p(class: "text-sm text-gray-700") { flash[:notice] }
29
+ end
30
+
31
+ def alert?
32
+ flash[:alert].present?
33
+ end
34
+
35
+ def alert
36
+ render Phlex::Icons::Iconoir::MessageAlert.new(class: "size-5 text-red-500", stroke_width: 2)
37
+ p(class: "text-sm text-gray-700") { flash[:alert] }
38
+ end
39
+ end
@@ -28,7 +28,7 @@ class Oversee::Layout::Application < Oversee::Base
28
28
  end
29
29
 
30
30
  body(class: "min-h-screen bg-gray-100 p-4") do
31
- # render Flash.new
31
+ render Oversee::Flash.new
32
32
 
33
33
  div(class: "flex gap-4 w-full") do
34
34
  div(class: "w-72 shrink-0") { render Oversee::Dashboard::Sidebar.new }
@@ -29,7 +29,7 @@ class Oversee::Resources::Index < Oversee::Base
29
29
  end
30
30
  hr(class: "my-4")
31
31
 
32
- render Oversee::Dashboard::Filters.new(params: @params)
32
+ render Oversee::Dashboard::Actions.new(params: @params)
33
33
 
34
34
  hr(class: "mt-4")
35
35
  render Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: @params)
@@ -57,30 +57,20 @@ class Oversee::Resources::Show < Oversee::Base
57
57
 
58
58
  hr(class: "my-4")
59
59
 
60
-
61
-
62
-
63
60
  # COLUMNS
64
- @resource_class.columns_hash.each do |key, metadata|
65
- next if @oversee_resource.foreign_keys.include?(key.to_s)
66
-
67
- div(class: "py-4") do
68
- div(class: "space-y-2") do
69
- render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
70
- div(id: dom_id(@resource, :"#{key}_row"), class: "flex items-center gap-2 mt-4") do
71
- render Oversee::Field::Display.new(resource:, key:, value: @resource.send(key), datatype: metadata.sql_type_metadata.type)
72
- # div(id: dom_id(@resource, :"#{key}_actions")) do
73
- # button(class: "bg-gray-100 hover:bg-gray-200 text-gray-400 hover:text-blue-500 size-10 aspect-square inline-flex items-center justify-center transition-colors") { render Phlex::Icons::Iconoir::Copy.new(class: "size-4") }
74
- # end
75
- end
76
- end
61
+ div(class: "flex flex-col gap-4") do
62
+ @resource_class.columns_hash.each do |key, metadata|
63
+ next if @oversee_resource.foreign_keys.include?(key.to_s)
64
+ value = @resource.send(key)
65
+ datatype = metadata.sql_type_metadata.type
66
+ render Oversee::Field::Set.new(resource:, key:, value:, datatype:)
77
67
  end
78
68
  end
79
69
 
80
- hr(class: "my-4")
81
70
 
82
71
  # RICH TEXT Associations
83
72
  if !!rich_text_associations.length
73
+ hr(class: "my-4")
84
74
  rich_text_associations.each do |association|
85
75
 
86
76
  # Remove the "rich_text_" prefix from the association name
@@ -78,7 +78,7 @@ class Oversee::Resources::Table < Oversee::Base
78
78
  next if @oversee_resource.foreign_keys.include?(key.to_s)
79
79
  row.data do
80
80
  div(class: "max-w-96") do
81
- render Oversee::Field::Value.new(datatype: metadata.sql_type_metadata.type, value: resource.send(key), key: key)
81
+ render Oversee::Field::Value.new(key:, datatype: metadata.sql_type_metadata.type, value: resource.send(key), for_table: true)
82
82
  end
83
83
  end
84
84
  end
@@ -1,29 +1,33 @@
1
1
  class Oversee::Resources::FieldsController < Oversee::ResourcesController
2
+ before_action :set_resource, only: %i[input]
3
+
2
4
  # Renders the display field for a resource
3
5
  def show
4
6
  end
5
7
 
6
8
  # Renders the input field for a resource
7
9
  def input
8
- set_resource
9
-
10
- key = params[:key].to_sym
11
- value = params[:value] || @resource.send(key)
12
- datatype = params[:datatype] || @resource.class.columns_hash[key.to_s].type
13
-
14
- # puts "---" * 30
15
- # puts "key: #{key}"
16
- # puts "value: #{value}"
17
- # puts "datatype: #{datatype}"
18
- # puts "---" * 30
19
-
20
- field_dom_id = dom_id(@resource, key)
21
- field = Oversee::Field::Form.new(resource: @resource, datatype:, key:, value:)
10
+ component_dom_id = dom_id(@resource, key)
11
+ component = Oversee::Field::Set.new(resource: @resource, datatype:, key:, value:, state: :input)
22
12
 
23
13
  respond_to do |format|
24
14
  format.turbo_stream do
25
- render turbo_stream: turbo_stream.replace(field_dom_id, field)
15
+ render turbo_stream: turbo_stream.replace(component_dom_id, component)
26
16
  end
27
17
  end
28
18
  end
19
+
20
+ private
21
+
22
+ def key
23
+ params[:key].to_sym
24
+ end
25
+
26
+ def value
27
+ params[:value] || @resource.send(key)
28
+ end
29
+
30
+ def datatype
31
+ params[:datatype] || @resource.class.columns_hash[key.to_s].type
32
+ end
29
33
  end
@@ -31,6 +31,7 @@ module Oversee
31
31
 
32
32
  respond_to do |format|
33
33
  if @resource.update(resource_params)
34
+ flash.now[:notice] = "Resource was successfully created."
34
35
  format.html { redirect_to resource_path(@resource.id, resource_class_name: @resource_class) }
35
36
  format.turbo_stream { redirect_to resource_path(@resource.id, resource_class_name: @resource_class), status: :see_other }
36
37
  else
@@ -58,10 +59,14 @@ module Oversee
58
59
 
59
60
  respond_to do |format|
60
61
  if @resource.update(resource_params)
62
+ flash.now[:notice] = "#{@resource.class.to_s.titleize.capitalize.gsub("::"," ")} was successfully updated!"
61
63
  format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
62
64
  format.turbo_stream do
63
- component = Oversee::Field::Display.new(resource: @resource, datatype:, key:, value: @resource.send(key))
64
- render turbo_stream: turbo_stream.replace(dom_id(@resource, key), component)
65
+ component = Oversee::Field::Set.new(resource: @resource, datatype:, key:, value: @resource.send(key))
66
+ render turbo_stream: [
67
+ turbo_stream.replace(dom_id(@resource, key), component),
68
+ turbo_stream.replace(:flash, Oversee::Flash)
69
+ ]
65
70
  end
66
71
  else
67
72
  end
@@ -0,0 +1,21 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["copyIcon", "successIcon"];
5
+ static values = { content: String };
6
+
7
+ async copy() {
8
+ try {
9
+ await navigator.clipboard.writeText(this.contentValue);
10
+ this.toggleIcons(true);
11
+ setTimeout(() => this.toggleIcons(false), 1500);
12
+ } catch (error) {
13
+ console.error(error.message);
14
+ }
15
+ }
16
+
17
+ toggleIcons(isCopied) {
18
+ this.copyIconTarget.classList.toggle("hidden", isCopied);
19
+ this.successIconTarget.classList.toggle("hidden", !isCopied);
20
+ }
21
+ }
@@ -1,4 +1,5 @@
1
1
  import { Application } from "@hotwired/stimulus";
2
+
2
3
  const application = Application.start();
3
4
 
4
5
  application.debug = false;
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ setTimeout(() => {
6
+ this.element.classList.remove("opacity-0");
7
+ }, 100);
8
+
9
+ setTimeout(() => {
10
+ this.element.classList.remove("opacity-100");
11
+ this.element.classList.add("opacity-0");
12
+ }, 2500);
13
+ setTimeout(() => {
14
+ this.element.outerHTML = "";
15
+ }, 3000);
16
+ }
17
+ }
@@ -1,7 +1,19 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  export default class extends Controller {
4
+ static targets = ["revealable"];
5
+ static values = { revealableId: String };
6
+
4
7
  connect() {
5
- console.log("Hello from reveal_controller.js");
8
+ if (this.hasRevealableIdValue)
9
+ this.target = document.getElementById(this.revealableIdValue);
10
+
11
+ if (!this.target && this.hasRevealableTarget)
12
+ this.target = this.revealableTarget;
13
+ }
14
+
15
+ toggle() {
16
+ if (!this.target) return;
17
+ this.target.classList.toggle("hidden");
6
18
  }
7
19
  }
@@ -2,22 +2,21 @@ class Oversee::Resource
2
2
  attr_reader :resource_class
3
3
  attr_reader :resource_class_name
4
4
  attr_reader :instance
5
- attr_reader :rich_text_associations
6
-
7
- attr_accessor :associations
5
+ attr_reader :associations
8
6
 
9
7
  def initialize(resource_class:, instance: nil)
10
8
  @resource_class = resource_class
11
- @resource_class_name = resource_class.to_s
12
9
  @instance = instance
13
10
  end
14
11
 
15
12
  # Route helpers
16
- def resources_path
17
-
13
+ def index_path
14
+ "/resources/#{resource_class_name}"
18
15
  end
19
16
 
20
- def resource_path
17
+ def show_path
18
+ # Rails.application.routes.url_helpers.resource_path(instance || object, resource_class_name:)
19
+ "/resources/#{resource_class_name}/#{instance.to_param}"
21
20
  end
22
21
 
23
22
  # Columns
@@ -59,4 +58,8 @@ class Oversee::Resource
59
58
  def rich_text_associations
60
59
  @rich_text_associations ||= associations[:has_one].select { |association| association[:rich_text] }
61
60
  end
61
+
62
+ def resource_class_name
63
+ @resource_class_name ||= resource_class.to_s
64
+ end
62
65
  end
@@ -1,3 +1,3 @@
1
1
  module Oversee
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
data/lib/oversee.rb CHANGED
@@ -11,6 +11,7 @@ require "phlex-rails"
11
11
 
12
12
  # Pagy
13
13
  require "pagy"
14
+ require "pagy/extras/i18n"
14
15
 
15
16
  # Zeitwerk
16
17
  loader = Zeitwerk::Loader.for_gem
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oversee
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elvinas Predkelis
@@ -9,10 +9,38 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-12-06 00:00:00.000000000 Z
12
+ date: 2024-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rails
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 7.0.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 7.0.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 7.0.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 7.0.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: activestorage
16
44
  requirement: !ruby/object:Gem::Requirement
17
45
  requirements:
18
46
  - - ">="
@@ -122,6 +150,8 @@ files:
122
150
  - app/assets/config/oversee_manifest.js
123
151
  - app/components/oversee/base.rb
124
152
  - app/components/oversee/card.rb
153
+ - app/components/oversee/dashboard/actions.rb
154
+ - app/components/oversee/dashboard/filter.rb
125
155
  - app/components/oversee/dashboard/filters.rb
126
156
  - app/components/oversee/dashboard/header.rb
127
157
  - app/components/oversee/dashboard/index.rb
@@ -129,25 +159,32 @@ files:
129
159
  - app/components/oversee/dashboard/pagination.rb
130
160
  - app/components/oversee/dashboard/sidebar.rb
131
161
  - app/components/oversee/dashboard/tailwind.rb
162
+ - app/components/oversee/field.rb
132
163
  - app/components/oversee/field/display.rb
133
164
  - app/components/oversee/field/form.rb
134
165
  - app/components/oversee/field/input.rb
135
166
  - app/components/oversee/field/input/belongs_to.rb
136
167
  - app/components/oversee/field/input/boolean.rb
168
+ - app/components/oversee/field/input/date.rb
137
169
  - app/components/oversee/field/input/datetime.rb
138
170
  - app/components/oversee/field/input/integer.rb
171
+ - app/components/oversee/field/input/json.rb
139
172
  - app/components/oversee/field/input/rich_text.rb
140
173
  - app/components/oversee/field/input/string.rb
141
174
  - app/components/oversee/field/label.rb
175
+ - app/components/oversee/field/set.rb
142
176
  - app/components/oversee/field/value.rb
143
177
  - app/components/oversee/field/value/belongs_to.rb
144
178
  - app/components/oversee/field/value/boolean.rb
179
+ - app/components/oversee/field/value/date.rb
145
180
  - app/components/oversee/field/value/datetime.rb
146
181
  - app/components/oversee/field/value/enum.rb
147
182
  - app/components/oversee/field/value/integer.rb
183
+ - app/components/oversee/field/value/json.rb
148
184
  - app/components/oversee/field/value/rich_text.rb
149
185
  - app/components/oversee/field/value/string.rb
150
186
  - app/components/oversee/field/value/text.rb
187
+ - app/components/oversee/flash.rb
151
188
  - app/components/oversee/layout/application.rb
152
189
  - app/components/oversee/resources/base.rb
153
190
  - app/components/oversee/resources/errors.rb
@@ -167,7 +204,9 @@ files:
167
204
  - app/controllers/oversee/resources/fields_controller.rb
168
205
  - app/controllers/oversee/resources_controller.rb
169
206
  - app/javascript/oversee/application.js
207
+ - app/javascript/oversee/controllers/clipboard_controller.js
170
208
  - app/javascript/oversee/controllers/index.js
209
+ - app/javascript/oversee/controllers/notification_controller.js
171
210
  - app/javascript/oversee/controllers/reveal_controller.js
172
211
  - app/javascript/oversee/controllers/sidebar/state_controller.js
173
212
  - app/models/oversee/application_record.rb