dbviewer 0.6.7 → 0.7.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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
  3. data/app/assets/javascripts/dbviewer/home.js +287 -0
  4. data/app/assets/javascripts/dbviewer/layout.js +194 -0
  5. data/app/assets/javascripts/dbviewer/query.js +277 -0
  6. data/app/assets/javascripts/dbviewer/table.js +1563 -0
  7. data/app/assets/stylesheets/dbviewer/application.css +1460 -21
  8. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
  9. data/app/assets/stylesheets/dbviewer/home.css +229 -0
  10. data/app/assets/stylesheets/dbviewer/logs.css +64 -0
  11. data/app/assets/stylesheets/dbviewer/query.css +171 -0
  12. data/app/assets/stylesheets/dbviewer/table.css +1144 -0
  13. data/app/views/dbviewer/connections/index.html.erb +0 -30
  14. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
  15. data/app/views/dbviewer/home/index.html.erb +9 -499
  16. data/app/views/dbviewer/logs/index.html.erb +5 -220
  17. data/app/views/dbviewer/tables/index.html.erb +0 -65
  18. data/app/views/dbviewer/tables/query.html.erb +129 -565
  19. data/app/views/dbviewer/tables/show.html.erb +4 -2429
  20. data/app/views/layouts/dbviewer/application.html.erb +13 -1544
  21. data/lib/dbviewer/version.rb +1 -1
  22. metadata +12 -7
  23. data/app/assets/javascripts/dbviewer/connections.js +0 -70
  24. data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
  25. data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
  26. data/app/views/dbviewer/connections/new.html.erb +0 -79
  27. data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
@@ -0,0 +1,277 @@
1
+ import * as monaco from "https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/+esm";
2
+
3
+ // Helper function to decode HTML entities
4
+ function decodeHTMLEntities(text) {
5
+ const textarea = document.createElement("textarea");
6
+ textarea.innerHTML = text;
7
+ return textarea.value;
8
+ }
9
+
10
+ // Get initial query value from a data attribute to avoid string escaping issues
11
+ const initialQueryEncoded = document
12
+ .getElementById("monaco-editor")
13
+ .getAttribute("data-initial-query");
14
+ const initialQuery = decodeHTMLEntities(initialQueryEncoded);
15
+
16
+ // Determine initial theme based on document theme
17
+ const initialTheme =
18
+ document.documentElement.getAttribute("data-bs-theme") === "dark"
19
+ ? "vs-dark"
20
+ : "vs";
21
+
22
+ // Initialize Monaco Editor with SQL syntax highlighting
23
+ const editor = monaco.editor.create(document.getElementById("monaco-editor"), {
24
+ value: initialQuery || "",
25
+ language: "sql",
26
+ theme: initialTheme,
27
+ automaticLayout: true, // Resize automatically
28
+ minimap: { enabled: true },
29
+ scrollBeyondLastLine: false,
30
+ lineNumbers: "on",
31
+ renderLineHighlight: "all",
32
+ tabSize: 2,
33
+ wordWrap: "on",
34
+ formatOnPaste: true,
35
+ formatOnType: true,
36
+ autoIndent: "full",
37
+ folding: true,
38
+ glyphMargin: false,
39
+ suggestOnTriggerCharacters: true,
40
+ fixedOverflowWidgets: true,
41
+ quickSuggestions: {
42
+ other: true,
43
+ comments: true,
44
+ strings: true,
45
+ },
46
+ suggest: {
47
+ showKeywords: true,
48
+ showSnippets: true,
49
+ preview: true,
50
+ showIcons: true,
51
+ maxVisibleSuggestions: 12,
52
+ },
53
+ });
54
+
55
+ // Theme change listener
56
+ document.addEventListener("dbviewerThemeChanged", (event) => {
57
+ const newTheme = event.detail.theme === "dark" ? "vs-dark" : "vs";
58
+ monaco.editor.setTheme(newTheme);
59
+
60
+ // Update editor container border color
61
+ const editorContainer = document.querySelector(".monaco-editor-container");
62
+ if (editorContainer) {
63
+ editorContainer.style.borderColor =
64
+ event.detail.theme === "dark" ? "#495057" : "#ced4da";
65
+ }
66
+
67
+ // Update status bar styling based on theme
68
+ updateStatusBarTheme(event.detail.theme);
69
+
70
+ // Update example query buttons
71
+ const exampleQueries = document.querySelectorAll(".example-query");
72
+ exampleQueries.forEach((query) => {
73
+ if (event.detail.theme === "dark") {
74
+ query.style.borderColor = "#495057";
75
+ if (!query.classList.contains("btn-primary")) {
76
+ query.style.color = "#f8f9fa";
77
+ }
78
+ } else {
79
+ query.style.borderColor = "#ced4da";
80
+ if (!query.classList.contains("btn-primary")) {
81
+ query.style.color = "";
82
+ }
83
+ }
84
+ });
85
+ });
86
+
87
+ function updateStatusBarTheme(theme) {
88
+ const statusBar = document.querySelector(".monaco-status-bar");
89
+ if (!statusBar) return;
90
+
91
+ if (theme === "dark") {
92
+ statusBar.style.backgroundColor = "#343a40";
93
+ statusBar.style.borderColor = "#495057";
94
+ statusBar.style.color = "#adb5bd";
95
+ } else {
96
+ statusBar.style.backgroundColor = "#f8f9fa";
97
+ statusBar.style.borderColor = "#ced4da";
98
+ statusBar.style.color = "#6c757d";
99
+ }
100
+ }
101
+
102
+ // Set up SQL intellisense with table/column completions
103
+ const tableName = document.getElementById("table_name").value;
104
+ const columns = JSON.parse(document.getElementById("columns_data").value);
105
+
106
+ // Register SQL completion providers
107
+ monaco.languages.registerCompletionItemProvider("sql", {
108
+ provideCompletionItems: function (model, position) {
109
+ const textUntilPosition = model.getValueInRange({
110
+ startLineNumber: position.lineNumber,
111
+ startColumn: 1,
112
+ endLineNumber: position.lineNumber,
113
+ endColumn: position.column,
114
+ });
115
+
116
+ const suggestions = [];
117
+
118
+ // Add table name suggestion
119
+ suggestions.push({
120
+ label: tableName,
121
+ kind: monaco.languages.CompletionItemKind.Class,
122
+ insertText: tableName,
123
+ detail: "Table name",
124
+ });
125
+
126
+ // Add column name suggestions
127
+ columns.forEach((col) => {
128
+ suggestions.push({
129
+ label: col.name,
130
+ kind: monaco.languages.CompletionItemKind.Field,
131
+ insertText: col.name,
132
+ detail: `Column (${col.type})`,
133
+ });
134
+ });
135
+
136
+ // Add common SQL keywords
137
+ const keywords = [
138
+ { label: "SELECT", insertText: "SELECT " },
139
+ { label: "FROM", insertText: "FROM " },
140
+ { label: "WHERE", insertText: "WHERE " },
141
+ { label: "ORDER BY", insertText: "ORDER BY " },
142
+ { label: "GROUP BY", insertText: "GROUP BY " },
143
+ { label: "HAVING", insertText: "HAVING " },
144
+ { label: "LIMIT", insertText: "LIMIT " },
145
+ { label: "JOIN", insertText: "JOIN " },
146
+ { label: "LEFT JOIN", insertText: "LEFT JOIN " },
147
+ { label: "INNER JOIN", insertText: "INNER JOIN " },
148
+ ];
149
+
150
+ keywords.forEach((kw) => {
151
+ suggestions.push({
152
+ label: kw.label,
153
+ kind: monaco.languages.CompletionItemKind.Keyword,
154
+ insertText: kw.insertText,
155
+ });
156
+ });
157
+
158
+ return { suggestions };
159
+ },
160
+ });
161
+
162
+ // Handle form submission - transfer content to hidden input before submitting
163
+ document
164
+ .getElementById("sql-query-form")
165
+ .addEventListener("submit", function (event) {
166
+ // Stop the form from submitting immediately
167
+ event.preventDefault();
168
+
169
+ // Get the query value from the editor and set it to the hidden input
170
+ const queryValue = editor.getValue();
171
+ document.getElementById("query-input").value = queryValue;
172
+
173
+ // Now manually submit the form
174
+ this.submit();
175
+ });
176
+
177
+ // Make example queries clickable
178
+ document.querySelectorAll(".example-query").forEach((example) => {
179
+ example.style.cursor = "pointer";
180
+ example.addEventListener("click", () => {
181
+ const query = decodeHTMLEntities(example.textContent);
182
+ editor.setValue(query);
183
+ editor.focus();
184
+ });
185
+ });
186
+
187
+ // Setup editor keybindings
188
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
189
+ // Get the query value from the editor and set it to the hidden input
190
+ const queryValue = editor.getValue();
191
+ document.getElementById("query-input").value = queryValue;
192
+
193
+ // Submit the form
194
+ document.getElementById("sql-query-form").submit();
195
+ });
196
+
197
+ // Add keyboard shortcuts for common SQL statements
198
+ editor.addAction({
199
+ id: "insert-select-all",
200
+ label: "Insert SELECT * FROM statement",
201
+ keybindings: [
202
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyS,
203
+ ],
204
+ run: function () {
205
+ editor.trigger("keyboard", "type", {
206
+ text: `SELECT * FROM ${tableName} LIMIT 100`,
207
+ });
208
+ return null;
209
+ },
210
+ });
211
+
212
+ editor.addAction({
213
+ id: "insert-where",
214
+ label: "Insert WHERE clause",
215
+ keybindings: [
216
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyW,
217
+ ],
218
+ run: function () {
219
+ editor.trigger("keyboard", "type", { text: " WHERE " });
220
+ return null;
221
+ },
222
+ });
223
+
224
+ editor.addAction({
225
+ id: "toggle-table-structure",
226
+ label: "Toggle Table Structure Reference",
227
+ keybindings: [
228
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyT,
229
+ ],
230
+ run: function () {
231
+ // Use Bootstrap's collapse API to toggle
232
+ bootstrap.Collapse.getOrCreateInstance(
233
+ document.getElementById("tableStructureContent")
234
+ ).toggle();
235
+ return null;
236
+ },
237
+ });
238
+
239
+ // Create a status bar showing cursor position and columns info
240
+ const statusBarDiv = document.createElement("div");
241
+ statusBarDiv.className = "monaco-status-bar";
242
+ statusBarDiv.innerHTML = `<div class="status-info">Ready</div>
243
+ <div class="column-info">Table: ${tableName} (${columns.length} columns)</div>`;
244
+ document.getElementById("monaco-editor").after(statusBarDiv);
245
+
246
+ // Apply initial theme to status bar
247
+ const currentTheme =
248
+ document.documentElement.getAttribute("data-bs-theme") || "light";
249
+ updateStatusBarTheme(currentTheme);
250
+
251
+ // Update status bar with cursor position
252
+ editor.onDidChangeCursorPosition((e) => {
253
+ const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`;
254
+ statusBarDiv.querySelector(".status-info").textContent = position;
255
+ });
256
+
257
+ // Focus the editor when page loads
258
+ window.addEventListener("load", () => {
259
+ editor.focus();
260
+ });
261
+
262
+ // Toggle icon when table structure collapses or expands
263
+ document
264
+ .getElementById("tableStructureContent")
265
+ .addEventListener("show.bs.collapse", function () {
266
+ document
267
+ .querySelector("#tableStructureHeader button i")
268
+ .classList.replace("bi-chevron-down", "bi-chevron-up");
269
+ });
270
+
271
+ document
272
+ .getElementById("tableStructureContent")
273
+ .addEventListener("hide.bs.collapse", function () {
274
+ document
275
+ .querySelector("#tableStructureHeader button i")
276
+ .classList.replace("bi-chevron-up", "bi-chevron-down");
277
+ });