databasium 0.1.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.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +109 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/builds/application.js +9045 -0
  7. data/app/assets/builds/application.js.map +7 -0
  8. data/app/assets/builds/databasium.css +2 -0
  9. data/app/assets/config/databasium_manifest.js +1 -0
  10. data/app/assets/javascript/databasium/application.js +2 -0
  11. data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
  12. data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
  13. data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
  14. data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
  15. data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
  16. data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
  17. data/app/assets/javascript/databasium/controllers/index.js +7 -0
  18. data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
  19. data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
  20. data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
  21. data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
  22. data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
  23. data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
  24. data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
  25. data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
  26. data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
  27. data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
  28. data/app/assets/stylesheets/databasium/application.css +15 -0
  29. data/app/assets/stylesheets/databasium/colors.css +55 -0
  30. data/app/assets/stylesheets/databasium/custom.css +36 -0
  31. data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
  32. data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
  33. data/app/components/base.rb +50 -0
  34. data/app/components/databasium/collapsable.rb +62 -0
  35. data/app/components/databasium/forms/model.rb +147 -0
  36. data/app/components/databasium/forms/search.rb +31 -0
  37. data/app/components/databasium/global/error.rb +60 -0
  38. data/app/components/databasium/global/flash.rb +73 -0
  39. data/app/components/databasium/global/header_actions.rb +36 -0
  40. data/app/components/databasium/global/sidebar.rb +45 -0
  41. data/app/components/databasium/global/suggestion.rb +25 -0
  42. data/app/components/databasium/migrations/action.rb +39 -0
  43. data/app/components/databasium/migrations/file.rb +58 -0
  44. data/app/components/databasium/migrations/form.rb +222 -0
  45. data/app/components/databasium/migrations/header_actions.rb +87 -0
  46. data/app/components/databasium/migrations/migration_status.rb +22 -0
  47. data/app/components/databasium/migrations/preview.rb +29 -0
  48. data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
  49. data/app/components/databasium/migrations/sidebar.rb +28 -0
  50. data/app/components/databasium/models/attributes.rb +49 -0
  51. data/app/components/databasium/models/form.rb +100 -0
  52. data/app/components/databasium/models/header_actions.rb +51 -0
  53. data/app/components/databasium/models/model_preview.rb +31 -0
  54. data/app/components/databasium/models/sidebar.rb +25 -0
  55. data/app/components/databasium/models/templates/attribute.rb +99 -0
  56. data/app/components/databasium/models/templates/base.rb +6 -0
  57. data/app/components/databasium/models/templates/relation.rb +56 -0
  58. data/app/components/databasium/models/templates/validation.rb +285 -0
  59. data/app/components/databasium/navigation/base_icon.rb +32 -0
  60. data/app/components/databasium/navigation/frontend_icon.rb +17 -0
  61. data/app/components/databasium/navigation/get_icon.rb +26 -0
  62. data/app/components/databasium/navigation/icon.rb +28 -0
  63. data/app/components/databasium/navigation/icon_panel.rb +26 -0
  64. data/app/components/databasium/navigation/post_icon.rb +25 -0
  65. data/app/components/databasium/navigation/put_icon.rb +18 -0
  66. data/app/components/databasium/records/filter.rb +73 -0
  67. data/app/components/databasium/records/foreign_records.rb +84 -0
  68. data/app/components/databasium/records/header_actions.rb +110 -0
  69. data/app/components/databasium/records/show_turbo_stream.rb +75 -0
  70. data/app/components/databasium/records/sidebar.rb +23 -0
  71. data/app/components/databasium/records/table/record_panel.rb +60 -0
  72. data/app/components/databasium/records/table/row.rb +104 -0
  73. data/app/components/databasium/records/table.rb +125 -0
  74. data/app/components/databasium/records/table_turbo_frame.rb +37 -0
  75. data/app/components/databasium/records/utilities.rb +25 -0
  76. data/app/components/databasium/schemas/header_actions.rb +99 -0
  77. data/app/components/databasium/schemas/sidebar.rb +25 -0
  78. data/app/components/databasium/search_results/migrations.rb +37 -0
  79. data/app/components/databasium/search_results/models.rb +36 -0
  80. data/app/components/databasium/search_results/schema_models.rb +37 -0
  81. data/app/components/databasium/search_results/tables.rb +31 -0
  82. data/app/components/databasium/type_select.rb +35 -0
  83. data/app/controllers/databasium/application_controller.rb +68 -0
  84. data/app/controllers/databasium/homepage_controller.rb +5 -0
  85. data/app/controllers/databasium/migrations_controller.rb +186 -0
  86. data/app/controllers/databasium/models_controller.rb +105 -0
  87. data/app/controllers/databasium/records_controller.rb +156 -0
  88. data/app/controllers/databasium/schemas_controller.rb +52 -0
  89. data/app/helpers/databasium/application_helper.rb +4 -0
  90. data/app/helpers/databasium/heroicon_helper.rb +21 -0
  91. data/app/helpers/databasium/models_helper.rb +4 -0
  92. data/app/jobs/databasium/application_job.rb +4 -0
  93. data/app/mailers/databasium/application_mailer.rb +6 -0
  94. data/app/models/databasium/application_record.rb +5 -0
  95. data/app/models/model.json +0 -0
  96. data/app/services/databasium/migration.rb +176 -0
  97. data/app/services/databasium/model.rb +182 -0
  98. data/app/services/databasium/record.rb +65 -0
  99. data/app/services/databasium/schema.rb +146 -0
  100. data/app/views/base.rb +13 -0
  101. data/app/views/databasium/errors/non_development.rb +21 -0
  102. data/app/views/databasium/homepage/index.rb +29 -0
  103. data/app/views/databasium/migrations/index.rb +33 -0
  104. data/app/views/databasium/migrations/new.rb +29 -0
  105. data/app/views/databasium/models/index.rb +31 -0
  106. data/app/views/databasium/models/new.rb +37 -0
  107. data/app/views/databasium/records/index.rb +24 -0
  108. data/app/views/databasium/schemas/index.rb +39 -0
  109. data/app/views/layouts/databasium/application.rb +56 -0
  110. data/config/importmap.rb +12 -0
  111. data/config/initializers/heroicon.rb +12 -0
  112. data/config/initializers/pagy.rb +48 -0
  113. data/config/initializers/phlex.rb +19 -0
  114. data/config/routes.rb +31 -0
  115. data/config/tailwind.config.js +10 -0
  116. data/lib/databasium/engine.rb +57 -0
  117. data/lib/databasium/engine_mount.rb +37 -0
  118. data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
  119. data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
  120. data/lib/databasium/templates/migration.rb.tt +48 -0
  121. data/lib/databasium/templates/model.rb.tt +23 -0
  122. data/lib/databasium/version.rb +3 -0
  123. data/lib/databasium.rb +11 -0
  124. data/lib/tasks/databasium_tasks.rake +4 -0
  125. metadata +272 -0
@@ -0,0 +1,36 @@
1
+ @layer components {
2
+ .scrollbar-thin {
3
+ /* The height makes the horizontal scroll thin */
4
+ &::-webkit-scrollbar {
5
+ height: 6px;
6
+ width: 6px;
7
+ @apply rounded-full;
8
+ }
9
+
10
+ &::-webkit-scrollbar-track {
11
+ @apply bg-background;
12
+ }
13
+
14
+ &::-webkit-scrollbar-thumb {
15
+ @apply bg-panel;
16
+ }
17
+
18
+ &::-webkit-scrollbar-thumb:hover {
19
+ @apply bg-hover;
20
+ }
21
+
22
+ /* Support for Firefox */
23
+ scrollbar-width: thin;
24
+ scrollbar-color: theme("colors.panel") theme("colors.background");
25
+ }
26
+ }
27
+
28
+ @layer base {
29
+ input:-webkit-autofill,
30
+ input:-webkit-autofill:hover,
31
+ input:-webkit-autofill:focus {
32
+ -webkit-box-shadow: 0 0 0px 1000px var(--color-background) inset !important;
33
+ -webkit-text-fill-color: var(--color-main-text) !important;
34
+ transition: background-color 5000s ease-in-out 0s;
35
+ }
36
+ }
@@ -0,0 +1,6 @@
1
+ /* Our entry point for tailwindcss */
2
+
3
+ @import "tailwindcss";
4
+ @import "./pagy-tailwind.css";
5
+ @import "./colors.css";
6
+ @import "./custom.css";
@@ -0,0 +1,66 @@
1
+ @layer components {
2
+ .pagy {
3
+ --B: 1;
4
+ --H: 360;
5
+ --S: 48.71;
6
+ --L: 73.56;
7
+ --A: 1;
8
+ --spacing: 0.1875rem;
9
+ --padding: 0.5rem;
10
+ --rounding: 0.75rem;
11
+ --border-width: 0.0625rem;
12
+ --font-size: 0.875rem;
13
+ --font-weight: 300;
14
+ --line-height: 1.75;
15
+
16
+ --text: hsl(var(--H) var(--S) calc(var(--L) - (30 * var(--B))));
17
+ --text-hover: hsl(var(--H) var(--S) calc(var(--L) - (33 * var(--B))));
18
+ --text-current: hsl(var(--H) var(--S) calc(100 * (var(--B) + 1)));
19
+ --background: hsl(var(--H) var(--S) calc(var(--L) + (30 * var(--B))));
20
+ --background-hover: hsl(var(--H) var(--S) calc(var(--L) + (20 * var(--B))));
21
+ --background-current: hsl(var(--H) var(--S) var(--L));
22
+ --background-input: hsl(var(--H) var(--S) calc(var(--L) + (45 * var(--B))));
23
+ --opacity: 1;
24
+
25
+ @apply flex gap-x-[var(--spacing)] font-[var(--font-weight)]
26
+ text-[length:var(--font-size)] text-[var(--text)]
27
+ leading-[var(--line-height)];
28
+
29
+ a:not([role="separator"]) {
30
+ /* all but gaps */
31
+ @apply block rounded-[var(--rounding)] px-[var(--padding)] py-[calc(var(--padding)/3)] bg-[var(--background)]
32
+ border-solid border-[var(--background-current)] border-[length:var(--border-width)] opacity-[var(--opacity)];
33
+ }
34
+
35
+ a[href]:hover {
36
+ /* all links on hover */
37
+ @apply bg-[var(--background-hover)] text-[var(--text-hover)];
38
+ }
39
+
40
+ a:not([href]) {
41
+ /* all but links */
42
+ @apply cursor-default;
43
+ }
44
+
45
+ a[role="link"]:not([aria-current]) {
46
+ /* disabled links */
47
+ @apply opacity-[calc(var(--opacity)*.6)];
48
+ }
49
+
50
+ a[aria-current] {
51
+ /* current page */
52
+ @apply bg-[var(--background-current)] text-[var(--text-current)];
53
+ }
54
+
55
+ label {
56
+ @apply inline-block whitespace-nowrap rounded-[var(--rounding)] px-[var(--padding)]
57
+ py-[calc(var(--padding)/3-var(--border-width))]
58
+ bg-[var(--background)] border-solid border-[length:var(--border-width)] border-[var(--background-current)];
59
+
60
+ input {
61
+ @apply text-[var(--text)] text-[length:var(--font-size)] leading-[var(--line-height)] rounded-[calc(var(--rounding)/2)]
62
+ font-[var(--font-weight)] bg-[var(--background-input)] border-[length:var(--border-width)] border-[var(--background-current)];
63
+ }
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Databasium
4
+ class Components::Base < Phlex::HTML
5
+ # Include any helpers you want to be available across all components
6
+ include Phlex::Rails::Helpers::Routes
7
+ include Phlex::Rails::Helpers::ClassNames
8
+ include ::Databasium::HeroiconHelper
9
+
10
+ if Rails.env.development?
11
+ def before_template
12
+ comment { "Before #{self.class.name}" }
13
+ super
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def minimalistic_label_class
20
+ "text-xs absolute -top-2.5 left-0"
21
+ end
22
+
23
+ def render_x_button(action: nil)
24
+ button(type: "button", class: "text-red-500", data: { action: action }) do
25
+ heroicon "x-mark", variant: :solid, options: { class: "w-8 h-8" }
26
+ end
27
+ end
28
+
29
+ def render_collapsable(
30
+ form: nil,
31
+ name:,
32
+ data_targets: {},
33
+ name_params: {},
34
+ class_name: nil,
35
+ target_container: nil,
36
+ &block
37
+ )
38
+ render Components::Databasium::Collapsable.new(
39
+ name: name,
40
+ form: form,
41
+ data_targets: data_targets,
42
+ name_params: name_params,
43
+ class_name: class_name,
44
+ target_container: target_container
45
+ ) do
46
+ yield if block_given?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Collapsable < Components::Base
6
+ def initialize(
7
+ name: nil,
8
+ form: nil,
9
+ class_name: nil,
10
+ data_targets: {},
11
+ name_params: nil,
12
+ target_container: nil
13
+ )
14
+ @name = name
15
+ @form = form
16
+ @class_name = class_name
17
+ @data_targets = data_targets
18
+ @name_params = name_params || {}
19
+ @target_container = target_container
20
+ end
21
+
22
+ def view_template(&block)
23
+ form(&block)
24
+ end
25
+
26
+ private
27
+
28
+ def form(&block)
29
+ div(
30
+ class: @class_name ? @class_name : "w-full border-1 border-border rounded-xl p-3",
31
+ data: @data_targets
32
+ ) do
33
+ div(data: { controller: "collapse" }) do
34
+ button(
35
+ data: {
36
+ action: "click->collapse#toggle"
37
+ },
38
+ class: "flex items-center justify-between w-full h-fit hover:cursor-pointer px-3"
39
+ ) do
40
+ if @form
41
+ raw @form.label(@name, class: "text-lg font-semibold")
42
+ else
43
+ h2(class: "text-lg font-semibold", **@name_params) { @name }
44
+ end
45
+ heroicon(
46
+ "chevron-down",
47
+ variant: :solid,
48
+ options: {
49
+ class: "w-8 h-8 text-red-500",
50
+ data_collapse_target: "collapseIcon"
51
+ }
52
+ )
53
+ end
54
+ div(class: "hidden", data: { collapse_target: "content" }) do
55
+ div(class: "w-full") { div(data: @target_container) { yield if block_given? } }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Forms::Model < Components::Base
6
+ include Phlex::Rails::Helpers::HiddenFieldTag
7
+ include Phlex::Rails::Helpers::ContentTag
8
+ include Phlex::Rails::Helpers::FormWith
9
+ include Phlex::Rails::Helpers::LinkTo
10
+ include Phlex::Rails::Helpers::TurboFrameTag
11
+
12
+ TYPES = %w[
13
+ string
14
+ text
15
+ integer
16
+ float
17
+ double
18
+ decimal
19
+ boolean
20
+ date
21
+ datetime
22
+ timestamp
23
+ time
24
+ binary
25
+ ].freeze
26
+ SKIPPED_COLUMNS = %w[created_at updated_at id].freeze
27
+
28
+ def initialize(columns_names_types:, model:)
29
+ @columns_names_types = columns_names_types
30
+ @model = model
31
+ end
32
+
33
+ def view_template
34
+ if @model
35
+ form_with(
36
+ method: :post,
37
+ scope: :record,
38
+ url: databasium.records_path,
39
+ class:
40
+ "border-b-1 border-border p-4 rounded-xl hidden max-h-100 overflow-y-auto flex-1",
41
+ id: "addRecord",
42
+ data: {
43
+ table_target: "addRecordForm"
44
+ }
45
+ ) do |form|
46
+ hidden_field_tag(:table, @model.name)
47
+ div(class: "grid grid-cols-2 gap-4 w-fit") do
48
+ render_fields(form)
49
+ render_submit_button(form)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def render_fields(form)
58
+ @columns_names_types.each do |column|
59
+ next if column[:name].in?(SKIPPED_COLUMNS)
60
+
61
+ raw form.label(column[:name], class: "underline p-1 h-full text-sm")
62
+
63
+ render_foreign_key_content(form, column) if column[:foreign_key]
64
+ render_field(form, column) unless column[:foreign_key]
65
+ end
66
+ end
67
+
68
+ def render_submit_button(form)
69
+ raw form.submit(
70
+ "Add record",
71
+ class: "bg-blue-500 px-4 py-2 rounded-md w-fit",
72
+ id: "add_record_button"
73
+ )
74
+ end
75
+
76
+ def render_field(form, column)
77
+ div(class: "flex flex-col relative") do
78
+ p(class: "text-xs font-light z-10 text-end absolute -top-3 right-0") { column[:type] }
79
+ raw form.public_send(
80
+ type_to_helper(column[:type]),
81
+ column[:name],
82
+ class: "border-1 rounded-xl p-1 border-border bg-panel text-sm"
83
+ )
84
+ end
85
+ end
86
+
87
+ def type_to_helper(type)
88
+ unless type.in?(TYPES)
89
+ raise ArgumentError, "Invalid type: #{type}. Known types are: #{TYPES.join(", ")}"
90
+ end
91
+
92
+ case type
93
+ when "decimal", "float", "double", "integer"
94
+ "number_field"
95
+ when "text"
96
+ "text_area"
97
+ when "date"
98
+ "date_field"
99
+ when "datetime", "timestamp"
100
+ "datetime_local_field"
101
+ when "time"
102
+ "time_field"
103
+ when "boolean"
104
+ "check_box"
105
+ when "binary"
106
+ "file_field"
107
+ else
108
+ "text_field"
109
+ end
110
+ end
111
+
112
+ def render_foreign_key_content(form, column)
113
+ frame_id = foreign_records_frame_id(column)
114
+ div(data: { controller: "table-select" }, class: "relative flex overflow-visible") do
115
+ raw form.public_send(
116
+ type_to_helper(column[:type]),
117
+ column[:name],
118
+ class: "border-2 rounded-xl p-1 border-gray-300 me-2 w-full",
119
+ data: {
120
+ table_select_target: "foreignKeyInput"
121
+ }
122
+ )
123
+
124
+ link_to(
125
+ helpers.records_records_path(table: column[:to_table], frame_id: frame_id),
126
+ class: "border-1 border-blue-500 rounded-md flex justify-center items-center p-1",
127
+ data: {
128
+ turbo_frame: frame_id
129
+ }
130
+ ) do
131
+ heroicon "arrow-right-circle",
132
+ variant: :solid,
133
+ options: {
134
+ class: "w-8 h-8 text-blue-500"
135
+ }
136
+ end
137
+
138
+ turbo_frame_tag(frame_id)
139
+ end
140
+ end
141
+
142
+ def foreign_records_frame_id(column)
143
+ "foreign_records_#{@model.name}_#{column[:to_table]}"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Forms::Search < Components::Base
6
+ include Phlex::Rails::Helpers::FormWith
7
+
8
+ def initialize(url:, turbo_frame:, placeholder:)
9
+ @url = url
10
+ @turbo_frame = turbo_frame
11
+ @placeholder = placeholder
12
+ end
13
+
14
+ def view_template
15
+ div(data: { controller: "search" }) do
16
+ form_with url: @url,
17
+ method: :get,
18
+ data: {
19
+ turbo_frame: @turbo_frame,
20
+ action: "input->search#update"
21
+ } do |form|
22
+ raw form.search_field :search,
23
+ class:
24
+ "border-2 border-border bg-background rounded-md p-2 w-full",
25
+ placeholder: @placeholder
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Global::Error < Components::Base
6
+ include Phlex::Rails::Helpers::TurboFrameTag
7
+
8
+ def initialize(message: nil, details: nil, type: nil)
9
+ @message = message
10
+ @details = details
11
+ @type = type
12
+ end
13
+
14
+ def view_template
15
+ div(id: "error") do
16
+ div(
17
+ class:
18
+ "absolute top-20 left-1/2 -translate-x-1/2 px-4 z-50 transition-opacity duration-1000 ease-in-out",
19
+ data: {
20
+ controller: "error"
21
+ }
22
+ ) { render_error if @message || @details }
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def render_error
29
+ div(
30
+ class:
31
+ "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl shadow-md lg:w-4xl mx-auto relative overflow-y-auto max-h-100",
32
+ role: "alert"
33
+ ) do
34
+ button(
35
+ type: "button",
36
+ class: "absolute right-4 top-1",
37
+ data: {
38
+ action: "click->error#close"
39
+ }
40
+ ) do
41
+ heroicon("x-mark", variant: :solid, options: { class: "w-8 h-8 hover:cursor-pointer" })
42
+ end
43
+ h2(class: "font-bold pr-10") { @type || "Error" }
44
+ p(class: "block whitespace-pre-wrap pr-10") { @message }
45
+ render_details if @details.present?
46
+ end
47
+ end
48
+
49
+ def render_details
50
+ details(class: "mt-3") do
51
+ summary(class: "cursor-pointer font-semibold") { "Details" }
52
+ pre(
53
+ class:
54
+ "mt-2 max-h-64 overflow-auto whitespace-pre rounded-lg bg-red-50 p-2 font-mono text-xs"
55
+ ) { plain @details }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Global::Flash < Components::Base
6
+ include Phlex::Rails::Helpers::TurboFrameTag
7
+
8
+ def initialize(success: nil, error: nil)
9
+ @success = success
10
+ @error = error
11
+ end
12
+
13
+ def view_template
14
+ turbo_frame_tag "flash" do
15
+ if @success || @error
16
+ div(
17
+ class:
18
+ "absolute top-20 left-1/2 -translate-x-1/2 px-4 z-50 transition-opacity duration-1000 ease-in-out",
19
+ data: {
20
+ controller: "flash"
21
+ }
22
+ ) do
23
+ render_success if @success
24
+ render_error if @error
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def render_success
33
+ div(
34
+ class:
35
+ "bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-xl shadow-md lg:w-4xl mx-auto relative",
36
+ role: "alert"
37
+ ) do
38
+ button(
39
+ type: "button",
40
+ class: "absolute right-4 top-2",
41
+ data: {
42
+ action: "click->flash#close"
43
+ }
44
+ ) do
45
+ heroicon("x-mark", variant: :solid, options: { class: "w-8 h-8 hover:cursor-pointer" })
46
+ end
47
+ h2(class: "font-bold border-b-1 border-b-gray-300") { "Success!" }
48
+ p(class: "block") { @success }
49
+ end
50
+ end
51
+
52
+ def render_error
53
+ div(
54
+ class:
55
+ "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl shadow-md lg:w-4xl mx-auto relative",
56
+ role: "alert"
57
+ ) do
58
+ button(
59
+ type: "button",
60
+ class: "absolute right-4 top-2",
61
+ data: {
62
+ action: "click->flash#close"
63
+ }
64
+ ) do
65
+ heroicon("x-mark", variant: :solid, options: { class: "w-8 h-8 hover:cursor-pointer" })
66
+ end
67
+ h2(class: "font-bold") { "Error!" }
68
+ p(class: "block") { @error }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Global::HeaderActions < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::Routes
8
+ include Phlex::Rails::Helpers::TurboFrameTag
9
+
10
+ def initialize(actions: nil)
11
+ @actions = actions
12
+ end
13
+
14
+ def view_template
15
+ render_navigation_links
16
+ end
17
+
18
+ private
19
+
20
+ def render_navigation_links
21
+ div(
22
+ class:
23
+ "border-b-1 bg-panel border-border flex min-w-0 items-center text-2xl gap-4 py-2 ps-4"
24
+ ) do
25
+ div(
26
+ class: "flex shrink-0 items-center gap-4 border-1 p-1 p-2 rounded-xl font-bold",
27
+ data: {
28
+ action: "click->layout#toggleSidebar"
29
+ }
30
+ ) { heroicon("arrows-right-left", variant: :outline, options: { class: "w-4 h-4" }) }
31
+ div(id: "header_actions", class: "min-w-0 flex-1 overflow-x-auto") { raw @actions }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ module Components
2
+ module Databasium
3
+ class Global::Sidebar < Components::Base
4
+ include Phlex::Rails::Helpers::LinkTo
5
+ include Phlex::Rails::Helpers::CurrentPage
6
+ PAGES = [
7
+ { icon: "list-bullet", path: :records_path, text: "Records" },
8
+ { icon: "arrow-path", path: :migrations_path, text: "Migrations" },
9
+ { icon: "cube", path: :new_model_path, text: "Models" },
10
+ { icon: "table-cells", path: :schemas_path, text: "Schema" }
11
+ ].freeze
12
+
13
+ def initialize(sidebar: nil)
14
+ @sidebar = sidebar
15
+ end
16
+
17
+ def view_template
18
+ div(
19
+ class: "flex flex-col bg-panel border-r-2 border-r-border py-2 px-3 w-80",
20
+ data: {
21
+ layout_target: "sidebar"
22
+ }
23
+ ) do
24
+ div(class: "flex flex-col gap-2 pb-6") do
25
+ PAGES.each do |page|
26
+ path = databasium.send(page[:path])
27
+ link_to(
28
+ path,
29
+ class:
30
+ class_names(
31
+ "text-main-text hover:text-hover flex items-center gap-2 py-2 px-3 border border-border rounded-lg",
32
+ "bg-accent shadow-accent" => current_page?(path)
33
+ )
34
+ ) do
35
+ heroicon(page[:icon], variant: :outline, options: { class: "w-6 h-6" })
36
+ span(class: "ml-2") { page[:text] }
37
+ end
38
+ end
39
+ end
40
+ raw @sidebar
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Global::Suggestion < Components::Base
6
+ def initialize(suggestions:)
7
+ @suggestions = suggestions
8
+ end
9
+
10
+ def view_template
11
+ div(
12
+ class: "flex flex-col gap-2 bg-panel border-1 border-border rounded-xl p-2 m-4",
13
+ id: "suggestion"
14
+ ) do
15
+ @suggestions.each do |suggestion|
16
+ div(class: "flex items-center gap-2") do
17
+ heroicon("information-circle", variant: :outline, options: { class: "w-6 h-6" })
18
+ span(class: "text-main-text") { suggestion }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ module Components
2
+ module Databasium
3
+ class Migrations::Action < Components::Base
4
+ include Phlex::Rails::Helpers::TurboStream
5
+
6
+ def initialize(success:, error:, migration_version: nil, status: nil)
7
+ @success = success
8
+ @error = error
9
+ @migration_version = migration_version
10
+ @status = status
11
+ end
12
+
13
+ def view_template
14
+ render_flash_stream if @success || @error
15
+ render_migration_status_stream if @status
16
+ end
17
+
18
+ private
19
+
20
+ def render_flash_stream
21
+ turbo_stream.replace("error", Components::Databasium::Global::Error.new)
22
+ turbo_stream.replace(
23
+ "flash",
24
+ Components::Databasium::Global::Flash.new(success: @success, error: @error)
25
+ )
26
+ end
27
+
28
+ def render_migration_status_stream
29
+ turbo_stream.replace(
30
+ "migration_#{@migration_version}_status",
31
+ Components::Databasium::Migrations::MigrationStatus.new(
32
+ status: @status,
33
+ version: @migration_version
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end