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,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Components
|
|
4
|
+
module Databasium
|
|
5
|
+
class Records::Filter < Components::Base
|
|
6
|
+
include Phlex::Rails::Helpers::FormWith
|
|
7
|
+
include Phlex::Rails::Helpers::HiddenFieldTag
|
|
8
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
9
|
+
def initialize(model:, turbo_frame:, columns_names_types:, hidden: true)
|
|
10
|
+
@model = model
|
|
11
|
+
@turbo_frame = turbo_frame
|
|
12
|
+
@columns_names_types = columns_names_types
|
|
13
|
+
@hidden = hidden
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template
|
|
17
|
+
if @model
|
|
18
|
+
div(
|
|
19
|
+
class: class_names("hidden" => @hidden),
|
|
20
|
+
id: "filter",
|
|
21
|
+
data: {
|
|
22
|
+
toggle_target: "filter",
|
|
23
|
+
controller: "filter",
|
|
24
|
+
filter_columns_value: @columns_names_types.to_json
|
|
25
|
+
}
|
|
26
|
+
) { render_filter }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def render_filter
|
|
33
|
+
form_with(
|
|
34
|
+
url: databasium.records_records_path(table: @model.name, frame_id: @turbo_frame),
|
|
35
|
+
method: :get,
|
|
36
|
+
class:
|
|
37
|
+
"max-h-50 overflow-y-auto border-b-1 rounded-xl border-border p-2 flex flex-col gap-2",
|
|
38
|
+
data: {
|
|
39
|
+
action: "change->search#update",
|
|
40
|
+
filter_target: "form",
|
|
41
|
+
turbo_frame: @turbo_frame
|
|
42
|
+
}
|
|
43
|
+
) do |form|
|
|
44
|
+
hidden_field_tag :table, @model.name
|
|
45
|
+
hidden_field_tag :frame_id, @turbo_frame
|
|
46
|
+
div(class: "w-full flex gap-4 items-center col-span-2") do
|
|
47
|
+
button(
|
|
48
|
+
data: {
|
|
49
|
+
action: "click->filter#addFilter"
|
|
50
|
+
},
|
|
51
|
+
class: "ps-4 py-1 rounded underline w-fit"
|
|
52
|
+
) { "Add Filter" }
|
|
53
|
+
div(class: "flex items-center gap-5") do
|
|
54
|
+
form.submit(
|
|
55
|
+
"Run Filters",
|
|
56
|
+
class: "bg-accent px-4 py-2 rounded-md",
|
|
57
|
+
data: {
|
|
58
|
+
turbo_stream: true
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
heroicon "x-mark",
|
|
64
|
+
variant: :solid,
|
|
65
|
+
options: {
|
|
66
|
+
class: "w-8 h-8 hidden mr-2",
|
|
67
|
+
data_filter_target: "removeIcon"
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Components
|
|
4
|
+
module Databasium
|
|
5
|
+
class Records::ForeignRecords < Components::Base
|
|
6
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
7
|
+
|
|
8
|
+
def initialize(model:, columns_names_types:, frame_id: nil)
|
|
9
|
+
@model = model
|
|
10
|
+
@columns_names_types = columns_names_types
|
|
11
|
+
@frame_id = frame_id || "foreign_records_#{@model&.name}"
|
|
12
|
+
@table_id = @frame_id.sub(/\Aforeign_records_/, "foreign_records_table_")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def view_template
|
|
16
|
+
turbo_frame_tag(@frame_id) do
|
|
17
|
+
div(
|
|
18
|
+
class: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/40 p-4",
|
|
19
|
+
data: {
|
|
20
|
+
table_select_target: "table"
|
|
21
|
+
}
|
|
22
|
+
) do
|
|
23
|
+
div(
|
|
24
|
+
class:
|
|
25
|
+
"relative bg-panel p-4 rounded-xl border-1 border-border w-fit max-w-[50vw] flex flex-col h-[90dvh] overflow-auto shadow-2xl",
|
|
26
|
+
id: @frame_id,
|
|
27
|
+
data: {
|
|
28
|
+
action: "click->table-select#stopPropagation"
|
|
29
|
+
}
|
|
30
|
+
) do
|
|
31
|
+
render_title
|
|
32
|
+
render_selected_record
|
|
33
|
+
render_filters
|
|
34
|
+
render_table
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def render_selected_record
|
|
43
|
+
div(class: "flex flex gap-2 items-center") do
|
|
44
|
+
h2 { "Selected Record:" }
|
|
45
|
+
p(class: "font-bold", data: { table_select_target: "selectedRecord" }) do
|
|
46
|
+
"You haven't yet selected a record"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render_title
|
|
52
|
+
div(class: "flex justify-between items-center mb-2 border-b-1 border-border pb-2 mb-2") do
|
|
53
|
+
h1(class: "w-ful text-2xl font-bold") { "Records of #{@model&.name}" }
|
|
54
|
+
button(
|
|
55
|
+
class: "text-accent hover:text-accent-dark",
|
|
56
|
+
data: {
|
|
57
|
+
action: "click->table-select#toggleVisibility"
|
|
58
|
+
}
|
|
59
|
+
) { heroicon("x-mark", variant: :solid, options: { class: "w-8 h-8" }) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_filters
|
|
64
|
+
render Records::Filter.new(
|
|
65
|
+
model: @model,
|
|
66
|
+
turbo_frame: @table_id,
|
|
67
|
+
columns_names_types: @columns_names_types,
|
|
68
|
+
hidden: false
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render_table
|
|
73
|
+
turbo_frame_tag @table_id,
|
|
74
|
+
class: "flex-1 min-h-0 overflow-auto block",
|
|
75
|
+
src:
|
|
76
|
+
helpers.records_records_path(
|
|
77
|
+
table: @model&.name,
|
|
78
|
+
frame_id: @table_id,
|
|
79
|
+
lazy: true
|
|
80
|
+
) { }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Components
|
|
4
|
+
module Databasium
|
|
5
|
+
class Records::HeaderActions < Components::Base
|
|
6
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
7
|
+
include Phlex::Rails::Helpers::Routes
|
|
8
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
9
|
+
include Phlex::Rails::Helpers::ButtonTo
|
|
10
|
+
LIMITS = [ 10, 20, 50, 100 ].freeze
|
|
11
|
+
def initialize(filter:, table:, limit:)
|
|
12
|
+
@filter = filter
|
|
13
|
+
@table = table
|
|
14
|
+
@limit = limit
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def view_template
|
|
18
|
+
div(id: "header_actions", class: "flex max-w-full items-center overflow-x-auto") do
|
|
19
|
+
limit = @limit.to_i
|
|
20
|
+
div(class: "flex items-center gap-2 mr-2") do
|
|
21
|
+
p(class: "text-sm font-bold") { "#{@table}" }
|
|
22
|
+
end
|
|
23
|
+
button(
|
|
24
|
+
type: "submit",
|
|
25
|
+
form: "delete_records_form",
|
|
26
|
+
data: {
|
|
27
|
+
turbo_stream: true
|
|
28
|
+
},
|
|
29
|
+
class: "hidden bg-accent px-4 py-1 rounded-xl text-base me-2"
|
|
30
|
+
) { span(data: { table_target: "deleteButton" }) { } }
|
|
31
|
+
|
|
32
|
+
render Components::Databasium::Navigation::IconPanel.new(
|
|
33
|
+
icons_with_text: [
|
|
34
|
+
{
|
|
35
|
+
icon: "funnel",
|
|
36
|
+
text: "filter",
|
|
37
|
+
method: :frontend,
|
|
38
|
+
data_params: {
|
|
39
|
+
toggle: "filter",
|
|
40
|
+
action: "click->toggle#toggle"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
icon: "plus-circle",
|
|
45
|
+
text: "add",
|
|
46
|
+
method: :frontend,
|
|
47
|
+
data_params: {
|
|
48
|
+
toggle: "addRecord",
|
|
49
|
+
action: "click->toggle#toggle"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
icon: "pencil-square",
|
|
54
|
+
text: "edit",
|
|
55
|
+
method: :frontend,
|
|
56
|
+
data_params: {
|
|
57
|
+
toggle: "editRecord",
|
|
58
|
+
action: "click->toggle#toggleSticky"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
icon: "chevron-double-up",
|
|
63
|
+
text: "10",
|
|
64
|
+
method: :get,
|
|
65
|
+
turbo_frame: "records_list",
|
|
66
|
+
path: records_path(),
|
|
67
|
+
active: limit == 10
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
icon: "chevron-double-up",
|
|
71
|
+
text: "20",
|
|
72
|
+
method: :get,
|
|
73
|
+
turbo_frame: "records_list",
|
|
74
|
+
path: records_path(limit: 20),
|
|
75
|
+
active: limit == 20
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
icon: "chevron-double-up",
|
|
79
|
+
text: "50",
|
|
80
|
+
method: :get,
|
|
81
|
+
turbo_frame: "records_list",
|
|
82
|
+
path: records_path(limit: 50),
|
|
83
|
+
active: limit == 50
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
icon: "chevron-double-up",
|
|
87
|
+
text: "100",
|
|
88
|
+
method: :get,
|
|
89
|
+
turbo_frame: "records_list",
|
|
90
|
+
path: records_path(limit: 100),
|
|
91
|
+
active: limit == 100
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def records_path(limit: 10)
|
|
101
|
+
databasium.records_records_path(
|
|
102
|
+
table: @table,
|
|
103
|
+
frame_id: "records_list",
|
|
104
|
+
filter: @filter,
|
|
105
|
+
limit: limit
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
class Components::Databasium::Records::ShowTurboStream < Components::Base
|
|
2
|
+
include Phlex::Rails::Helpers::TurboStream
|
|
3
|
+
|
|
4
|
+
def initialize(
|
|
5
|
+
refresh:,
|
|
6
|
+
filter:,
|
|
7
|
+
table:,
|
|
8
|
+
records:,
|
|
9
|
+
model:,
|
|
10
|
+
turbo_frame:,
|
|
11
|
+
pagy:,
|
|
12
|
+
feedback:,
|
|
13
|
+
columns_names_types:,
|
|
14
|
+
limit:
|
|
15
|
+
)
|
|
16
|
+
@filter = filter
|
|
17
|
+
@table = table
|
|
18
|
+
@records = records
|
|
19
|
+
@model = model
|
|
20
|
+
@turbo_frame = turbo_frame
|
|
21
|
+
@pagy = pagy
|
|
22
|
+
@feedback = feedback
|
|
23
|
+
@columns_names_types = columns_names_types
|
|
24
|
+
@limit = limit
|
|
25
|
+
@refresh = refresh
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def view_template
|
|
29
|
+
foreign = @turbo_frame.start_with?("foreign_records_table_")
|
|
30
|
+
target_frame = foreign ? @turbo_frame : "records_list"
|
|
31
|
+
|
|
32
|
+
turbo_stream.replace(
|
|
33
|
+
target_frame,
|
|
34
|
+
Components::Databasium::Records::Table.new(
|
|
35
|
+
records: @records,
|
|
36
|
+
model: @model,
|
|
37
|
+
turbo_frame: target_frame,
|
|
38
|
+
pagy: @pagy,
|
|
39
|
+
feedback: @feedback
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return if foreign
|
|
44
|
+
|
|
45
|
+
turbo_stream.update(
|
|
46
|
+
"header_actions",
|
|
47
|
+
Components::Databasium::Records::HeaderActions.new(filter: nil, table: @table, limit: @limit)
|
|
48
|
+
)
|
|
49
|
+
if @refresh
|
|
50
|
+
turbo_stream.replace(
|
|
51
|
+
"filter",
|
|
52
|
+
Components::Databasium::Records::Filter.new(
|
|
53
|
+
model: @model,
|
|
54
|
+
turbo_frame: @turbo_frame,
|
|
55
|
+
columns_names_types: @columns_names_types,
|
|
56
|
+
hidden: true
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
turbo_stream.replace(
|
|
60
|
+
"addRecord",
|
|
61
|
+
Components::Databasium::Forms::Model.new(
|
|
62
|
+
columns_names_types: @columns_names_types,
|
|
63
|
+
model: @model
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
turbo_stream.replace(
|
|
67
|
+
"records_utilities",
|
|
68
|
+
Components::Databasium::Records::Utilities.new(
|
|
69
|
+
model: @model,
|
|
70
|
+
columns_names_types: @columns_names_types
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Components
|
|
2
|
+
module Databasium
|
|
3
|
+
class Records::Sidebar < Components::Base
|
|
4
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template
|
|
10
|
+
div(class: "flex flex-col w-full") do
|
|
11
|
+
render Components::Databasium::Forms::Search.new(
|
|
12
|
+
url: databasium.sidebar_records_path,
|
|
13
|
+
turbo_frame: "results",
|
|
14
|
+
placeholder: "Search for a table"
|
|
15
|
+
)
|
|
16
|
+
turbo_frame_tag("results", src: databasium.sidebar_records_path) do
|
|
17
|
+
p(class: "mt-2 animate-pulse") { "Loading tables..." }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Components
|
|
2
|
+
module Databasium
|
|
3
|
+
class Records::Table::RecordPanel < Components::Base
|
|
4
|
+
def initialize
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def view_template
|
|
8
|
+
div(class: "flex flex-col min-h-0 items-end") do
|
|
9
|
+
div(
|
|
10
|
+
class:
|
|
11
|
+
"hidden bg-panel flex-1 min-h-0 overflow-y-auto rounded-bl-xl mb-2 min-h-[calc(100dvh-51px)] flex flex-col w-125",
|
|
12
|
+
data: {
|
|
13
|
+
table_target: "recordsPanel"
|
|
14
|
+
},
|
|
15
|
+
id: "editRecord"
|
|
16
|
+
) do
|
|
17
|
+
button(
|
|
18
|
+
class:
|
|
19
|
+
"w-fit text-accent hover:text-accent-dark border-1 border-accent p-1 rounded-xl m-2 ms-auto",
|
|
20
|
+
data: {
|
|
21
|
+
action: "click->table#closeRecordsPanel"
|
|
22
|
+
}
|
|
23
|
+
) { raw heroicon("x-mark", variant: :solid, options: { class: "w-6 h-6" }) }
|
|
24
|
+
div(
|
|
25
|
+
class:
|
|
26
|
+
"flex bg-background px-2 overflow-x-auto max-w-fill divide-x-1 divide-border gap-x-2",
|
|
27
|
+
data: {
|
|
28
|
+
table_target: "recordTabs"
|
|
29
|
+
}
|
|
30
|
+
) do
|
|
31
|
+
template(data: { table_target: "recordTab" }) do
|
|
32
|
+
button(
|
|
33
|
+
type: "button",
|
|
34
|
+
class: "flex items-center gap-2 px-2 cursor-pointer",
|
|
35
|
+
data: {
|
|
36
|
+
action: "click->table#openTab"
|
|
37
|
+
}
|
|
38
|
+
) do
|
|
39
|
+
div(data: { table_target: "recordTabTitle" }) { plain "Name" }
|
|
40
|
+
div(data: { action: "click->table#closeTab" }) do
|
|
41
|
+
raw heroicon(
|
|
42
|
+
"x-mark",
|
|
43
|
+
variant: :solid,
|
|
44
|
+
options: {
|
|
45
|
+
class: "w-3 h-3 me-auto hover:bg-panel"
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
div(class: "m-2", data: { table_target: "recordTabsContent" }) do
|
|
53
|
+
plain "Double click on a record to update to open update form"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
class Components::Databasium::Records::Table::Row < Components::Base
|
|
2
|
+
include Phlex::Rails::Helpers::DOMID
|
|
3
|
+
include Phlex::Rails::Helpers::ClassNames
|
|
4
|
+
|
|
5
|
+
attr_reader :record
|
|
6
|
+
|
|
7
|
+
def initialize(record:, turbo_frame:, render_as_cards: false)
|
|
8
|
+
@record = record
|
|
9
|
+
@turbo_frame = turbo_frame
|
|
10
|
+
@render_as_cards = render_as_cards
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
@render_as_cards ? render_card : render_row
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def click_action
|
|
20
|
+
@turbo_frame == "records_list" ? "click->table#handleClick" : "click->table-select#selectRecord"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render_card
|
|
24
|
+
article(
|
|
25
|
+
id: dom_id(record),
|
|
26
|
+
class:
|
|
27
|
+
"flex flex-col border-1 border-border rounded-md bg-panel " \
|
|
28
|
+
"hover:bg-background hover:cursor-pointer overflow-hidden min-w-0 w-full max-w-125",
|
|
29
|
+
data: {
|
|
30
|
+
action: click_action,
|
|
31
|
+
record_id: record.id
|
|
32
|
+
}
|
|
33
|
+
) { record.class.columns.each { |column| render_card_field(column) } }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render_card_field(column)
|
|
37
|
+
div(
|
|
38
|
+
class:
|
|
39
|
+
"grid grid-cols-2 divide-x divide-border " \
|
|
40
|
+
"border-b-1 border-border last:border-b-0 py-1",
|
|
41
|
+
data: {
|
|
42
|
+
attribute_name: column.name
|
|
43
|
+
}
|
|
44
|
+
) do
|
|
45
|
+
div(
|
|
46
|
+
class: "text-base font-semibold px-2 overflow-x-auto whitespace-nowrap min-w-0 max-w-full"
|
|
47
|
+
) { column.name }
|
|
48
|
+
div(
|
|
49
|
+
class: "text-base font-light px-2 overflow-x-auto whitespace-nowrap min-w-0 max-w-full"
|
|
50
|
+
) { format_cell_value(record.public_send(column.name)) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def render_row
|
|
55
|
+
tr(
|
|
56
|
+
id: dom_id(record),
|
|
57
|
+
class: "hover:bg-background hover:cursor-pointer",
|
|
58
|
+
data: {
|
|
59
|
+
action: click_action,
|
|
60
|
+
record_id: record.id
|
|
61
|
+
}
|
|
62
|
+
) do
|
|
63
|
+
render_checkbox if @turbo_frame == "records_list"
|
|
64
|
+
record.class.columns.each { |column| render_plain_td(column) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def render_checkbox
|
|
69
|
+
td(class: "text-center py-2 border-1 border-border overflow-auto") do
|
|
70
|
+
input(
|
|
71
|
+
type: "checkbox",
|
|
72
|
+
id: "#{record.id}",
|
|
73
|
+
name: "ids[]",
|
|
74
|
+
value: record.id,
|
|
75
|
+
data: {
|
|
76
|
+
table_target: "checkbox"
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def render_plain_td(column)
|
|
83
|
+
td(class: cell_classes, data: { attribute_name: column.name }) do
|
|
84
|
+
plain format_cell_value(record.public_send(column.name))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def format_cell_value(value)
|
|
89
|
+
case value
|
|
90
|
+
when Time, DateTime, ActiveSupport::TimeWithZone
|
|
91
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
|
92
|
+
when Date
|
|
93
|
+
value.strftime("%Y-%m-%d")
|
|
94
|
+
when File
|
|
95
|
+
link_to value.url, value.url, target: "_blank"
|
|
96
|
+
else
|
|
97
|
+
value.to_s
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def cell_classes
|
|
102
|
+
"text-center max-w-15 h-15 p-2 border-1 border-border overflow-auto"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module Components
|
|
2
|
+
module Databasium
|
|
3
|
+
class Records::Table < Components::Base
|
|
4
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
5
|
+
include Phlex::Rails::Helpers::HiddenFieldTag
|
|
6
|
+
include Phlex::Rails::Helpers::CheckBoxTag
|
|
7
|
+
include Phlex::Rails::Helpers::FormWith
|
|
8
|
+
include Phlex::Rails::Helpers::DOMID
|
|
9
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
10
|
+
|
|
11
|
+
def initialize(
|
|
12
|
+
records:,
|
|
13
|
+
model:,
|
|
14
|
+
turbo_frame:,
|
|
15
|
+
pagy: nil,
|
|
16
|
+
feedback: nil,
|
|
17
|
+
render_as_cards: false
|
|
18
|
+
)
|
|
19
|
+
@records = records
|
|
20
|
+
@model = model
|
|
21
|
+
@turbo_frame = turbo_frame
|
|
22
|
+
@pagy = pagy
|
|
23
|
+
@feedback = feedback
|
|
24
|
+
@render_as_cards = render_as_cards
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def view_template
|
|
28
|
+
turbo_frame_tag(@turbo_frame, class: "flex min-h-0 min-w-0 flex-1 flex-col relative") do
|
|
29
|
+
form_with(
|
|
30
|
+
url: databasium.bulk_destroy_records_path,
|
|
31
|
+
data: {
|
|
32
|
+
turbo_method: :destroy,
|
|
33
|
+
action: "submit->table#resetDeleteButton"
|
|
34
|
+
},
|
|
35
|
+
method: :delete,
|
|
36
|
+
scope: :table,
|
|
37
|
+
id: "delete_records_form",
|
|
38
|
+
class: "flex min-h-0 min-w-0 flex-1 flex-col"
|
|
39
|
+
) do |form|
|
|
40
|
+
hidden_field_tag(:table, @model.name)
|
|
41
|
+
div(class: "flex-1 min-h-0 min-w-0 max-h-fit overflow-auto") do
|
|
42
|
+
if @render_as_cards
|
|
43
|
+
render_card_body
|
|
44
|
+
else
|
|
45
|
+
table(class: "whitespace-nowrap bg-panel min-w-max w-full") do
|
|
46
|
+
render_table_head
|
|
47
|
+
render_table_body
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
render_pagy
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def render_table_head
|
|
59
|
+
thead do
|
|
60
|
+
tr(class: "bg-accent shadow-accent") do
|
|
61
|
+
if @turbo_frame == "records_list"
|
|
62
|
+
th(class: table_head_classes) do
|
|
63
|
+
button(
|
|
64
|
+
type: "button",
|
|
65
|
+
data: {
|
|
66
|
+
action: "click->table#toggleAllRecords",
|
|
67
|
+
table_target: "toggleAllRecordsButton"
|
|
68
|
+
},
|
|
69
|
+
class: "px-4 py-1 rounded-xl text-base me-2 hover:text-hover"
|
|
70
|
+
) { heroicon("check-circle", variant: :solid, options: { class: "w-6 h-6" }) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
@model&.columns&.each { |column| th(class: table_head_classes) { plain column.name } }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_card_body
|
|
79
|
+
div(id: "records_body", class: "grid gap-4 w-full p-4 justify-center w-full") do
|
|
80
|
+
if @records&.any?
|
|
81
|
+
@records.each do |record|
|
|
82
|
+
render Components::Databasium::Records::Table::Row.new(
|
|
83
|
+
record: record,
|
|
84
|
+
turbo_frame: @turbo_frame,
|
|
85
|
+
render_as_cards: @render_as_cards
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
render Components::Databasium::Global::Suggestion.new(
|
|
90
|
+
suggestions: [ @feedback || "No records found for #{@model&.name} table." ]
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def render_table_body
|
|
97
|
+
tbody(id: "records_body") do
|
|
98
|
+
if @records&.any?
|
|
99
|
+
@records.each do |record|
|
|
100
|
+
render Components::Databasium::Records::Table::Row.new(
|
|
101
|
+
record: record,
|
|
102
|
+
turbo_frame: @turbo_frame,
|
|
103
|
+
render_as_cards: @render_as_cards
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
render Components::Databasium::Global::Suggestion.new(
|
|
108
|
+
suggestions: [ @feedback || "No records found for #{@model&.name} table." ]
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def render_pagy
|
|
115
|
+
if @pagy && @records&.any?
|
|
116
|
+
div(class: "m-4 flex justify-start") { raw @pagy.series_nav.html_safe }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def table_head_classes
|
|
121
|
+
"text-center w-55 max-w-55 py-2 border-1 border-border overflow-auto"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Components
|
|
4
|
+
module Databasium
|
|
5
|
+
class Records::TableTurboFrame < Components::Base
|
|
6
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
7
|
+
|
|
8
|
+
def initialize(model: nil, feedback: nil)
|
|
9
|
+
@model = model
|
|
10
|
+
@feedback = feedback
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
div(class: "flex min-h-0 min-w-0 flex-1 flex-col") do
|
|
15
|
+
div(id: "records_utilities") { }
|
|
16
|
+
render_table
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def render_table
|
|
23
|
+
turbo_frame_tag("records_list") do
|
|
24
|
+
suggestion =
|
|
25
|
+
if @feedback
|
|
26
|
+
@feedback
|
|
27
|
+
elsif @model
|
|
28
|
+
"No records found for #{@model&.name} table."
|
|
29
|
+
else
|
|
30
|
+
"Please select a table to view records."
|
|
31
|
+
end
|
|
32
|
+
render Components::Databasium::Global::Suggestion.new(suggestions: [ suggestion ])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|