solid_litequeen 0.8.1 → 0.9.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 +4 -4
- data/app/controllers/solid_litequeen/databases_controller.rb +17 -1
- data/app/javascript/controllers/table_controller.js +7 -0
- data/app/javascript/solid_litequeen/controllers/table_controller.js +161 -0
- data/app/views/layouts/solid_litequeen/application.html.erb +2 -0
- data/app/views/solid_litequeen/databases/table_rows.html.erb +14 -7
- data/config/routes.rb +1 -0
- data/lib/solid_litequeen/version.rb +1 -1
- metadata +3 -2
- data/app/javascript/solid_litequeen/controllers/tet_controller.js +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 536ae72fe762f8bfcc75364afa5fa995199f02b6ca43d24f7b97bc118b344731
|
4
|
+
data.tar.gz: 87bcc08b867f182fd988bac2fe9c2eb768b46f12869d6e7bfca2a12d4e68b091
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b57761f2bce146ca25e0d99812cbc30ac182365141156796edfa2a72efab0f4d4644a0a6f37c328719fa4c1db9ce60c4310f74a6b6a9da8ae23b38d74e9f0e0f
|
7
|
+
data.tar.gz: 71d073a29576d62ca4dab1ad01bcfdd9b4b8c0eabe0d3b2bf8b1e4efe1d46fe266eb1b2f0095c1fe887f96c0c320b8f68da4584e9df366fc0f4de03aa7f333fa
|
@@ -61,16 +61,20 @@ module SolidLitequeen
|
|
61
61
|
# Verify the sort column exists in the table to prevent SQL injection
|
62
62
|
valid_columns = DynamicDatabase.connection.columns(@table_name).map(&:name)
|
63
63
|
|
64
|
+
# Use the column order from session if it exists; otherwise, default to all columns
|
65
|
+
ordered_columns = session["#{@database_id}_#{@table_name}_column_order"] || valid_columns
|
66
|
+
|
64
67
|
order_clause = if @sort_column.present? && valid_columns.include?(@sort_column)
|
65
68
|
"#{DynamicDatabase.connection.quote_column_name(@sort_column)} #{@sort_direction}"
|
66
69
|
end
|
67
70
|
|
68
|
-
sql = [ "SELECT
|
71
|
+
sql = [ "SELECT #{ordered_columns.join(', ')} FROM #{@table_name}" ]
|
69
72
|
sql << "ORDER BY #{order_clause}" if order_clause
|
70
73
|
sql << "LIMIT 50"
|
71
74
|
|
72
75
|
|
73
76
|
@data = DynamicDatabase.connection.select_all(sql.join(" "))
|
77
|
+
|
74
78
|
@row_count = row_count = DynamicDatabase.connection.select_value("SELECT COUNT(*) FROM #{@table_name}").to_i
|
75
79
|
end
|
76
80
|
|
@@ -93,5 +97,17 @@ module SolidLitequeen
|
|
93
97
|
type: "application/x-sqlite3",
|
94
98
|
disposition: "attachment"
|
95
99
|
end
|
100
|
+
|
101
|
+
def set_column_order
|
102
|
+
table_name = params[:table]
|
103
|
+
database_id = params[:database_id]
|
104
|
+
|
105
|
+
column_order = params[:columnOrder]
|
106
|
+
|
107
|
+
# Store column order in session using database and table as key
|
108
|
+
session["#{database_id}_#{table_name}_column_order"] = column_order
|
109
|
+
|
110
|
+
head :ok
|
111
|
+
end
|
96
112
|
end
|
97
113
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
// Connects to data-controller="table"
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = [];
|
6
|
+
// We'll store the dragged element here
|
7
|
+
draggedTh = null;
|
8
|
+
|
9
|
+
connect() {
|
10
|
+
this.element.addEventListener(
|
11
|
+
"dragstart",
|
12
|
+
this.handleThDragstart.bind(this),
|
13
|
+
);
|
14
|
+
this.element.addEventListener("dragover", this.handleThDragover.bind(this));
|
15
|
+
this.element.addEventListener("drop", this.handleThDrop.bind(this));
|
16
|
+
this.element.addEventListener("dragend", this.handleThDragend.bind(this));
|
17
|
+
this.element.addEventListener(
|
18
|
+
"dragenter",
|
19
|
+
this.handleThDragenter.bind(this),
|
20
|
+
);
|
21
|
+
this.element.addEventListener(
|
22
|
+
"dragleave",
|
23
|
+
this.handleThDragleave.bind(this),
|
24
|
+
);
|
25
|
+
}
|
26
|
+
|
27
|
+
disconnect() {
|
28
|
+
this.element.removeEventListener("dragstart", this.handleThDragstart);
|
29
|
+
|
30
|
+
this.element.removeEventListener("dragover", this.handleThDragover);
|
31
|
+
|
32
|
+
this.element.removeEventListener("drop", this.handleThDrop);
|
33
|
+
|
34
|
+
this.element.removeEventListener("dragend", this.handleThDragend);
|
35
|
+
this.element.removeEventListener("dragenter", this.handleThDragenter);
|
36
|
+
this.element.removeEventListener("dragleave", this.handleThDragleave);
|
37
|
+
}
|
38
|
+
|
39
|
+
handleThDragstart(e) {
|
40
|
+
// Only allow dragging if the target is a table header
|
41
|
+
if (!e.target.matches("th")) {
|
42
|
+
e.preventDefault();
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
|
46
|
+
// Save the dragged header element
|
47
|
+
this.draggedTh = e.target;
|
48
|
+
|
49
|
+
this.draggedTh.setAttribute("data-is-dragging", true);
|
50
|
+
|
51
|
+
e.dataTransfer.effectAllowed = "move";
|
52
|
+
|
53
|
+
// Some browsers require data to be set in order for dragging to work
|
54
|
+
e.dataTransfer.setData("text/plain", "");
|
55
|
+
}
|
56
|
+
|
57
|
+
handleThDragover(e) {
|
58
|
+
const targetTh = e.target.closest("th");
|
59
|
+
// Only allow dragging if the target is a table header
|
60
|
+
if (!targetTh) {
|
61
|
+
e.preventDefault();
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
|
65
|
+
e.preventDefault();
|
66
|
+
|
67
|
+
// Show a move indicator
|
68
|
+
e.dataTransfer.dropEffect = "move";
|
69
|
+
|
70
|
+
this.draggedTh.removeAttribute("data-is-dragging");
|
71
|
+
}
|
72
|
+
|
73
|
+
handleThDrop(e) {
|
74
|
+
const targetTh = e.target.closest("th");
|
75
|
+
|
76
|
+
// Only handle drop if the target is a table header and we have a dragged header
|
77
|
+
if (!targetTh || !this.draggedTh) {
|
78
|
+
e.preventDefault();
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
e.preventDefault();
|
82
|
+
|
83
|
+
// Swap the positions of the dragged header and the target header
|
84
|
+
const parent = targetTh.parentElement;
|
85
|
+
// Insert the dragged header before the target header
|
86
|
+
parent.insertBefore(this.draggedTh, targetTh);
|
87
|
+
|
88
|
+
const headers = Array.from(parent.querySelectorAll("th"));
|
89
|
+
const columnOrder = headers.map((th) => {
|
90
|
+
// You might want to use data attributes to store column identifiers
|
91
|
+
return th.dataset.columnName;
|
92
|
+
});
|
93
|
+
|
94
|
+
const token = document.querySelector('meta[name="csrf-token"]').content;
|
95
|
+
|
96
|
+
fetch(this.element.dataset.setTableOrderPath, {
|
97
|
+
method: "POST",
|
98
|
+
headers: {
|
99
|
+
"Content-Type": "application/json",
|
100
|
+
"X-CSRF-Token": token,
|
101
|
+
},
|
102
|
+
body: JSON.stringify({ columnOrder }),
|
103
|
+
}).then((result) => {
|
104
|
+
if (result.ok) {
|
105
|
+
// Get all rows in the table body
|
106
|
+
const tbody = this.element.querySelector("tbody");
|
107
|
+
const rows = Array.from(tbody.querySelectorAll("tr"));
|
108
|
+
|
109
|
+
// Reorder cells in each row to match new column order
|
110
|
+
for (const row of rows) {
|
111
|
+
const cells = Array.from(row.querySelectorAll("td"));
|
112
|
+
const reorderedCells = columnOrder.map((colName) => {
|
113
|
+
// Find cell with matching data-column attribute
|
114
|
+
return cells.find((cell) => cell.dataset.column === colName);
|
115
|
+
});
|
116
|
+
|
117
|
+
// Clear row and append cells in new order
|
118
|
+
row.innerHTML = "";
|
119
|
+
for (const cell of reorderedCells) {
|
120
|
+
row.appendChild(cell);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
});
|
125
|
+
|
126
|
+
targetTh.removeAttribute("data-column-order-about-to-be-swapped");
|
127
|
+
}
|
128
|
+
|
129
|
+
handleThDragend(e) {
|
130
|
+
// Only allow dragging if the target is a table header
|
131
|
+
if (!e.target.matches("th")) {
|
132
|
+
e.preventDefault();
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
|
136
|
+
// Reset the dragged header
|
137
|
+
this.draggedTh = null;
|
138
|
+
}
|
139
|
+
|
140
|
+
handleThDragenter(e) {
|
141
|
+
const targetTh = e.target.closest("th");
|
142
|
+
// If there's no valid target or the target is the dragged header itself, do nothing
|
143
|
+
if (!targetTh || targetTh === this.draggedTh) {
|
144
|
+
return;
|
145
|
+
}
|
146
|
+
|
147
|
+
e.preventDefault();
|
148
|
+
|
149
|
+
targetTh.setAttribute("data-column-order-about-to-be-swapped", true);
|
150
|
+
}
|
151
|
+
|
152
|
+
handleThDragleave(e) {
|
153
|
+
const targetTh = e.target.closest("th");
|
154
|
+
if (!targetTh) {
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
e.preventDefault();
|
158
|
+
|
159
|
+
targetTh.removeAttribute("data-column-order-about-to-be-swapped");
|
160
|
+
}
|
161
|
+
}
|
@@ -5,6 +5,8 @@
|
|
5
5
|
<%= csrf_meta_tags %>
|
6
6
|
<%= csp_meta_tag %>
|
7
7
|
|
8
|
+
<meta name="turbo-refresh-method" content="morph">
|
9
|
+
<meta name="turbo-refresh-scroll" content="preserve">
|
8
10
|
|
9
11
|
<%= stylesheet_link_tag "solid_litequeen/application", media: "all", "data-turbo-track": "reload" %>
|
10
12
|
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
@@ -15,13 +15,19 @@
|
|
15
15
|
<p class="text-gray-600"><%= pluralize(@row_count, "row") %> found</p>
|
16
16
|
</div>
|
17
17
|
|
18
|
+
|
18
19
|
<div class="bg-white rounded-lg shadow overflow-x-auto">
|
19
20
|
<div class="min-w-full inline-block align-middle">
|
20
|
-
<table class="min-w-full relative">
|
21
|
+
<table data-controller="table" data-set-table-order-path="<%= database_set_table_column_order_path(params[:database_id], @table_name) %>" class="min-w-full relative">
|
21
22
|
<thead class="">
|
22
23
|
<tr class="bg-gray-100 border-b border-gray-200">
|
23
|
-
<% @data.columns.
|
24
|
-
<th
|
24
|
+
<% @data.columns.each_with_index do |column, index| %>
|
25
|
+
<th
|
26
|
+
draggable="true"
|
27
|
+
data-column-index="<%= index %>"
|
28
|
+
data-column-name="<%= column %>"
|
29
|
+
class="hover:cursor-move px-6 py-3 text-left text-sm font-medium text-gray-700 whitespace-nowrap data-[is-dragging]:bg-orange-300/30 data-[column-order-about-to-be-swapped]:bg-green-300/30"
|
30
|
+
>
|
25
31
|
<%#= column %>
|
26
32
|
|
27
33
|
<%= link_to column,
|
@@ -30,19 +36,20 @@
|
|
30
36
|
table: @table_name,
|
31
37
|
sort_column: column,
|
32
38
|
sort_direction: (@sort_column == column && @sort_direction == 'DESC') ? 'ASC' : 'DESC'
|
33
|
-
) %>
|
39
|
+
), class: "" %>
|
34
40
|
<%= '▼' if @sort_column == column && @sort_direction == 'DESC' %>
|
35
41
|
<%= '▲' if @sort_column == column && @sort_direction == 'ASC' %>
|
36
42
|
</th>
|
37
43
|
<% end %>
|
38
44
|
</tr>
|
39
45
|
</thead>
|
46
|
+
|
40
47
|
<tbody class="divide-y divide-gray-200">
|
41
48
|
<% @data.rows.each do |row| %>
|
42
|
-
<tr class="hover:bg-gray-50">
|
43
|
-
<% row.
|
49
|
+
<tr class="hover:bg-gray-50" >
|
50
|
+
<% row.each_with_index do |item, index| %>
|
44
51
|
<% truncated_item = item&.truncate(80) %>
|
45
|
-
<td class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap" >
|
52
|
+
<td data-column="<%= @data.columns[index] %>" class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap" >
|
46
53
|
|
47
54
|
<div class="flex justify-between">
|
48
55
|
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
SolidLitequeen::Engine.routes.draw do
|
2
2
|
resources :databases, only: [ :index, :show ] do
|
3
3
|
get "/tables/:table", to: "databases#table_rows", as: :table_rows
|
4
|
+
post "/tables/:table/set-column-order", to: "databases#set_column_order", as: :set_table_column_order
|
4
5
|
get "download", to: "databases#download", as: "download"
|
5
6
|
end
|
6
7
|
root to: "databases#index"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_litequeen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vik Borges
|
@@ -157,10 +157,11 @@ files:
|
|
157
157
|
- app/controllers/solid_litequeen/databases_controller.rb
|
158
158
|
- app/helpers/solid_litequeen/application_helper.rb
|
159
159
|
- app/helpers/solid_litequeen/databases_helper.rb
|
160
|
+
- app/javascript/controllers/table_controller.js
|
160
161
|
- app/javascript/solid_litequeen/application.js
|
161
162
|
- app/javascript/solid_litequeen/controllers/application.js
|
162
163
|
- app/javascript/solid_litequeen/controllers/index.js
|
163
|
-
- app/javascript/solid_litequeen/controllers/
|
164
|
+
- app/javascript/solid_litequeen/controllers/table_controller.js
|
164
165
|
- app/jobs/solid_litequeen/application_job.rb
|
165
166
|
- app/mailers/solid_litequeen/application_mailer.rb
|
166
167
|
- app/models/solid_litequeen/application_record.rb
|