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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +32 -0
- data/MIT-LICENSE +20 -0
- data/README.md +109 -0
- data/Rakefile +6 -0
- data/app/assets/builds/application.js +9045 -0
- data/app/assets/builds/application.js.map +7 -0
- data/app/assets/builds/databasium.css +2 -0
- data/app/assets/config/databasium_manifest.js +1 -0
- data/app/assets/javascript/databasium/application.js +2 -0
- data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
- data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
- data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
- data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
- data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
- data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
- data/app/assets/javascript/databasium/controllers/index.js +7 -0
- data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
- data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
- data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
- data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
- data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
- data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
- data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
- data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
- data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
- data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
- data/app/assets/stylesheets/databasium/application.css +15 -0
- data/app/assets/stylesheets/databasium/colors.css +55 -0
- data/app/assets/stylesheets/databasium/custom.css +36 -0
- data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
- data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
- data/app/components/base.rb +50 -0
- data/app/components/databasium/collapsable.rb +62 -0
- data/app/components/databasium/forms/model.rb +147 -0
- data/app/components/databasium/forms/search.rb +31 -0
- data/app/components/databasium/global/error.rb +60 -0
- data/app/components/databasium/global/flash.rb +73 -0
- data/app/components/databasium/global/header_actions.rb +36 -0
- data/app/components/databasium/global/sidebar.rb +45 -0
- data/app/components/databasium/global/suggestion.rb +25 -0
- data/app/components/databasium/migrations/action.rb +39 -0
- data/app/components/databasium/migrations/file.rb +58 -0
- data/app/components/databasium/migrations/form.rb +222 -0
- data/app/components/databasium/migrations/header_actions.rb +87 -0
- data/app/components/databasium/migrations/migration_status.rb +22 -0
- data/app/components/databasium/migrations/preview.rb +29 -0
- data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
- data/app/components/databasium/migrations/sidebar.rb +28 -0
- data/app/components/databasium/models/attributes.rb +49 -0
- data/app/components/databasium/models/form.rb +100 -0
- data/app/components/databasium/models/header_actions.rb +51 -0
- data/app/components/databasium/models/model_preview.rb +31 -0
- data/app/components/databasium/models/sidebar.rb +25 -0
- data/app/components/databasium/models/templates/attribute.rb +99 -0
- data/app/components/databasium/models/templates/base.rb +6 -0
- data/app/components/databasium/models/templates/relation.rb +56 -0
- data/app/components/databasium/models/templates/validation.rb +285 -0
- data/app/components/databasium/navigation/base_icon.rb +32 -0
- data/app/components/databasium/navigation/frontend_icon.rb +17 -0
- data/app/components/databasium/navigation/get_icon.rb +26 -0
- data/app/components/databasium/navigation/icon.rb +28 -0
- data/app/components/databasium/navigation/icon_panel.rb +26 -0
- data/app/components/databasium/navigation/post_icon.rb +25 -0
- data/app/components/databasium/navigation/put_icon.rb +18 -0
- data/app/components/databasium/records/filter.rb +73 -0
- data/app/components/databasium/records/foreign_records.rb +84 -0
- data/app/components/databasium/records/header_actions.rb +110 -0
- data/app/components/databasium/records/show_turbo_stream.rb +75 -0
- data/app/components/databasium/records/sidebar.rb +23 -0
- data/app/components/databasium/records/table/record_panel.rb +60 -0
- data/app/components/databasium/records/table/row.rb +104 -0
- data/app/components/databasium/records/table.rb +125 -0
- data/app/components/databasium/records/table_turbo_frame.rb +37 -0
- data/app/components/databasium/records/utilities.rb +25 -0
- data/app/components/databasium/schemas/header_actions.rb +99 -0
- data/app/components/databasium/schemas/sidebar.rb +25 -0
- data/app/components/databasium/search_results/migrations.rb +37 -0
- data/app/components/databasium/search_results/models.rb +36 -0
- data/app/components/databasium/search_results/schema_models.rb +37 -0
- data/app/components/databasium/search_results/tables.rb +31 -0
- data/app/components/databasium/type_select.rb +35 -0
- data/app/controllers/databasium/application_controller.rb +68 -0
- data/app/controllers/databasium/homepage_controller.rb +5 -0
- data/app/controllers/databasium/migrations_controller.rb +186 -0
- data/app/controllers/databasium/models_controller.rb +105 -0
- data/app/controllers/databasium/records_controller.rb +156 -0
- data/app/controllers/databasium/schemas_controller.rb +52 -0
- data/app/helpers/databasium/application_helper.rb +4 -0
- data/app/helpers/databasium/heroicon_helper.rb +21 -0
- data/app/helpers/databasium/models_helper.rb +4 -0
- data/app/jobs/databasium/application_job.rb +4 -0
- data/app/mailers/databasium/application_mailer.rb +6 -0
- data/app/models/databasium/application_record.rb +5 -0
- data/app/models/model.json +0 -0
- data/app/services/databasium/migration.rb +176 -0
- data/app/services/databasium/model.rb +182 -0
- data/app/services/databasium/record.rb +65 -0
- data/app/services/databasium/schema.rb +146 -0
- data/app/views/base.rb +13 -0
- data/app/views/databasium/errors/non_development.rb +21 -0
- data/app/views/databasium/homepage/index.rb +29 -0
- data/app/views/databasium/migrations/index.rb +33 -0
- data/app/views/databasium/migrations/new.rb +29 -0
- data/app/views/databasium/models/index.rb +31 -0
- data/app/views/databasium/models/new.rb +37 -0
- data/app/views/databasium/records/index.rb +24 -0
- data/app/views/databasium/schemas/index.rb +39 -0
- data/app/views/layouts/databasium/application.rb +56 -0
- data/config/importmap.rb +12 -0
- data/config/initializers/heroicon.rb +12 -0
- data/config/initializers/pagy.rb +48 -0
- data/config/initializers/phlex.rb +19 -0
- data/config/routes.rb +31 -0
- data/config/tailwind.config.js +10 -0
- data/lib/databasium/engine.rb +57 -0
- data/lib/databasium/engine_mount.rb +37 -0
- data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
- data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
- data/lib/databasium/templates/migration.rb.tt +48 -0
- data/lib/databasium/templates/model.rb.tt +23 -0
- data/lib/databasium/version.rb +3 -0
- data/lib/databasium.rb +11 -0
- data/lib/tasks/databasium_tasks.rake +4 -0
- 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,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
|