solid_litequeen 0.11.1 → 0.13.1

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: 90243fc8ba17fd413d9237ad0f4d6361f73f9dbf1497b5383ffadf5f484a67e1
4
- data.tar.gz: f0e9b68b711aa9c2866cfcab0117d34baf3199413fc0772d7946f62059dd3a00
3
+ metadata.gz: 52bfc6916fed8002c0b9151366f26b381ebf6a24209457fe6e77bf2035778cc4
4
+ data.tar.gz: 379b3b67db8b7f081c70567c2e86427a3307e99f673380ca61a51c47415f5202
5
5
  SHA512:
6
- metadata.gz: a202ac2d37b116b661e89b54e25751a9459647d4f3fc52eb152d79c063e932b7c5fe1c4add8010caee6af07f392781ef1a773ed4ad3a5b1c3a180250050a7b2a
7
- data.tar.gz: c8faf1143ed4208d12f302553a34a8f2b132e784d9921380c5b2b4b192989581a069397b996de15c3139836156665a478efd7a8f4980008fcec377ecd8415625
6
+ metadata.gz: 1c981a17c29cf6d884644a989e15f96f495f0cd93a2a050ebac3ecf7ff1092035f24069100f008454364a57359d19c219b58c9433549a5563ed50bfe024b6c5e
7
+ data.tar.gz: f7edab2c355f6870cd4a86aeec2800119bbffbf57a7172e8ba49ef0152a2f897f64d7927ad353a9a2498f845d54309c837fe4c3aa9244a746d547e6c815682fc
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-ellipsis"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-spline"><circle cx="19" cy="5" r="2"></circle><circle cx="5" cy="19" r="2"></circle><path d="M5 17A12 12 0 0 1 17 5"></path></svg>
@@ -93,8 +93,17 @@ module SolidLitequeen
93
93
 
94
94
  table_columns = DynamicDatabase.connection.columns(@table_name)
95
95
 
96
+ foreign_keys = DynamicDatabase.connection.foreign_keys(@table_name)
97
+
98
+ # Build a mapping from column name to its foreign key details
99
+ fk_info = {}
100
+ foreign_keys.each do |fk|
101
+ # Depending on your Rails version, you might access these properties as below:
102
+ fk_info[fk.column] = { to_table: fk.to_table, primary_key: fk.primary_key }
103
+ end
104
+
96
105
  @columns_info = table_columns.each_with_object({}) do |column, hash|
97
- hash[column.name] = {
106
+ info = {
98
107
  sql_type: column.sql_type_metadata.sql_type,
99
108
  type: column.sql_type_metadata.type,
100
109
  limit: column.sql_type_metadata.limit,
@@ -103,6 +112,12 @@ module SolidLitequeen
103
112
  null: column.null,
104
113
  default: column.default
105
114
  }
115
+
116
+ # Append foreign key info if available for this column
117
+ if fk_info[column.name]
118
+ info[:foreign_key] = fk_info[column.name]
119
+ end
120
+ hash[column.name] = info
106
121
  end
107
122
 
108
123
  # Verify the sort column exists in the table to prevent SQL injection
@@ -170,5 +185,28 @@ module SolidLitequeen
170
185
 
171
186
  head :ok
172
187
  end
188
+
189
+ def get_foreign_key_data
190
+ @database_id = params.expect(:database_id)
191
+ @table_name = params.expect(:table)
192
+ @target_table = params.expect(:target_table)
193
+ @target_field = params.expect(:target_field)
194
+ @target_field_value = params.expect(:target_field_value)
195
+
196
+ @database_location = Base64.urlsafe_decode64(@database_id)
197
+
198
+ DynamicDatabase.establish_connection(
199
+ adapter: "sqlite3",
200
+ database: @database_location
201
+ )
202
+
203
+
204
+ # Query the target table for the record matching the foreign key value
205
+ query = "SELECT * FROM #{@target_table} WHERE #{@target_field} = ? LIMIT 1"
206
+ @result = DynamicDatabase.connection.exec_query(query, "SQL", [ @target_field_value ])
207
+
208
+
209
+ render partial: "foreign-key-data"
210
+ end
173
211
  end
174
212
  end
@@ -0,0 +1,37 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="dialog"
4
+ export default class extends Controller {
5
+ connect() {
6
+ // this.element === dialog
7
+ this.element.addEventListener(
8
+ "click",
9
+ this.handleClickOusideDialog.bind(this),
10
+ );
11
+ }
12
+
13
+ disconnect() {
14
+ this.element.removeEventListener(
15
+ "click",
16
+ this.handleClickOusideDialog.bind(this),
17
+ );
18
+
19
+ this.element?.close();
20
+ }
21
+
22
+ /**
23
+ * Handles mouse events for the search dialog.
24
+ * @param {MouseEvent} event - The mouse event triggered by user interaction.
25
+ */
26
+ handleClickOusideDialog(event) {
27
+ const rect = this.element.getBoundingClientRect();
28
+ const isInDialog =
29
+ rect.top <= event.clientY &&
30
+ event.clientY <= rect.top + rect.height &&
31
+ rect.left <= event.clientX &&
32
+ event.clientX <= rect.left + rect.width;
33
+ if (!isInDialog) {
34
+ this.element?.close();
35
+ }
36
+ }
37
+ }
@@ -161,6 +161,43 @@ export default class extends Controller {
161
161
  targetTh.removeAttribute("data-column-order-about-to-be-swapped");
162
162
  }
163
163
 
164
+ /**
165
+ *
166
+ * @param {MouseEvent} e
167
+ * @returns
168
+ */
169
+ load_foreign_key_data(e) {
170
+ const foreign_key_data_dialog = document.querySelector(
171
+ "dialog#foreign-key-data",
172
+ );
173
+
174
+ const foreign_key_data_frame = document.querySelector(
175
+ "#foreign-key-data-frame",
176
+ );
177
+
178
+ const fk_data_dialog_button = e.currentTarget;
179
+ const fk_target_table = fk_data_dialog_button.dataset.fk_target_table;
180
+ const fk_target_field = fk_data_dialog_button.dataset.fk_target_field;
181
+ const fk_target_field_value =
182
+ fk_data_dialog_button.dataset.fk_target_field_value;
183
+
184
+ const new_frame_src = `${this.element.dataset.database_table_path}/foreign-key-data/${fk_target_table}/${fk_target_field}/${fk_target_field_value}`;
185
+
186
+ // if the src didn't change let's just show what we have
187
+ if (foreign_key_data_frame.src) {
188
+ const frame_src_current_path = new URL(foreign_key_data_frame.src)
189
+ ?.pathname;
190
+
191
+ if (new_frame_src === frame_src_current_path) {
192
+ foreign_key_data_dialog.showModal();
193
+ return;
194
+ }
195
+ }
196
+
197
+ foreign_key_data_frame.src = new_frame_src;
198
+ foreign_key_data_dialog.showModal();
199
+ }
200
+
164
201
  #load_datetime_local_title() {
165
202
  setTimeout(() => {
166
203
  const datetime_items = this.element.querySelectorAll(
@@ -18,6 +18,12 @@
18
18
  @theme {
19
19
  --color-clifford: #da373d;
20
20
  }
21
+
22
+ @utility filter-blue{
23
+ filter: invert(32%) sepia(65%) saturate(6380%) hue-rotate(219deg) brightness(98%) contrast(102%);
24
+ }
25
+
26
+
21
27
  </style>
22
28
 
23
29
 
@@ -0,0 +1,33 @@
1
+ <!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
2
+ <svg width="120" height="30" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="<%= local_assigns[:class] %>">
3
+ <circle cx="15" cy="15" r="15">
4
+ <animate attributeName="r" from="15" to="15"
5
+ begin="0s" dur="0.8s"
6
+ values="15;9;15" calcMode="linear"
7
+ repeatCount="indefinite" />
8
+ <animate attributeName="fill-opacity" from="1" to="1"
9
+ begin="0s" dur="0.8s"
10
+ values="1;.5;1" calcMode="linear"
11
+ repeatCount="indefinite" />
12
+ </circle>
13
+ <circle cx="60" cy="15" r="9" fill-opacity="0.3">
14
+ <animate attributeName="r" from="9" to="9"
15
+ begin="0s" dur="0.8s"
16
+ values="9;15;9" calcMode="linear"
17
+ repeatCount="indefinite" />
18
+ <animate attributeName="fill-opacity" from="0.5" to="0.5"
19
+ begin="0s" dur="0.8s"
20
+ values=".5;1;.5" calcMode="linear"
21
+ repeatCount="indefinite" />
22
+ </circle>
23
+ <circle cx="105" cy="15" r="15">
24
+ <animate attributeName="r" from="15" to="15"
25
+ begin="0s" dur="0.8s"
26
+ values="15;9;15" calcMode="linear"
27
+ repeatCount="indefinite" />
28
+ <animate attributeName="fill-opacity" from="1" to="1"
29
+ begin="0s" dur="0.8s"
30
+ values="1;.5;1" calcMode="linear"
31
+ repeatCount="indefinite" />
32
+ </circle>
33
+ </svg>
@@ -0,0 +1,38 @@
1
+ <turbo-frame id="foreign-key-data-frame" >
2
+
3
+
4
+ <h1 class="text-lg text-center">
5
+ <span> <%= @table_name %></span>
6
+ <span> &gt;</span>
7
+ <span class="font-semibold"><%= @target_table %></span>
8
+
9
+ </h1>
10
+
11
+ <div class="mx-auto my-4 p-4 max-w-[90%]">
12
+ <table class="min-w-full divide-y divide-gray-200 border border-gray-200">
13
+ <thead class="bg-gray-50">
14
+ <tr>
15
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
16
+ Column Name
17
+ </th>
18
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
19
+ Value
20
+ </th>
21
+ </tr>
22
+ </thead>
23
+ <tbody class="bg-white divide-y divide-gray-200">
24
+ <% @result.rows.each do |row| %>
25
+ <% @result.columns.zip(row).each do |column, value| %>
26
+ <tr>
27
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 font-semibold"><%= column %></td>
28
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 in-aria-busy:blur"><%= value&.truncate(80) %></td>
29
+ </tr>
30
+ <% end %>
31
+ <% end %>
32
+
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+
37
+
38
+ </turbo-frame>
@@ -0,0 +1,15 @@
1
+ <dialog id="<%= dialog_id %>" data-controller="dialog" class="w-[800px] m-auto overscroll-y-contain">
2
+ <form method="submit" class="flex flex-row-reverse">
3
+ <button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
4
+ <%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
5
+ </button>
6
+ </form>
7
+
8
+ <h1 class="text-lg font-semibold text-center"><%= column_name %></h1>
9
+
10
+ <div class="flex items-center max-w-[90%] h-80 max-h-80 mx-auto my-4 p-2 bg-gray-100 rounded ">
11
+ <p class="text-wrap overflow-auto w-[inherit] h-[inherit] p-2">
12
+ <%= data %>
13
+ </p>
14
+ </div>
15
+ </dialog>
@@ -1,4 +1,4 @@
1
- <dialog id="table_relationships" data-controller="table-relations" data-relations="<%= @table_relations.to_json %>" class="w-[1000px] h-full m-auto overscroll-y-contain">
1
+ <dialog id="table_relationships" data-controller="dialog table-relations" data-relations="<%= @table_relations.to_json %>" class="w-[1000px] h-full m-auto overscroll-y-contain">
2
2
  <form method="submit" class="flex flex-row-reverse">
3
3
  <button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
4
4
  <%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
@@ -18,7 +18,12 @@
18
18
 
19
19
  <div class="bg-white rounded-lg shadow overflow-x-auto">
20
20
  <div class="min-w-full inline-block align-middle">
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
+ <table
22
+ data-controller="table"
23
+ data-database_table_path="<%= database_table_rows_path %>"
24
+ data-set-table-order-path="<%= database_set_table_column_order_path(params[:database_id], @table_name) %>"
25
+ class="min-w-full relative"
26
+ >
22
27
  <thead class="">
23
28
  <tr class="bg-gray-100 border-b border-gray-200">
24
29
  <% @data.columns.each_with_index do |column, index| %>
@@ -43,35 +48,89 @@
43
48
  <% end %>
44
49
  </tr>
45
50
  </thead>
46
-
51
+
47
52
  <tbody class="divide-y divide-gray-200">
48
53
  <% @data.rows.each do |row| %>
49
54
  <tr class="hover:bg-gray-50" >
50
55
  <% row.each_with_index do |item, index| %>
51
56
  <% truncated_item = item&.truncate(80) %>
52
57
  <% column_name = @data.columns[index] %>
58
+ <% foreign_key = @columns_info[column_name]&.dig(:foreign_key) %>
53
59
  <td
54
60
  data-column="<%= column_name %>"
55
61
  data-data_type="<%= @columns_info.dig(column_name).dig(:type) %>"
56
62
  class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap"
57
63
  >
58
64
 
59
- <div class="flex justify-between">
65
+ <div class="flex justify-between gap-1">
60
66
 
61
67
  <span data-column_item><%= truncated_item %></span>
68
+
69
+ <% if item.present? and foreign_key.present? %>
70
+ <button
71
+ data-action="click->table#load_foreign_key_data"
72
+ data-fk_target_table="<%= foreign_key[:to_table] %>"
73
+ data-fk_target_field="<%= foreign_key[:primary_key]%>"
74
+ data-fk_target_field_value="<%= truncated_item %>"
75
+ class="size-4 mt-0.5 hover:cursor-pointer flex-grow outline-none"
76
+ >
77
+ <%= image_tag "solid_litequeen/icons/spline.svg", class: "size-4 filter-blue" %>
78
+ </button>
79
+
80
+ <% end %>
62
81
 
82
+
83
+ <%# TODO: we can use one dynamic modal instead and fetch the data. for now this works and is (s)crapy! %>
63
84
  <% if truncated_item&.to_s&.ends_with?("...") %>
64
- <span title="<%= item %>" class="cursor-pointer size-4">
65
- <%= image_tag "solid_litequeen/icons/info.svg", class: "size-4" %>
66
- </span>
85
+ <% dialog_id = "#{column_name}_#{SecureRandom.hex(8)}_context_dialog" %>
86
+
87
+ <%= render "table-data-context-dialog", dialog_id: dialog_id, column_name: column_name, data: item %>
88
+
89
+ <button onclick="document.getElementById('<%= dialog_id %>').showModal()" class="cursor-pointer size-4 outline-none">
90
+ <%= image_tag "solid_litequeen/icons/circle-elipsis.svg", class: "size-4" %>
91
+
92
+ </button>
93
+
94
+
67
95
  <% end %>
96
+
97
+
68
98
  </div>
69
99
  </td>
70
100
  <% end %>
71
101
  </tr>
72
102
  <% end %>
73
103
  </tbody>
104
+
105
+ <dialog id="foreign-key-data" data-controller="dialog" class="w-[900px] m-auto overscroll-y-contain">
106
+ <form method="submit" class="">
107
+ <div class="flex flex-row-reverse">
108
+ <button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
109
+ <%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
110
+ </button>
111
+
112
+ </div>
113
+ </form>
114
+
115
+ <div class="overflow-x-auto relative">
116
+ <turbo-frame id="foreign-key-data-frame" class="peer">
117
+ <%# loading animation for the first render %>
118
+ <div class="flex w-full h-52 items-center justify-center">
119
+ <%= render "solid_litequeen/loading-animation" %>
120
+ </div>
121
+
122
+ </turbo-frame>
123
+
124
+ <%# loading animation everytime we load a new foreign key data %>
125
+ <div class="hidden peer-aria-busy:flex absolute inset-0 w-full h-52 items-center justify-center">
126
+ <%= render "solid_litequeen/loading-animation" %>
127
+ </div>
128
+
129
+ </div>
130
+ </dialog>
74
131
  </table>
132
+
133
+
75
134
  </div>
76
135
  </div>
77
136
  </div>
data/config/routes.rb CHANGED
@@ -2,6 +2,7 @@ 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
4
  post "/tables/:table/set-column-order", to: "databases#set_column_order", as: :set_table_column_order
5
+ get "/tables/:table/foreign-key-data/:target_table/:target_field/:target_field_value", to: "databases#get_foreign_key_data", as: :get_table_foreign_key_data
5
6
  get "download", to: "databases#download", as: "download"
6
7
  end
7
8
  root to: "databases#index"
@@ -1,3 +1,3 @@
1
1
  module SolidLitequeen
2
- VERSION = "0.11.1"
2
+ VERSION = "0.13.1"
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.11.1
4
+ version: 0.13.1
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-17 00:00:00.000000000 Z
11
+ date: 2025-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -164,8 +164,10 @@ files:
164
164
  - app/assets/config/solid_litequeen_manifest.js
165
165
  - app/assets/images/solid_litequeen/icons/chevron-left.svg
166
166
  - app/assets/images/solid_litequeen/icons/chevron-right.svg
167
+ - app/assets/images/solid_litequeen/icons/circle-elipsis.svg
167
168
  - app/assets/images/solid_litequeen/icons/database.svg
168
169
  - app/assets/images/solid_litequeen/icons/info.svg
170
+ - app/assets/images/solid_litequeen/icons/spline.svg
169
171
  - app/assets/images/solid_litequeen/icons/workflow.svg
170
172
  - app/assets/images/solid_litequeen/icons/x.svg
171
173
  - app/assets/stylesheets/solid_litequeen/application.css
@@ -175,6 +177,7 @@ files:
175
177
  - app/helpers/solid_litequeen/databases_helper.rb
176
178
  - app/javascript/solid_litequeen/application.js
177
179
  - app/javascript/solid_litequeen/controllers/application.js
180
+ - app/javascript/solid_litequeen/controllers/dialog_controller.js
178
181
  - app/javascript/solid_litequeen/controllers/index.js
179
182
  - app/javascript/solid_litequeen/controllers/table_controller.js
180
183
  - app/javascript/solid_litequeen/controllers/table_relations_controller.js
@@ -183,6 +186,9 @@ files:
183
186
  - app/models/solid_litequeen/application_record.rb
184
187
  - app/views/layouts/solid_litequeen/application.html.erb
185
188
  - app/views/solid_litequeen/_database-selector.html.erb
189
+ - app/views/solid_litequeen/_loading-animation.html.erb
190
+ - app/views/solid_litequeen/databases/_foreign-key-data.html.erb
191
+ - app/views/solid_litequeen/databases/_table-data-context-dialog.html.erb
186
192
  - app/views/solid_litequeen/databases/_table-relationships-dialog.html.erb
187
193
  - app/views/solid_litequeen/databases/index.html.erb
188
194
  - app/views/solid_litequeen/databases/show.html.erb