solid_litequeen 0.11.1 → 0.13.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: 90243fc8ba17fd413d9237ad0f4d6361f73f9dbf1497b5383ffadf5f484a67e1
4
- data.tar.gz: f0e9b68b711aa9c2866cfcab0117d34baf3199413fc0772d7946f62059dd3a00
3
+ metadata.gz: 133668d3a4340adb9111ea7e7e9aeea464173c2e87839e4dad82a2b5751e4058
4
+ data.tar.gz: 2d84c8ffef4315afcc3ac17bfa06f086efc8923c358d5ebf358daeb3438b057a
5
5
  SHA512:
6
- metadata.gz: a202ac2d37b116b661e89b54e25751a9459647d4f3fc52eb152d79c063e932b7c5fe1c4add8010caee6af07f392781ef1a773ed4ad3a5b1c3a180250050a7b2a
7
- data.tar.gz: c8faf1143ed4208d12f302553a34a8f2b132e784d9921380c5b2b4b192989581a069397b996de15c3139836156665a478efd7a8f4980008fcec377ecd8415625
6
+ metadata.gz: ae9e8e0f6b912011d11d857318d005dfe6f70850997ece81a48c529d277f9ac7c3832b7e1144f39fb51c964d0788a00fa8e9e7adf1e54c0b1912dc525b2e12a6
7
+ data.tar.gz: 73d39ca407117d57fea0ab2c753342fada254c6eda13816ef5d3806b84ee60f861f4d2f13257d8e885d2c3f8b7f824dfe03c3ca5cd0d62cfff4fa9a0e44e32db
@@ -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,29 @@ 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
+
211
+ end
173
212
  end
174
213
  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,38 @@ export default class extends Controller {
161
161
  targetTh.removeAttribute("data-column-order-about-to-be-swapped");
162
162
  }
163
163
 
164
+ load_foreign_key_data(e) {
165
+ const foreign_key_data_dialog = document.querySelector(
166
+ "dialog#foreign-key-data",
167
+ );
168
+
169
+ const foreign_key_data_frame = document.querySelector(
170
+ "#foreign-key-data-frame",
171
+ );
172
+
173
+ const fk_data_dialog_button = e.currentTarget;
174
+ const fk_target_table = fk_data_dialog_button.dataset.fk_target_table;
175
+ const fk_target_field = fk_data_dialog_button.dataset.fk_target_field;
176
+ const fk_target_field_value =
177
+ fk_data_dialog_button.dataset.fk_target_field_value;
178
+
179
+ const new_frame_src = `${this.element.dataset.database_table_path}/foreign-key-data/${fk_target_table}/${fk_target_field}/${fk_target_field_value}`;
180
+
181
+ // if the src didn't change let's just show what we have
182
+ if (foreign_key_data_frame.src) {
183
+ const frame_src_current_path = new URL(foreign_key_data_frame.src)
184
+ ?.pathname;
185
+
186
+ if (new_frame_src === frame_src_current_path) {
187
+ foreign_key_data_dialog.showModal();
188
+ return;
189
+ }
190
+ }
191
+
192
+ foreign_key_data_frame.src = new_frame_src;
193
+ foreign_key_data_dialog.showModal();
194
+ }
195
+
164
196
  #load_datetime_local_title() {
165
197
  setTimeout(() => {
166
198
  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,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"><%= 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,81 @@
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 %>
81
+
62
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">
116
+ <turbo-frame id="foreign-key-data-frame" >
117
+ <h1>loading...</h1>
118
+ </turbo-frame>
119
+ </div>
120
+
121
+
122
+ </dialog>
74
123
  </table>
124
+
125
+
75
126
  </div>
76
127
  </div>
77
128
  </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.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.11.1
4
+ version: 0.13.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-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,8 @@ 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/databases/_foreign-key-data.html.erb
190
+ - app/views/solid_litequeen/databases/_table-data-context-dialog.html.erb
186
191
  - app/views/solid_litequeen/databases/_table-relationships-dialog.html.erb
187
192
  - app/views/solid_litequeen/databases/index.html.erb
188
193
  - app/views/solid_litequeen/databases/show.html.erb