solid_litequeen 0.8.1 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f307bf9d9eb772756d40395b38cef8f0f0a9cd905e1e0caff872855d9bedf3a6
4
- data.tar.gz: 0a0a6528451d67eede41bf3a967a87aebf78da47e07adb4ebc8fab6e20b36d12
3
+ metadata.gz: 2193fbf74ea85b0636eacfaf274db08f75fc6e3df3f20ff92092f332665cc5ae
4
+ data.tar.gz: 1916c89aa9c9091fd562535e1f415f8f3844e7b686241ecd5ca19800bf5a82c6
5
5
  SHA512:
6
- metadata.gz: ed7ae24cd435790f31d182634a344202d6f5c839cfedae4d41660113d13b394168fb4426fc6916aeb02d2ba81d2e06f9c208f42608cea508d5b650dc39d67ee4
7
- data.tar.gz: 926ec78a8f509413478913ae70fde680992dc353a362250808125c5cac7b8e3c1d69c482dc987fc9f260763a9a67f8427ed1b7645e4a70a99ba34f51d13a2ac5
6
+ metadata.gz: ff33367977208e249b3d4eb19a1a0ab4aba4744868dd8c40a52375fe48535ba9eacb2f145c38f58fae57041fc5322ae0047c9cf9038a49568f277358d78b7579
7
+ data.tar.gz: 7da74f483615c35208adf2fa98673896e74d45ed31c64ba6f040aad43637832ec0c2c254d6d3542983892b0bdfd2674df22d01709b854996fa6269ff789af95a
@@ -49,8 +49,6 @@ module SolidLitequeen
49
49
  @sort_column = sort_prefs["sort_column"]
50
50
  @sort_direction = sort_prefs["sort_direction"]
51
51
 
52
- # debugger
53
-
54
52
  @database_location = Base64.urlsafe_decode64(@database_id)
55
53
 
56
54
  DynamicDatabase.establish_connection(
@@ -58,19 +56,37 @@ module SolidLitequeen
58
56
  database: @database_location
59
57
  )
60
58
 
59
+ table_columns = DynamicDatabase.connection.columns(@table_name)
60
+
61
+ @columns_info = table_columns.each_with_object({}) do |column, hash|
62
+ hash[column.name] = {
63
+ sql_type: column.sql_type_metadata.sql_type,
64
+ type: column.sql_type_metadata.type,
65
+ limit: column.sql_type_metadata.limit,
66
+ precision: column.sql_type_metadata.precision,
67
+ scale: column.sql_type_metadata.scale,
68
+ null: column.null,
69
+ default: column.default
70
+ }
71
+ end
72
+
61
73
  # Verify the sort column exists in the table to prevent SQL injection
62
- valid_columns = DynamicDatabase.connection.columns(@table_name).map(&:name)
74
+ valid_columns = table_columns.map(&:name)
75
+
76
+ # Use the column order from session if it exists; otherwise, default to all columns
77
+ ordered_columns = session["#{@database_id}_#{@table_name}_column_order"] || valid_columns
63
78
 
64
79
  order_clause = if @sort_column.present? && valid_columns.include?(@sort_column)
65
80
  "#{DynamicDatabase.connection.quote_column_name(@sort_column)} #{@sort_direction}"
66
81
  end
67
82
 
68
- sql = [ "SELECT * FROM #{@table_name}" ]
83
+ sql = [ "SELECT #{ordered_columns.join(', ')} FROM #{@table_name}" ]
69
84
  sql << "ORDER BY #{order_clause}" if order_clause
70
85
  sql << "LIMIT 50"
71
86
 
72
87
 
73
88
  @data = DynamicDatabase.connection.select_all(sql.join(" "))
89
+
74
90
  @row_count = row_count = DynamicDatabase.connection.select_value("SELECT COUNT(*) FROM #{@table_name}").to_i
75
91
  end
76
92
 
@@ -93,5 +109,17 @@ module SolidLitequeen
93
109
  type: "application/x-sqlite3",
94
110
  disposition: "attachment"
95
111
  end
112
+
113
+ def set_column_order
114
+ table_name = params[:table]
115
+ database_id = params[:database_id]
116
+
117
+ column_order = params[:columnOrder]
118
+
119
+ # Store column order in session using database and table as key
120
+ session["#{database_id}_#{table_name}_column_order"] = column_order
121
+
122
+ head :ok
123
+ end
96
124
  end
97
125
  end
@@ -0,0 +1,198 @@
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
+ this.#load_datetime_local_title();
27
+ }
28
+
29
+ disconnect() {
30
+ this.element.removeEventListener("dragstart", this.handleThDragstart);
31
+
32
+ this.element.removeEventListener("dragover", this.handleThDragover);
33
+
34
+ this.element.removeEventListener("drop", this.handleThDrop);
35
+
36
+ this.element.removeEventListener("dragend", this.handleThDragend);
37
+ this.element.removeEventListener("dragenter", this.handleThDragenter);
38
+ this.element.removeEventListener("dragleave", this.handleThDragleave);
39
+ }
40
+
41
+ handleThDragstart(e) {
42
+ // Only allow dragging if the target is a table header
43
+ if (!e.target.matches("th")) {
44
+ e.preventDefault();
45
+ return;
46
+ }
47
+
48
+ // Save the dragged header element
49
+ this.draggedTh = e.target;
50
+
51
+ this.draggedTh.setAttribute("data-is-dragging", true);
52
+
53
+ e.dataTransfer.effectAllowed = "move";
54
+
55
+ // Some browsers require data to be set in order for dragging to work
56
+ e.dataTransfer.setData("text/plain", "");
57
+ }
58
+
59
+ handleThDragover(e) {
60
+ const targetTh = e.target.closest("th");
61
+ // Only allow dragging if the target is a table header
62
+ if (!targetTh) {
63
+ e.preventDefault();
64
+ return;
65
+ }
66
+
67
+ e.preventDefault();
68
+
69
+ // Show a move indicator
70
+ e.dataTransfer.dropEffect = "move";
71
+
72
+ this.draggedTh.removeAttribute("data-is-dragging");
73
+ }
74
+
75
+ handleThDrop(e) {
76
+ const targetTh = e.target.closest("th");
77
+
78
+ // Only handle drop if the target is a table header and we have a dragged header
79
+ if (!targetTh || !this.draggedTh) {
80
+ e.preventDefault();
81
+ return;
82
+ }
83
+ e.preventDefault();
84
+
85
+ // Swap the positions of the dragged header and the target header
86
+ const parent = targetTh.parentElement;
87
+ // Insert the dragged header before the target header
88
+ parent.insertBefore(this.draggedTh, targetTh);
89
+
90
+ const headers = Array.from(parent.querySelectorAll("th"));
91
+ const columnOrder = headers.map((th) => {
92
+ // You might want to use data attributes to store column identifiers
93
+ return th.dataset.columnName;
94
+ });
95
+
96
+ const token = document.querySelector('meta[name="csrf-token"]').content;
97
+
98
+ fetch(this.element.dataset.setTableOrderPath, {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "X-CSRF-Token": token,
103
+ },
104
+ body: JSON.stringify({ columnOrder }),
105
+ }).then((result) => {
106
+ if (result.ok) {
107
+ // Get all rows in the table body
108
+ const tbody = this.element.querySelector("tbody");
109
+ const rows = Array.from(tbody.querySelectorAll("tr"));
110
+
111
+ // Reorder cells in each row to match new column order
112
+ for (const row of rows) {
113
+ const cells = Array.from(row.querySelectorAll("td"));
114
+ const reorderedCells = columnOrder.map((colName) => {
115
+ // Find cell with matching data-column attribute
116
+ return cells.find((cell) => cell.dataset.column === colName);
117
+ });
118
+
119
+ // Clear row and append cells in new order
120
+ row.innerHTML = "";
121
+ for (const cell of reorderedCells) {
122
+ row.appendChild(cell);
123
+ }
124
+ }
125
+ }
126
+ });
127
+
128
+ targetTh.removeAttribute("data-column-order-about-to-be-swapped");
129
+ }
130
+
131
+ handleThDragend(e) {
132
+ // Only allow dragging if the target is a table header
133
+ if (!e.target.matches("th")) {
134
+ e.preventDefault();
135
+ return;
136
+ }
137
+
138
+ // Reset the dragged header
139
+ this.draggedTh = null;
140
+ }
141
+
142
+ handleThDragenter(e) {
143
+ const targetTh = e.target.closest("th");
144
+ // If there's no valid target or the target is the dragged header itself, do nothing
145
+ if (!targetTh || targetTh === this.draggedTh) {
146
+ return;
147
+ }
148
+
149
+ e.preventDefault();
150
+
151
+ targetTh.setAttribute("data-column-order-about-to-be-swapped", true);
152
+ }
153
+
154
+ handleThDragleave(e) {
155
+ const targetTh = e.target.closest("th");
156
+ if (!targetTh) {
157
+ return;
158
+ }
159
+ e.preventDefault();
160
+
161
+ targetTh.removeAttribute("data-column-order-about-to-be-swapped");
162
+ }
163
+
164
+ #load_datetime_local_title() {
165
+ setTimeout(() => {
166
+ const datetime_items = this.element.querySelectorAll(
167
+ 'td[data-data_type="datetime"]',
168
+ );
169
+
170
+ for (const item of datetime_items) {
171
+ const column_item_datetime = item.querySelector(
172
+ "span[data-column_item]",
173
+ )?.textContent;
174
+
175
+ if (column_item_datetime) {
176
+ try {
177
+ // Parse the UTC datetime string
178
+ const utcDate = new Date(`${column_item_datetime.trim()}Z`);
179
+
180
+ // Format to local datetime using Intl.DateTimeFormat
181
+ const localDatetime = new Intl.DateTimeFormat("default", {
182
+ year: "numeric",
183
+ month: "short",
184
+ day: "numeric",
185
+ hour: "numeric",
186
+ minute: "numeric",
187
+ second: "numeric",
188
+ }).format(utcDate);
189
+
190
+ item.setAttribute("title", `${localDatetime} local time.`);
191
+ } catch (error) {
192
+ console.error("Error converting datetime:", error);
193
+ }
194
+ }
195
+ }
196
+ }, 2000);
197
+ }
198
+ }
@@ -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,20 @@
15
15
  <p class="text-gray-600"><%= pluralize(@row_count, "row") %> found</p>
16
16
  </div>
17
17
 
18
+ <%= console %>
19
+
18
20
  <div class="bg-white rounded-lg shadow overflow-x-auto">
19
21
  <div class="min-w-full inline-block align-middle">
20
- <table class="min-w-full relative">
22
+ <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
23
  <thead class="">
22
24
  <tr class="bg-gray-100 border-b border-gray-200">
23
- <% @data.columns.each do |column| %>
24
- <th class="px-6 py-3 text-left text-sm font-medium text-gray-700 whitespace-nowrap">
25
+ <% @data.columns.each_with_index do |column, index| %>
26
+ <th
27
+ draggable="true"
28
+ data-column-index="<%= index %>"
29
+ data-column-name="<%= column %>"
30
+ 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"
31
+ >
25
32
  <%#= column %>
26
33
 
27
34
  <%= link_to column,
@@ -30,23 +37,29 @@
30
37
  table: @table_name,
31
38
  sort_column: column,
32
39
  sort_direction: (@sort_column == column && @sort_direction == 'DESC') ? 'ASC' : 'DESC'
33
- ) %>
40
+ ), class: "" %>
34
41
  <%= '▼' if @sort_column == column && @sort_direction == 'DESC' %>
35
42
  <%= '▲' if @sort_column == column && @sort_direction == 'ASC' %>
36
43
  </th>
37
44
  <% end %>
38
45
  </tr>
39
46
  </thead>
47
+
40
48
  <tbody class="divide-y divide-gray-200">
41
49
  <% @data.rows.each do |row| %>
42
- <tr class="hover:bg-gray-50">
43
- <% row.each do |item| %>
50
+ <tr class="hover:bg-gray-50" >
51
+ <% row.each_with_index do |item, index| %>
44
52
  <% truncated_item = item&.truncate(80) %>
45
- <td class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap" >
53
+ <% column_name = @data.columns[index] %>
54
+ <td
55
+ data-column="<%= column_name %>"
56
+ data-data_type="<%= @columns_info.dig(column_name).dig(:type) %>"
57
+ class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap"
58
+ >
46
59
 
47
60
  <div class="flex justify-between">
48
61
 
49
- <span><%= truncated_item %></span>
62
+ <span data-column_item><%= truncated_item %></span>
50
63
 
51
64
  <% if truncated_item&.to_s&.ends_with?("...") %>
52
65
  <span title="<%= item %>" class="cursor-pointer size-4">
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"
@@ -1,3 +1,3 @@
1
1
  module SolidLitequeen
2
- VERSION = "0.8.1"
2
+ VERSION = "0.10.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_litequeen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vik Borges
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-10 00:00:00.000000000 Z
11
+ date: 2025-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -160,7 +160,7 @@ files:
160
160
  - app/javascript/solid_litequeen/application.js
161
161
  - app/javascript/solid_litequeen/controllers/application.js
162
162
  - app/javascript/solid_litequeen/controllers/index.js
163
- - app/javascript/solid_litequeen/controllers/tet_controller.js
163
+ - app/javascript/solid_litequeen/controllers/table_controller.js
164
164
  - app/jobs/solid_litequeen/application_job.rb
165
165
  - app/mailers/solid_litequeen/application_mailer.rb
166
166
  - app/models/solid_litequeen/application_record.rb
@@ -1,8 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- // Connects to data-controller="tet"
4
- export default class extends Controller {
5
- connect() {
6
- console.log("hello potato");
7
- }
8
- }