solid_litequeen 0.18.1 → 0.19.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 +4 -4
- data/app/assets/images/solid_litequeen/icons/table.svg +1 -0
- data/app/controllers/solid_litequeen/databases_controller.rb +51 -1
- data/app/javascript/solid_litequeen/controllers/command_palette_controller.js +227 -0
- data/app/javascript/solid_litequeen/controllers/orbit_controller.js +65 -0
- data/app/javascript/solid_litequeen/controllers/table_relations_controller.js +10 -10
- data/app/views/layouts/solid_litequeen/application.html.erb +32 -44
- data/app/views/solid_litequeen/_command-palette.html.erb +91 -0
- data/app/views/solid_litequeen/_database-selector.html.erb +1 -1
- data/app/views/solid_litequeen/databases/_foreign-key-data.html.erb +8 -8
- data/app/views/solid_litequeen/databases/_table-data-context-dialog.html.erb +3 -3
- data/app/views/solid_litequeen/databases/_table-relationships-dialog.html.erb +3 -3
- data/app/views/solid_litequeen/databases/index.html.erb +6 -6
- data/app/views/solid_litequeen/databases/show.html.erb +12 -12
- data/app/views/solid_litequeen/databases/table_rows.html.erb +61 -56
- data/config/routes.rb +1 -0
- data/lib/solid_litequeen/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b075b9f956c23d7f80e7778872d9a1b1f04eb7b7917bc2e1824bb62311eb018
|
4
|
+
data.tar.gz: b6156fa03a6d00b5624171c540e9841c2cfd381c8049a0ca2915f8b1557ab4cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c1b9e99524020847a7a68be86cfe4d13dae4eb79002cd21cf4eaee2d4ff58bd9a67020b0ec9eb000aa68aa48f7353eaaa122677ce26f2ec6fd5a60130e6ba6
|
7
|
+
data.tar.gz: fe803864a6775505c2dbd331d43107386d27ce0adfaecfadd5b5579559a26b2f8314b316c0f483b16143d5c4945174f20ebb4f5129c99cb3c4077f160cf74764
|
@@ -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-table"><path d="M12 3v18"></path><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M3 9h18"></path><path d="M3 15h18"></path></svg>
|
@@ -154,6 +154,7 @@ module SolidLitequeen
|
|
154
154
|
stored_order = session["#{@database_id}_#{@table_name}_column_order"] || []
|
155
155
|
ordered_columns = stored_order & valid_columns
|
156
156
|
ordered_columns += valid_columns - ordered_columns
|
157
|
+
# Persist merged column order so future requests use the updated schema
|
157
158
|
session["#{@database_id}_#{@table_name}_column_order"] = ordered_columns
|
158
159
|
|
159
160
|
order_clause = if @sort_column.present? && valid_columns.include?(@sort_column)
|
@@ -203,7 +204,6 @@ module SolidLitequeen
|
|
203
204
|
end
|
204
205
|
|
205
206
|
|
206
|
-
|
207
207
|
def set_column_order
|
208
208
|
table_name = params[:table]
|
209
209
|
database_id = params[:database_id]
|
@@ -248,9 +248,59 @@ module SolidLitequeen
|
|
248
248
|
render partial: "foreign-key-data"
|
249
249
|
end
|
250
250
|
|
251
|
+
def get_command_palette_data
|
252
|
+
palette_data = []
|
253
|
+
id_counter = 1
|
254
|
+
|
255
|
+
@available_databases ||= ActiveRecord::Base.configurations.configurations.select do |config|
|
256
|
+
config.adapter == "sqlite3" && config.env_name == Rails.env && config.database.present?
|
257
|
+
end
|
258
|
+
|
259
|
+
@available_databases.each do |db|
|
260
|
+
db_id = Base64.urlsafe_encode64(db.database)
|
261
|
+
db_file_name = db.database
|
262
|
+
|
263
|
+
# Add database entry
|
264
|
+
palette_data << {
|
265
|
+
id: id_counter,
|
266
|
+
type: "database",
|
267
|
+
name: db.name,
|
268
|
+
database_file_name: db_file_name,
|
269
|
+
rowCount: nil,
|
270
|
+
path: database_path(db_id)
|
271
|
+
}
|
272
|
+
id_counter += 1
|
273
|
+
|
274
|
+
# Establish a connection to fetch tables for this database
|
275
|
+
DynamicDatabase.establish_connection(
|
276
|
+
adapter: "sqlite3",
|
277
|
+
database: db.database
|
278
|
+
)
|
279
|
+
|
280
|
+
DynamicDatabase.connection.tables.each do |table_name|
|
281
|
+
row_count = DynamicDatabase.connection.select_value("SELECT COUNT(*) FROM #{table_name}").to_i
|
282
|
+
|
283
|
+
# Add table entry
|
284
|
+
palette_data << {
|
285
|
+
id: id_counter,
|
286
|
+
type: "table",
|
287
|
+
name: table_name,
|
288
|
+
database_name: db.name,
|
289
|
+
database_file_name: db_file_name,
|
290
|
+
rowCount: row_count,
|
291
|
+
path: database_table_rows_path(db_id, table_name)
|
292
|
+
}
|
293
|
+
id_counter += 1
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
render json: palette_data
|
298
|
+
end
|
299
|
+
|
251
300
|
private
|
252
301
|
|
253
302
|
def enum_mappings
|
303
|
+
# get all the enums defined in models in the app
|
254
304
|
Rails.application.eager_load! unless Rails.application.config.eager_load
|
255
305
|
ActiveRecord::Base.descendants.each_with_object({}) do |model, map|
|
256
306
|
next if model.abstract_class?
|
@@ -0,0 +1,227 @@
|
|
1
|
+
/**
|
2
|
+
* Command palette
|
3
|
+
*
|
4
|
+
* eg:
|
5
|
+
*
|
6
|
+
|
7
|
+
*
|
8
|
+
*
|
9
|
+
* */
|
10
|
+
|
11
|
+
|
12
|
+
import { Controller } from "@hotwired/stimulus";
|
13
|
+
|
14
|
+
|
15
|
+
// Connects to data-controller="command_palette"
|
16
|
+
export default class extends Controller {
|
17
|
+
|
18
|
+
|
19
|
+
connect() {
|
20
|
+
this.isOpen = false;
|
21
|
+
this.selectedIndex = 0;
|
22
|
+
this.searchItems = [];
|
23
|
+
this.filteredItems = [];
|
24
|
+
|
25
|
+
this.modal = document.getElementById('commandPaletteModal');
|
26
|
+
this.dialog = document.getElementById('commandPaletteDialog');
|
27
|
+
this.input = document.getElementById('commandPaletteInput');
|
28
|
+
this.input_timeout = null;
|
29
|
+
this.resultsList = document.getElementById('resultsList');
|
30
|
+
this.noResults = document.getElementById('noResults');
|
31
|
+
this.resultsCount = document.getElementById('resultsCount');
|
32
|
+
this.trigger = document.getElementById('commandPaletteTrigger');
|
33
|
+
|
34
|
+
this.initializeSearchItems();
|
35
|
+
this.bindEvents();
|
36
|
+
this.updateResults();
|
37
|
+
}
|
38
|
+
|
39
|
+
async initializeSearchItems() {
|
40
|
+
this.searchItems = [];
|
41
|
+
const request = await fetch(this.element.dataset.command_palette_data_path);
|
42
|
+
const data = await request.json();
|
43
|
+
this.searchItems = [...data];
|
44
|
+
}
|
45
|
+
|
46
|
+
bindEvents() {
|
47
|
+
// Trigger button click
|
48
|
+
this.trigger.addEventListener('click', () => this.open());
|
49
|
+
|
50
|
+
// Keyboard shortcuts
|
51
|
+
document.addEventListener('keydown', (e) => this.handleKeyDown(e));
|
52
|
+
|
53
|
+
// Input change
|
54
|
+
this.input.addEventListener('input', (e) => this.handleSearch(e.target.value));
|
55
|
+
|
56
|
+
// Click outside to close
|
57
|
+
this.modal.addEventListener('click', (e) => {
|
58
|
+
if (!this.dialog.contains(e.target)) {
|
59
|
+
this.close();
|
60
|
+
}
|
61
|
+
});
|
62
|
+
}
|
63
|
+
|
64
|
+
handleKeyDown(e) {
|
65
|
+
// Global shortcut
|
66
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
67
|
+
e.preventDefault();
|
68
|
+
if(this.isOpen) return this.close();
|
69
|
+
|
70
|
+
this.open();
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (!this.isOpen) return;
|
75
|
+
|
76
|
+
switch (e.key) {
|
77
|
+
case 'Escape':
|
78
|
+
this.close();
|
79
|
+
break;
|
80
|
+
case 'ArrowDown':
|
81
|
+
e.preventDefault();
|
82
|
+
this.selectedIndex = this.selectedIndex < this.filteredItems.length - 1
|
83
|
+
? this.selectedIndex + 1
|
84
|
+
: 0;
|
85
|
+
this.updateSelection();
|
86
|
+
break;
|
87
|
+
case 'ArrowUp':
|
88
|
+
e.preventDefault();
|
89
|
+
this.selectedIndex = this.selectedIndex > 0
|
90
|
+
? this.selectedIndex - 1
|
91
|
+
: this.filteredItems.length - 1;
|
92
|
+
this.updateSelection();
|
93
|
+
break;
|
94
|
+
case 'Enter':
|
95
|
+
if (this.filteredItems[this.selectedIndex]) {
|
96
|
+
this.selectItem(this.filteredItems[this.selectedIndex]);
|
97
|
+
}
|
98
|
+
break;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
handleSearch(query) {
|
103
|
+
clearTimeout(this.input_timeout);
|
104
|
+
|
105
|
+
if (!query) {
|
106
|
+
this.filteredItems = [...this.searchItems];
|
107
|
+
this.selectedIndex = 0;
|
108
|
+
this.updateResults();
|
109
|
+
|
110
|
+
} else {
|
111
|
+
|
112
|
+
this.input_timeout = setTimeout(() => {
|
113
|
+
this.filteredItems = this.searchItems.filter(item =>
|
114
|
+
item.name.toLowerCase().includes(query.toLowerCase()) ||
|
115
|
+
(item.database_name && item.database_name.toLowerCase().includes(query.toLowerCase())) ||
|
116
|
+
(item.database_file_name && item.database_file_name.toLowerCase().includes(query.toLowerCase()))
|
117
|
+
);
|
118
|
+
|
119
|
+
this.selectedIndex = 0;
|
120
|
+
this.updateResults();
|
121
|
+
}, 200);
|
122
|
+
}
|
123
|
+
|
124
|
+
|
125
|
+
}
|
126
|
+
|
127
|
+
updateResults() {
|
128
|
+
this.resultsCount.textContent = `${this.filteredItems.length} results`;
|
129
|
+
|
130
|
+
if (this.filteredItems.length === 0) {
|
131
|
+
this.noResults.classList.remove('hidden');
|
132
|
+
this.resultsList.innerHTML = '';
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
|
136
|
+
this.noResults.classList.add('hidden');
|
137
|
+
|
138
|
+
this.resultsList.innerHTML = this.filteredItems.slice(0, 25).map((item, index) => {
|
139
|
+
const isSelected = index === this.selectedIndex;
|
140
|
+
|
141
|
+
const icon = item.type === 'database'
|
142
|
+
? `<img src="${this.element.dataset.database_svg_img_path}" class="size-4 filter-blue opacity-80" />`
|
143
|
+
: `<img src="${this.element.dataset.table_svg_img_path}" class="size-4 filter-lime opacity-80" />`;
|
144
|
+
|
145
|
+
const badge = item.type === 'table'
|
146
|
+
? `<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-neutral-content text-neutral">${item.rowCount?.toLocaleString()} rows</span>`
|
147
|
+
: '';
|
148
|
+
|
149
|
+
const database = item.database_file_name
|
150
|
+
? `<div class="text-xs text-base-content/60 truncate">in ${item.database_file_name}</div>`
|
151
|
+
: '';
|
152
|
+
|
153
|
+
return `
|
154
|
+
<div ${isSelected ? 'data-selected': ''} data-index="${index}" class="data-selected:outline-2 outline-[var(--color-primary)] data-selected:bg-[var(--color-base-100)] flex items-center gap-3 rounded-md px-3 py-2 text-sm cursor-pointer transition-colors">
|
155
|
+
${icon}
|
156
|
+
<div class="flex-1 min-w-0">
|
157
|
+
<div class="flex items-center gap-2">
|
158
|
+
<span class="font-medium truncate">${item.name}</span>
|
159
|
+
${badge}
|
160
|
+
</div>
|
161
|
+
${database}
|
162
|
+
</div>
|
163
|
+
<svg class="w-4 h-4 text-base-content/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
164
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
165
|
+
</svg>
|
166
|
+
</div>
|
167
|
+
`;
|
168
|
+
}).join('');
|
169
|
+
|
170
|
+
// Add click handlers to result items
|
171
|
+
this.resultsList.querySelectorAll('[data-index]').forEach((element, index) => {
|
172
|
+
element.addEventListener('click', () => this.selectItem(this.filteredItems[index]));
|
173
|
+
element.addEventListener('mouseenter', () => {
|
174
|
+
this.selectedIndex = index;
|
175
|
+
this.updateSelection();
|
176
|
+
});
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
updateSelection() {
|
181
|
+
this.resultsList.querySelectorAll('[data-index]').forEach((element, index) => {
|
182
|
+
if (index === this.selectedIndex) {
|
183
|
+
element.setAttribute('data-selected', true);
|
184
|
+
element.scrollIntoView({ block: 'nearest' });
|
185
|
+
} else {
|
186
|
+
element.removeAttribute('data-selected');
|
187
|
+
}
|
188
|
+
});
|
189
|
+
}
|
190
|
+
|
191
|
+
selectItem(item) {
|
192
|
+
this.close(true);
|
193
|
+
Turbo.visit(item.path);
|
194
|
+
|
195
|
+
}
|
196
|
+
|
197
|
+
open() {
|
198
|
+
this.isOpen = true;
|
199
|
+
this.modal.classList.remove('hidden');
|
200
|
+
// this.dialog.classList.add('dialog-enter');
|
201
|
+
this.input.focus();
|
202
|
+
this.input.value = '';
|
203
|
+
this.handleSearch('');
|
204
|
+
}
|
205
|
+
|
206
|
+
close(quiet=false) {
|
207
|
+
this.isOpen = false;
|
208
|
+
// this.dialog.classList.remove('dialog-enter');
|
209
|
+
|
210
|
+
if(quiet){
|
211
|
+
this.modal.classList.add('hidden');
|
212
|
+
this.selectedIndex = 0;
|
213
|
+
return
|
214
|
+
}
|
215
|
+
|
216
|
+
// this.dialog.classList.add('dialog-exit');
|
217
|
+
|
218
|
+
setTimeout(() => {
|
219
|
+
this.modal.classList.add('hidden');
|
220
|
+
// this.dialog.classList.remove('dialog-exit');
|
221
|
+
this.selectedIndex = 0;
|
222
|
+
}, 100);
|
223
|
+
}
|
224
|
+
|
225
|
+
|
226
|
+
}
|
227
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/**
|
2
|
+
*
|
3
|
+
* Stimulus to put an object in the "orbit" or another object using floating-ui.
|
4
|
+
* We define a "planet" and its "moon" that'll orbit around it.
|
5
|
+
* eg:
|
6
|
+
*
|
7
|
+
<div data-controller="orbit">
|
8
|
+
<button data-orbit-target="planet" popovertarget="menu-popover" style="anchor-name:--anchor-menu">🌐</button>
|
9
|
+
|
10
|
+
<ul popover id="menu-popover" data-orbit-target="moon" style="position-anchor:--anchor-menu">
|
11
|
+
<li>one</li>
|
12
|
+
<li>two</li>
|
13
|
+
</ul>
|
14
|
+
</div>
|
15
|
+
*
|
16
|
+
**/
|
17
|
+
|
18
|
+
import { Controller } from "@hotwired/stimulus"
|
19
|
+
|
20
|
+
const { computePosition, flip, shift, offset, autoUpdate } = window.FloatingUIDOM;
|
21
|
+
|
22
|
+
// data-controller="orbit"
|
23
|
+
export default class extends Controller {
|
24
|
+
// data-orbit-target="planet"
|
25
|
+
// data-orbit-target="moon"
|
26
|
+
static targets = ["planet", "moon"]
|
27
|
+
|
28
|
+
connect() {
|
29
|
+
this.setupOrbit();
|
30
|
+
|
31
|
+
}
|
32
|
+
|
33
|
+
setupOrbit() {
|
34
|
+
this.cleanup = autoUpdate(
|
35
|
+
this.planetTarget,
|
36
|
+
this.moonTarget,
|
37
|
+
() => this.updatePosition()
|
38
|
+
);
|
39
|
+
}
|
40
|
+
|
41
|
+
updatePosition() {
|
42
|
+
computePosition(this.planetTarget, this.moonTarget, {
|
43
|
+
placement: 'bottom',
|
44
|
+
strategy: 'fixed', // wow this was what was missing!
|
45
|
+
middleware: [
|
46
|
+
offset(6),
|
47
|
+
flip(),
|
48
|
+
shift({ padding: 5 })
|
49
|
+
],
|
50
|
+
}).then(({x, y}) => {
|
51
|
+
Object.assign(this.moonTarget.style, {
|
52
|
+
left: `${x}px`,
|
53
|
+
top: `${y}px`,
|
54
|
+
});
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
disconnect() {
|
60
|
+
if (this.cleanup) {
|
61
|
+
this.cleanup();
|
62
|
+
}
|
63
|
+
|
64
|
+
}
|
65
|
+
}
|
@@ -21,7 +21,7 @@ export default class extends Controller {
|
|
21
21
|
gridSize: 10,
|
22
22
|
drawGrid: true,
|
23
23
|
background: {
|
24
|
-
color: "var(--color-
|
24
|
+
// color: "var(--color-neutral)",
|
25
25
|
},
|
26
26
|
interactive: (cellView) => {
|
27
27
|
// If this cell is embedded (a field node),
|
@@ -68,7 +68,7 @@ export default class extends Controller {
|
|
68
68
|
target: { id: targetId },
|
69
69
|
attrs: {
|
70
70
|
line: {
|
71
|
-
stroke: "var(--color-
|
71
|
+
stroke: "var(--color-info)",
|
72
72
|
strokeWidth: 2,
|
73
73
|
targetMarker: {
|
74
74
|
type: "path",
|
@@ -98,19 +98,19 @@ export default class extends Controller {
|
|
98
98
|
size: { width: width, height: height },
|
99
99
|
attrs: {
|
100
100
|
body: {
|
101
|
-
fill: "
|
101
|
+
fill: "var(--color-base-100)",
|
102
102
|
strokeWidth: 2,
|
103
|
-
stroke: "
|
103
|
+
stroke: "var(--color-base-100)",
|
104
104
|
},
|
105
105
|
header: {
|
106
|
-
fill: "
|
106
|
+
fill: "var(--color-base-100)",
|
107
107
|
strokeWidth: 1,
|
108
|
-
stroke: "
|
108
|
+
stroke: "var(--color-base-content)",
|
109
109
|
height: headerHeight,
|
110
110
|
},
|
111
111
|
headerText: {
|
112
112
|
text: tableName,
|
113
|
-
fill: "
|
113
|
+
fill: "var(--color-primary)",
|
114
114
|
fontWeight: "bold",
|
115
115
|
fontSize: 16,
|
116
116
|
},
|
@@ -128,13 +128,13 @@ export default class extends Controller {
|
|
128
128
|
size: { width: width, height: fieldHeight },
|
129
129
|
attrs: {
|
130
130
|
body: {
|
131
|
-
fill: "var(--color-
|
131
|
+
fill: "var(--color-base-100)",
|
132
132
|
strokeWidth: 1,
|
133
|
-
stroke: "
|
133
|
+
stroke: "var(--color-base-content)",
|
134
134
|
},
|
135
135
|
label: {
|
136
136
|
text: field,
|
137
|
-
fill: "var(--color-
|
137
|
+
fill: "var(--color-base-content)",
|
138
138
|
fontSize: 14,
|
139
139
|
},
|
140
140
|
},
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
-
<html>
|
2
|
+
<html class="text-base-content bg-base-100">
|
3
3
|
<head>
|
4
4
|
<title>Solid litequeen</title>
|
5
5
|
<%= csrf_meta_tags %>
|
@@ -11,38 +11,19 @@
|
|
11
11
|
|
12
12
|
<%= stylesheet_link_tag "solid_litequeen/application", media: "all", "data-turbo-track": "reload" %>
|
13
13
|
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
14
|
+
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
15
|
+
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
14
16
|
<script src="https://cdn.jsdelivr.net/npm/@joint/core@4.0.1/dist/joint.js"></script>
|
15
17
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/core@1.7.2"></script>
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.2"></script>
|
16
20
|
<style type="text/tailwindcss">
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
--color-background: var(--color-slate-50);
|
23
|
-
--color-background-light: var(--color-white);
|
24
|
-
--color-border-muted: var(--color-slate-200);
|
25
|
-
--color-text: var(--color-gray-900);
|
26
|
-
--color-text-muted: var(--color-gray-500);
|
27
|
-
--color-background-secondary: var(--color-slate-50);
|
28
|
-
/* paper is the graph generated by joint.js. */
|
29
|
-
--color-paper-background: var(--color-slate-50);
|
30
|
-
--color-paper-links: var(--color-slate-600);
|
31
|
-
}
|
21
|
+
/* @plugin "@tailwindcss/forms"; */
|
22
|
+
|
23
|
+
/* @utility text-muted{
|
24
|
+
@apply text-base-content/60;
|
25
|
+
} */
|
32
26
|
|
33
|
-
@media (prefers-color-scheme: dark) {
|
34
|
-
:root {
|
35
|
-
--color-background: var(--color-slate-900);
|
36
|
-
--color-background-light: var(--color-slate-800);
|
37
|
-
--color-text: var(--color-slate-300);
|
38
|
-
--color-text-muted: var(--color-gray-400);
|
39
|
-
--color-background-secondary: var(--color-slate-300);
|
40
|
-
/* paper is the graph generated by joint.js. */
|
41
|
-
--color-paper-background: var(--color-slate-600);
|
42
|
-
--color-paper-links: var(--color-slate-300);
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
27
|
|
47
28
|
@utility filter-blue{
|
48
29
|
filter: invert(32%) sepia(65%) saturate(6380%) hue-rotate(219deg) brightness(98%) contrast(102%);
|
@@ -57,21 +38,17 @@
|
|
57
38
|
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
58
39
|
}
|
59
40
|
|
41
|
+
@utility filter-lime{
|
42
|
+
filter: invert(50%) sepia(100%) saturate(3000%) hue-rotate(90deg) brightness(100%) contrast(100%);
|
43
|
+
}
|
60
44
|
|
61
|
-
|
62
|
-
|
63
|
-
/* Safari-only CSS here */
|
64
|
-
_::-webkit-full-page-media, _:future, :root div[popover] {
|
65
|
-
position: fixed !important;
|
66
|
-
top: 50% !important;
|
67
|
-
left: 50% !important;
|
68
|
-
transform: translate(-50%, -50%) !important;
|
69
|
-
margin: 0 !important;
|
70
|
-
max-width: 90vw;
|
71
|
-
z-index: 1000;
|
72
|
-
}
|
45
|
+
@utility filter-orange{
|
46
|
+
filter: invert(53%) sepia(91%) saturate(7000%) hue-rotate(22deg) brightness(110%) contrast(110%);
|
73
47
|
}
|
74
48
|
|
49
|
+
|
50
|
+
|
51
|
+
|
75
52
|
</style>
|
76
53
|
|
77
54
|
|
@@ -79,13 +56,24 @@
|
|
79
56
|
|
80
57
|
<%= yield :head %>
|
81
58
|
</head>
|
82
|
-
<
|
83
|
-
|
59
|
+
<script>
|
60
|
+
const setTheme = () => {
|
61
|
+
const storedTheme = localStorage.getItem('theme');
|
62
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
63
|
+
document.documentElement.setAttribute('data-theme', storedTheme || (prefersDark ? 'sunset' : 'light'));
|
64
|
+
};
|
65
|
+
setTheme();
|
66
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', setTheme);
|
67
|
+
</script>
|
68
|
+
<body class="">
|
69
|
+
<header class="container flex items-center justify-between mx-auto mt-4 mb-8">
|
84
70
|
<%= render "solid_litequeen/database-selector" %>
|
71
|
+
|
72
|
+
<%= render "solid_litequeen/command-palette" %>
|
85
73
|
</header>
|
86
74
|
<%= yield %>
|
87
75
|
|
88
|
-
<footer class="container mx-auto mt-8 mb-4 text-center text-
|
76
|
+
<footer class="container mx-auto mt-8 mb-4 text-center text-base-content/60 text-xs">
|
89
77
|
<p>Solid litequeen v<%= SolidLitequeen::VERSION %></p>
|
90
78
|
</footer>
|
91
79
|
</body>
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<style>
|
2
|
+
|
3
|
+
.dialog-enter {
|
4
|
+
animation: dialogEnter 0.2s ease-out;
|
5
|
+
}
|
6
|
+
.dialog-exit {
|
7
|
+
animation: dialogExit 0.2s ease-in;
|
8
|
+
}
|
9
|
+
@keyframes dialogEnter {
|
10
|
+
from {
|
11
|
+
opacity: 0;
|
12
|
+
transform: scale(0.95);
|
13
|
+
}
|
14
|
+
to {
|
15
|
+
opacity: 1;
|
16
|
+
transform: scale(1);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
@keyframes dialogExit {
|
20
|
+
from {
|
21
|
+
opacity: 1;
|
22
|
+
transform: scale(1);
|
23
|
+
}
|
24
|
+
to {
|
25
|
+
opacity: 0;
|
26
|
+
transform: scale(0.95);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
</style>
|
31
|
+
|
32
|
+
<section
|
33
|
+
data-controller="command-palette"
|
34
|
+
data-command_palette_data_path="<%= command_palette_data_path %>"
|
35
|
+
data-database_svg_img_path="<%= image_path("solid_litequeen/icons/database.svg") %>"
|
36
|
+
data-table_svg_img_path="<%= image_path("solid_litequeen/icons/table.svg") %>"
|
37
|
+
|
38
|
+
>
|
39
|
+
<!-- Command Palette Trigger -->
|
40
|
+
<button id="commandPaletteTrigger" class="flex items-center gap-2 px-3 py-2 text-sm bg-base-200 border border-base-content/20 hover:bg-base-100 rounded-md transition-colors">
|
41
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
42
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
43
|
+
</svg>
|
44
|
+
<span>Search databases and tables...</span>
|
45
|
+
<kbd class="ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-base-300 px-1.5 font-mono text-[10px] font-medium opacity-100">
|
46
|
+
<span class="text-xs">⌘</span>K
|
47
|
+
</kbd>
|
48
|
+
</button>
|
49
|
+
|
50
|
+
<!-- Command Palette Modal -->
|
51
|
+
<div id="commandPaletteModal" class="fixed inset-0 z-50 hidden">
|
52
|
+
<!-- Backdrop -->
|
53
|
+
<div class="fixed inset-0 backdrop-blur "></div>
|
54
|
+
|
55
|
+
<!-- Dialog -->
|
56
|
+
<div class="fixed inset-0 flex items-start justify-center pt-[10vh]">
|
57
|
+
<div id="commandPaletteDialog" class="bg-base-200 border border-base-content/20 rounded-lg shadow-xl w-full max-w-2xl mx-4 dialog-enter">
|
58
|
+
<!-- Search Input -->
|
59
|
+
<div class="flex items-center border-b border-base-content/30 px-3">
|
60
|
+
<svg class="mr-2 h-4 w-4 shrink-0 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
61
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
62
|
+
</svg>
|
63
|
+
<input
|
64
|
+
id="commandPaletteInput"
|
65
|
+
type="text"
|
66
|
+
placeholder="Search databases and tables..."
|
67
|
+
class="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none border-0 focus:ring-0"
|
68
|
+
autocomplete="off"
|
69
|
+
/>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<!-- Results -->
|
73
|
+
<div id="commandPaletteResults" class="max-h-[400px] overflow-y-auto">
|
74
|
+
<div id="noResults" class="py-6 text-center text-sm text-base-content/60 hidden">
|
75
|
+
No databases or tables found.
|
76
|
+
</div>
|
77
|
+
<div id="resultsList" class="p-2"></div>
|
78
|
+
</div>
|
79
|
+
|
80
|
+
<!-- Footer -->
|
81
|
+
<div class="border-t border-base-content/30 px-3 py-2 text-xs text-base-content/60">
|
82
|
+
<div class="flex items-center justify-between">
|
83
|
+
<span>Use ↑↓ to navigate, Enter to select, Esc to close</span>
|
84
|
+
<span id="resultsCount">0 results</span>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
|
91
|
+
</section>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<% if controller.controller_name == "databases" && action_name.in?([ "show", "table_rows"]) %>
|
3
3
|
<div class="w-86">
|
4
4
|
<div class="database-selector p-4">
|
5
|
-
<select id="database-select" class="block w-
|
5
|
+
<select id="database-select" class="select block w-52 focus:outline-none outline-none ring-0">
|
6
6
|
<option value="">Select a database...</option>
|
7
7
|
<% available_databases.each do |db| %>
|
8
8
|
<option value="<%= Base64.urlsafe_encode64(db.database) %>" <%= 'selected' if defined?(@database_id) && @database_id == Base64.urlsafe_encode64(db.database) %>>
|
@@ -9,23 +9,23 @@
|
|
9
9
|
</h1>
|
10
10
|
|
11
11
|
<div class="mx-auto my-4 p-4 max-w-[90%]">
|
12
|
-
<table class="min-w-full
|
13
|
-
<thead class="bg-
|
12
|
+
<table class="min-w-full border border-base-content/20">
|
13
|
+
<thead class="bg-base-300 border-b border-base-content/20">
|
14
14
|
<tr>
|
15
|
-
<th class="px-6 py-3 text-left text-xs font-medium dark:font-bold
|
15
|
+
<th class="px-6 py-3 text-left text-xs font-medium dark:font-bold text-base-content/80 uppercase tracking-wider">
|
16
16
|
Column Name
|
17
17
|
</th>
|
18
|
-
<th class="px-6 py-3 text-left text-xs font-medium dark:font-bold
|
18
|
+
<th class="px-6 py-3 text-left text-xs font-medium dark:font-bold text-base-content/80 uppercase tracking-wider">
|
19
19
|
Value
|
20
20
|
</th>
|
21
21
|
</tr>
|
22
22
|
</thead>
|
23
|
-
<tbody class="bg-
|
23
|
+
<tbody class="bg-base-200 ">
|
24
24
|
<% @result.rows.each do |row| %>
|
25
25
|
<% @result.columns.zip(row).each do |column, value| %>
|
26
|
-
<tr>
|
27
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
28
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
26
|
+
<tr class="border-b border-base-content/10 last:border-none hover:bg-base-100">
|
27
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold"><%= column %></td>
|
28
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 in-aria-busy:blur"><%= value&.truncate(80) %></td>
|
29
29
|
</tr>
|
30
30
|
<% end %>
|
31
31
|
<% end %>
|
@@ -14,16 +14,16 @@
|
|
14
14
|
end
|
15
15
|
%>
|
16
16
|
|
17
|
-
<dialog id="<%= dialog_id %>" data-controller="dialog" class="bg-
|
17
|
+
<dialog id="<%= dialog_id %>" data-controller="dialog" class="bg-base-200 rounded border border-base-content/20 w-[800px] m-auto overscroll-y-contain">
|
18
18
|
<div class="flex flex-row-reverse">
|
19
19
|
<button data-action="click->dialog#close" class="cursor-pointer mr-4 mt-2 outline-none">
|
20
|
-
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5 dark:filter-white" %>
|
20
|
+
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5 opacity-60 dark:filter-white" %>
|
21
21
|
</button>
|
22
22
|
</div>
|
23
23
|
|
24
24
|
<h1 class="text-lg font-semibold text-center"><%= column_name %></h1>
|
25
25
|
|
26
|
-
<div data-controller="clipboard" class="relative flex items-center max-w-[90%] h-80 max-h-80 mx-auto my-4 p-2 dark:bg-transparent bg-
|
26
|
+
<div data-controller="clipboard" class="relative flex items-center max-w-[90%] h-80 max-h-80 mx-auto my-4 p-2 dark:bg-transparent bg-base-300 rounded ">
|
27
27
|
|
28
28
|
<button title="Copy" data-action="clipboard#copy" class="absolute top-2 right-2 hover:cursor-pointer">
|
29
29
|
<%= image_tag "solid_litequeen/icons/copy-clipboard.svg", class: "in-data-copied:hidden size-5 dark:filter-white opacity-50" %>
|
@@ -1,12 +1,12 @@
|
|
1
|
-
<dialog id="table_relationships" data-controller="dialog table-relations" data-relations="<%= @table_relations.to_json %>" class="
|
1
|
+
<dialog id="table_relationships" data-controller="dialog table-relations" data-relations="<%= @table_relations.to_json %>" class="bg-base-200 border border-base-content/20 backdrop-blur rounded w-[1000px] h-full m-auto overscroll-y-contain">
|
2
2
|
<div class="flex flex-row-reverse">
|
3
3
|
<button data-action="click->dialog#close" class="cursor-pointer mr-4 mt-2 outline-none">
|
4
|
-
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5 dark:filter-white" %>
|
4
|
+
<%= image_tag "solid_litequeen/icons/x.svg", class: "opacity-60 size-5 dark:filter-white" %>
|
5
5
|
</button>
|
6
6
|
</div>
|
7
7
|
<h1 class=" text-center mt-4">Table Relationships</h1>
|
8
8
|
|
9
9
|
<div class="w-full p-4 mx-auto overflow-x-auto">
|
10
|
-
<div id="paper" class="m-auto my-10 w-full h-[600px] border border-
|
10
|
+
<div id="paper" class="m-auto my-10 w-full h-[600px] border border-base-content/20 shadow rounded"></div>
|
11
11
|
</div>
|
12
12
|
</dialog>
|
@@ -3,21 +3,21 @@
|
|
3
3
|
<h1 class="text-3xl font-bold mb-6">Solid Lite Queen</h1>
|
4
4
|
|
5
5
|
<div class="mb-6">
|
6
|
-
<p class="text-
|
6
|
+
<p class="text-base-content/60"><%= pluralize(available_databases.count, "SQLite database") %> found</p>
|
7
7
|
</div>
|
8
8
|
|
9
9
|
<% if available_databases.any? %>
|
10
10
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
11
11
|
<% available_databases.each do |db| %>
|
12
|
-
<a href="<%= database_path(Base64.urlsafe_encode64(db.database)) %>" class="block bg-
|
12
|
+
<a href="<%= database_path(Base64.urlsafe_encode64(db.database)) %>" class="block bg-base-200 border border-base-content/20 rounded-lg shadow-sm hover:shadow-md transition duration-150 overflow-hidden">
|
13
13
|
<div class="p-5">
|
14
14
|
<div class="flex items-center justify-between mb-2">
|
15
|
-
<h2 class="text-lg font-semibold
|
16
|
-
<span class="px-2 py-1 text-xs font-medium rounded-full
|
15
|
+
<h2 class="text-lg font-semibold truncate"><%= db.name %></h2>
|
16
|
+
<span class="badge badge-soft badge-xs badge-info px-2 py-1 text-xs font-medium rounded-full "><%= db.env_name %></span>
|
17
17
|
</div>
|
18
|
-
<p class="text-sm text-
|
18
|
+
<p class="text-sm text-base-content/60 truncate mb-4"><%= db.database %></p>
|
19
19
|
<div class="flex justify-end">
|
20
|
-
<span class="inline-flex items-center text-sm font-medium
|
20
|
+
<span class="inline-flex items-center text-sm font-medium">
|
21
21
|
Open
|
22
22
|
<svg class="w-4 h-4 ml-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
23
23
|
<path fill-rule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" />
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div class="container mx-auto px-4 py-8">
|
2
2
|
<h1 class="text-3xl font-bold mb-6 flex gap-1 items-center justify-center">
|
3
|
-
<%= image_tag "solid_litequeen/icons/database.svg", class: "size-6 dark:filter-white" %>
|
3
|
+
<%= image_tag "solid_litequeen/icons/database.svg", class: "opacity-60 size-6 dark:filter-white" %>
|
4
4
|
<span>
|
5
5
|
<%= @database_location %>
|
6
6
|
</span>
|
@@ -16,31 +16,31 @@
|
|
16
16
|
<%= render "table-relationships-dialog" %>
|
17
17
|
|
18
18
|
<button title="Relationships" onclick="table_relationships.showModal()" class="hover:cursor-pointer outline-none">
|
19
|
-
<%= image_tag "solid_litequeen/icons/workflow.svg", class: "size-5 -mb-1 dark:filter-white" %>
|
19
|
+
<%= image_tag "solid_litequeen/icons/workflow.svg", class: "size-5 -mb-1 opacity-60 dark:filter-white" %>
|
20
20
|
</button>
|
21
21
|
|
22
22
|
<% end %>
|
23
23
|
</h2>
|
24
|
-
<p class="text-
|
24
|
+
<p class="text-base-content/60"><%= pluralize(@tables.count, "table") %> found</p>
|
25
25
|
</div>
|
26
26
|
|
27
|
-
<div class="bg-
|
27
|
+
<div class="bg-base-200 border border-base-content/20 rounded-lg shadow overflow-hidden">
|
28
28
|
<table class="w-full">
|
29
29
|
<thead>
|
30
|
-
<tr class="bg-
|
31
|
-
<th class="px-6 py-3 text-left text-sm font-medium
|
32
|
-
<th class="px-6 py-3 text-left text-sm font-medium
|
30
|
+
<tr class="bg-base-300 border-b border-base-content/20">
|
31
|
+
<th class="px-6 py-3 text-left text-sm font-medium ">Table Name</th>
|
32
|
+
<th class="px-6 py-3 text-left text-sm font-medium">Row Count</th>
|
33
33
|
</tr>
|
34
34
|
</thead>
|
35
|
-
<tbody class="
|
35
|
+
<tbody class="">
|
36
36
|
<% @tables.each do |table| %>
|
37
|
-
<tr class="hover:bg-
|
37
|
+
<tr class="hover:bg-base-100 border-b last:border-none border-base-content/10">
|
38
38
|
<td class="px-6 py-4">
|
39
|
-
<%= link_to database_table_rows_path(@database_id, table[:name]), class: "
|
39
|
+
<%= link_to database_table_rows_path(@database_id, table[:name]), class: "link-primary font-medium" do %>
|
40
40
|
<%= table.dig(:name) %>
|
41
41
|
<% end %>
|
42
42
|
</td>
|
43
|
-
<td class="px-6 py-4 text-
|
43
|
+
<td class="px-6 py-4 text-base-content/60">
|
44
44
|
<%= pluralize(table.dig(:row_count), "Row") %>
|
45
45
|
</td>
|
46
46
|
</tr>
|
@@ -48,5 +48,5 @@
|
|
48
48
|
</tbody>
|
49
49
|
</table>
|
50
50
|
</div>
|
51
|
-
<%= link_to "Download Database", database_download_path(@database_id), class: "
|
51
|
+
<%= link_to "Download Database", database_download_path(@database_id), class: "link link-hover link-secondary text-xs mt-4" %>
|
52
52
|
</div>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<div class="container mx-auto px-4 py-8">
|
2
2
|
<h1 class="text-3xl font-bold mb-6 flex gap-1 items-center justify-center">
|
3
3
|
<%= link_to database_path(params[:database_id]) do %>
|
4
|
-
<%= image_tag "solid_litequeen/icons/database.svg", class: "size-6 dark:filter-white" %>
|
4
|
+
<%= image_tag "solid_litequeen/icons/database.svg", class: "opacity-60 size-6 dark:filter-white" %>
|
5
5
|
<% end %>
|
6
6
|
|
7
|
-
<%= image_tag "solid_litequeen/icons/chevron-right.svg", class: "size-6 dark:filter-white" %>
|
7
|
+
<%= image_tag "solid_litequeen/icons/chevron-right.svg", class: "opacity-60 size-6 dark:filter-white" %>
|
8
8
|
|
9
9
|
<span>
|
10
10
|
<%= @table_name %>
|
@@ -12,10 +12,10 @@
|
|
12
12
|
</h1>
|
13
13
|
|
14
14
|
<div class="mb-6">
|
15
|
-
<p class="text-
|
15
|
+
<p class="text-base-content/60"><%= pluralize(@row_count, "row") %> found</p>
|
16
16
|
</div>
|
17
17
|
|
18
|
-
<div class="bg-
|
18
|
+
<div class="bg-base-200 border border-base-content/20 rounded-lg shadow overflow-x-auto">
|
19
19
|
<div class="min-w-full inline-block align-middle">
|
20
20
|
<table
|
21
21
|
data-controller="table"
|
@@ -24,52 +24,57 @@
|
|
24
24
|
class="min-w-full relative"
|
25
25
|
>
|
26
26
|
<thead class="">
|
27
|
-
<tr class="bg-
|
27
|
+
<tr class="bg-base-300 border-b border-base-content/20">
|
28
28
|
<% @data.columns.each_with_index do |column, index| %>
|
29
29
|
<th
|
30
|
-
draggable="true"
|
30
|
+
draggable="true"
|
31
|
+
data-controller="orbit"
|
31
32
|
data-column-index="<%= index %>"
|
32
33
|
data-column-name="<%= column %>"
|
33
|
-
class="hover:cursor-move px-6 py-3 text-left text-sm font-medium
|
34
|
+
class="hover:cursor-move px-6 py-3 text-left text-sm font-medium whitespace-nowrap data-[is-dragging]:bg-orange-300/30 data-[column-order-about-to-be-swapped]:bg-green-300/30"
|
34
35
|
>
|
35
36
|
|
36
37
|
<%# popover the the column info %>
|
37
|
-
<%
|
38
|
+
<%
|
39
|
+
popover_id = "popover_#{column}_#{SecureRandom.hex(8)}"
|
40
|
+
anchor_name = "--anchor_#{popover_id}"
|
41
|
+
%>
|
42
|
+
|
38
43
|
|
39
|
-
<button popovertarget="<%= popover_id %>" class="mr-1 p-1 hover:cursor-pointer" style="anchor-name:
|
40
|
-
|
44
|
+
<button popovertarget="<%= popover_id %>" data-orbit-target="planet" class="mr-1 p-1 hover:cursor-pointer" style="anchor-name: <%= anchor_name %> ;">
|
45
|
+
<%= image_tag "solid_litequeen/icons/info.svg", class: "size-3.5 opacity-60 dark:filter-white" %>
|
41
46
|
</button>
|
42
47
|
|
43
|
-
<div popover id="<%= popover_id %>" class="max-w-lg min-h-10 bg-
|
44
|
-
<table class="min-w-full divide-y
|
48
|
+
<div popover id="<%= popover_id %>" data-orbit-target="moon" class="max-w-lg min-h-10 bg-base-200 border border-base-content/20 p-4 rounded-md" style="position-anchor: <%= anchor_name %>;">
|
49
|
+
<table class="min-w-full divide-y border border-base-content/10">
|
45
50
|
|
46
|
-
<tbody class="bg-
|
51
|
+
<tbody class="bg-base-100">
|
47
52
|
<% column_info = @columns_info[column] %>
|
48
53
|
|
49
|
-
<tr>
|
50
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
51
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
54
|
+
<tr class="border-b last:border-none border-base-content/10">
|
55
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Type</td>
|
56
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "><%= column_info&.dig(:sql_type) %></td>
|
52
57
|
</tr>
|
53
58
|
|
54
|
-
<tr>
|
55
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
56
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
59
|
+
<tr class="border-b last:border-none border-base-content/10">
|
60
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Primary Key</td>
|
61
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "><%= column_info&.dig(:is_primary_key) %></td>
|
57
62
|
</tr>
|
58
63
|
|
59
|
-
<tr>
|
60
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
61
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
64
|
+
<tr class="border-b last:border-none border-base-content/10">
|
65
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Nullable</td>
|
66
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "><%= column_info&.dig(:null) %></td>
|
62
67
|
</tr>
|
63
68
|
|
64
|
-
<tr>
|
65
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
66
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
69
|
+
<tr class="border-b last:border-none border-base-content/10">
|
70
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Default</td>
|
71
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "><%= column_info&.dig(:default) %></td>
|
67
72
|
</tr>
|
68
73
|
|
69
74
|
<% if column_info[:enum_options] %>
|
70
|
-
<tr>
|
71
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
72
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
75
|
+
<tr class="border-b last:border-none border-base-content/10">
|
76
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Enum values</td>
|
77
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80">
|
73
78
|
<%= column_info[:enum_options].map { |k, v| "#{k}: #{v}" }.join(', ') %>
|
74
79
|
</td>
|
75
80
|
</tr>
|
@@ -77,49 +82,49 @@
|
|
77
82
|
|
78
83
|
<% if column_info&.dig(:foreign_key).present? %>
|
79
84
|
|
80
|
-
<tr>
|
81
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
82
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
85
|
+
<tr class="border-b last:border-none border-base-content/10">
|
86
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold"></td>
|
87
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "> </td>
|
83
88
|
</tr>
|
84
89
|
|
85
|
-
<tr>
|
86
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
87
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
90
|
+
<tr class="border-b last:border-none border-base-content/10">
|
91
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Foreign Key</td>
|
92
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80">
|
88
93
|
<%= column_info&.dig(:foreign_key).dig(:to_table) %>
|
89
94
|
>
|
90
95
|
<%= column_info&.dig(:foreign_key).dig(:primary_key) %>
|
91
96
|
</td>
|
92
97
|
</tr>
|
93
98
|
|
94
|
-
<tr>
|
95
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
96
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
99
|
+
<tr class="border-b last:border-none border-base-content/10">
|
100
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">ON_UPDATE</td>
|
101
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80"><%= column_info&.dig(:foreign_key).dig(:on_update) %></td>
|
97
102
|
</tr>
|
98
103
|
|
99
|
-
<tr>
|
100
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
101
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
104
|
+
<tr class="border-b last:border-none border-base-content/10">
|
105
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">ON_DELETE</td>
|
106
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80"><%= column_info&.dig(:foreign_key).dig(:on_delete) %></td>
|
102
107
|
</tr>
|
103
108
|
|
104
109
|
<% end %>
|
105
110
|
|
106
111
|
<% if column_info&.dig(:indexes).present? %>
|
107
112
|
|
108
|
-
<tr>
|
109
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
110
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
113
|
+
<tr class="border-b last:border-none border-base-content/10">
|
114
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold"></td>
|
115
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80 "> </td>
|
111
116
|
</tr>
|
112
117
|
|
113
118
|
<% column_info&.dig(:indexes).each do |index| %>
|
114
|
-
<tr>
|
115
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
116
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
119
|
+
<tr class="border-b last:border-none border-base-content/10">
|
120
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Index Name</td>
|
121
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80">
|
117
122
|
<%= index[:name] %>
|
118
123
|
</td>
|
119
124
|
</tr>
|
120
|
-
<tr>
|
121
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
122
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
125
|
+
<tr class="border-b last:border-none border-base-content/10">
|
126
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content font-semibold">Unique</td>
|
127
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-base-content/80">
|
123
128
|
<%= index[:unique] %>
|
124
129
|
</td>
|
125
130
|
</tr>
|
@@ -149,9 +154,9 @@
|
|
149
154
|
</tr>
|
150
155
|
</thead>
|
151
156
|
|
152
|
-
<tbody class="main
|
157
|
+
<tbody class="main">
|
153
158
|
<% @data.rows.each do |row| %>
|
154
|
-
<tr class="
|
159
|
+
<tr class="border-b border-base-content/10 last:border-none hover:bg-base-100" >
|
155
160
|
<% row.each_with_index do |item, index| %>
|
156
161
|
<% truncated_item = item&.truncate(80) %>
|
157
162
|
<% column_name = @data.columns[index] %>
|
@@ -159,7 +164,7 @@
|
|
159
164
|
<td
|
160
165
|
data-column="<%= column_name %>"
|
161
166
|
data-data_type="<%= @columns_info.dig(column_name).dig(:type) %>"
|
162
|
-
class="px-6 py-4 text-sm
|
167
|
+
class="px-6 py-4 text-sm whitespace-nowrap"
|
163
168
|
>
|
164
169
|
|
165
170
|
<div class="flex justify-between gap-1">
|
@@ -176,7 +181,7 @@
|
|
176
181
|
data-fk_target_field_value="<%= truncated_item %>"
|
177
182
|
class="size-4 mt-0.5 hover:cursor-pointer flex-grow outline-none"
|
178
183
|
>
|
179
|
-
<%= image_tag "solid_litequeen/icons/spline.svg", class: "size-4 filter-
|
184
|
+
<%= image_tag "solid_litequeen/icons/spline.svg", class: "size-4 filter-orange" %>
|
180
185
|
</button>
|
181
186
|
|
182
187
|
<% end %>
|
@@ -189,7 +194,7 @@
|
|
189
194
|
<%= render "table-data-context-dialog", dialog_id: dialog_id, column_name: column_name, data: item %>
|
190
195
|
|
191
196
|
<button onclick="document.getElementById('<%= dialog_id %>').showModal()" class="cursor-pointer size-4 outline-none">
|
192
|
-
<%= image_tag "solid_litequeen/icons/circle-elipsis.svg", class: "size-4 dark:filter-white" %>
|
197
|
+
<%= image_tag "solid_litequeen/icons/circle-elipsis.svg", class: "opacity-60 size-4 dark:filter-white" %>
|
193
198
|
|
194
199
|
</button>
|
195
200
|
|
@@ -204,10 +209,10 @@
|
|
204
209
|
<% end %>
|
205
210
|
</tbody>
|
206
211
|
|
207
|
-
<dialog id="foreign-key-data" data-controller="dialog" class="bg-
|
212
|
+
<dialog id="foreign-key-data" data-controller="dialog" class="bg-base-200 border rounded border-base-content/20 w-[900px] m-auto overscroll-y-contain">
|
208
213
|
<div class="flex flex-row-reverse">
|
209
214
|
<button data-action="click->dialog#close" class="cursor-pointer mr-4 mt-2 outline-none">
|
210
|
-
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5 dark:filter-white" %>
|
215
|
+
<%= image_tag "solid_litequeen/icons/x.svg", class: "opacity-60 size-5 dark:filter-white" %>
|
211
216
|
</button>
|
212
217
|
|
213
218
|
</div>
|
data/config/routes.rb
CHANGED
@@ -5,5 +5,6 @@ SolidLitequeen::Engine.routes.draw do
|
|
5
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
|
6
6
|
get "download", to: "databases#download", as: "download"
|
7
7
|
end
|
8
|
+
get "command-palette-data", to: "databases#get_command_palette_data"
|
8
9
|
root to: "databases#index"
|
9
10
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_litequeen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vik Borges
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rails
|
@@ -170,6 +169,7 @@ files:
|
|
170
169
|
- app/assets/images/solid_litequeen/icons/database.svg
|
171
170
|
- app/assets/images/solid_litequeen/icons/info.svg
|
172
171
|
- app/assets/images/solid_litequeen/icons/spline.svg
|
172
|
+
- app/assets/images/solid_litequeen/icons/table.svg
|
173
173
|
- app/assets/images/solid_litequeen/icons/workflow.svg
|
174
174
|
- app/assets/images/solid_litequeen/icons/x.svg
|
175
175
|
- app/assets/stylesheets/solid_litequeen/application.css
|
@@ -180,14 +180,17 @@ files:
|
|
180
180
|
- app/javascript/solid_litequeen/application.js
|
181
181
|
- app/javascript/solid_litequeen/controllers/application.js
|
182
182
|
- app/javascript/solid_litequeen/controllers/clipboard_controller.js
|
183
|
+
- app/javascript/solid_litequeen/controllers/command_palette_controller.js
|
183
184
|
- app/javascript/solid_litequeen/controllers/dialog_controller.js
|
184
185
|
- app/javascript/solid_litequeen/controllers/index.js
|
186
|
+
- app/javascript/solid_litequeen/controllers/orbit_controller.js
|
185
187
|
- app/javascript/solid_litequeen/controllers/table_controller.js
|
186
188
|
- app/javascript/solid_litequeen/controllers/table_relations_controller.js
|
187
189
|
- app/jobs/solid_litequeen/application_job.rb
|
188
190
|
- app/mailers/solid_litequeen/application_mailer.rb
|
189
191
|
- app/models/solid_litequeen/application_record.rb
|
190
192
|
- app/views/layouts/solid_litequeen/application.html.erb
|
193
|
+
- app/views/solid_litequeen/_command-palette.html.erb
|
191
194
|
- app/views/solid_litequeen/_database-selector.html.erb
|
192
195
|
- app/views/solid_litequeen/_loading-animation.html.erb
|
193
196
|
- app/views/solid_litequeen/databases/_foreign-key-data.html.erb
|
@@ -209,7 +212,6 @@ metadata:
|
|
209
212
|
homepage_uri: https://solid.litequeen.com
|
210
213
|
source_code_uri: https://github.com/kivS/solid_litequeen
|
211
214
|
changelog_uri: https://solid.litequeen.com
|
212
|
-
post_install_message:
|
213
215
|
rdoc_options: []
|
214
216
|
require_paths:
|
215
217
|
- lib
|
@@ -224,8 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
226
|
- !ruby/object:Gem::Version
|
225
227
|
version: '0'
|
226
228
|
requirements: []
|
227
|
-
rubygems_version: 3.
|
228
|
-
signing_key:
|
229
|
+
rubygems_version: 3.6.7
|
229
230
|
specification_version: 4
|
230
231
|
summary: Manage SQLite databases on your server with ease
|
231
232
|
test_files: []
|