Skip to content

Commit 73e74d0

Browse files
author
Bhargav Gondaliya
committed
Initial commit of SQLite Viewer project
0 parents  commit 73e74d0

5 files changed

Lines changed: 658 additions & 0 deletions

File tree

index.html

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>SQLite Browser</title>
7+
8+
<link rel="preconnect" href="https://fonts.googleapis.com">
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
11+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
12+
13+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css">
14+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/theme/material-darker.min.css">
15+
16+
<link rel="stylesheet" href="style.css">
17+
</head>
18+
<body>
19+
<div class="container">
20+
<header>
21+
<h1><i class="fa-solid fa-database"></i> SQLite Viewer</h1>
22+
<div class="header-controls">
23+
<label for="dbFile" class="custom-file-upload">
24+
<i class="fa-solid fa-folder-open"></i> Open Database
25+
</label>
26+
<input type="file" id="dbFile" accept=".sqlite, .db, .sqlite3">
27+
<button id="theme-toggle" aria-label="Toggle dark mode"><i class="fa-solid fa-moon"></i></button>
28+
</div>
29+
</header>
30+
31+
<p id="message" class="message"></p>
32+
33+
<div id="db-viewer-container">
34+
<div id="sidebar">
35+
<div class="sidebar-header">
36+
<h2><i class="fa-solid fa-table-list"></i> Tables</h2>
37+
</div>
38+
<ul id="table-list">
39+
<li class="no-data">Load a database to see tables</li>
40+
</ul>
41+
</div>
42+
<div id="main-content">
43+
<div id="query-section">
44+
<div class="query-header">
45+
<h2><i class="fa-solid fa-terminal"></i> SQL Query</h2>
46+
<button id="execute-query" class="execute-btn" disabled>
47+
<i class="fa-solid fa-play"></i> Execute
48+
</button>
49+
</div>
50+
<div id="sqlQueryContainer"></div>
51+
</div>
52+
<div id="results-container">
53+
<div id="results-header">
54+
<h3><i class="fa-solid fa-table-cells"></i> Results</h3>
55+
<div id="query-status"></div>
56+
</div>
57+
<div id="results">
58+
<p class="no-data">Results will appear here</p>
59+
</div>
60+
</div>
61+
</div>
62+
</div>
63+
</div>
64+
65+
<script src="sql.js"></script>
66+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js"></script>
67+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/mode/sql/sql.min.js"></script>
68+
69+
<script src="script.js"></script>
70+
</body>
71+
</html>

script.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// --- Global Variables ---
2+
let db = null; // To hold the database instance
3+
let editor; // To hold the CodeMirror editor instance
4+
5+
// --- DOM Element References ---
6+
const dbFileInput = document.getElementById('dbFile');
7+
const messageElem = document.getElementById('message');
8+
const tableListElem = document.getElementById('table-list');
9+
const executeQueryBtn = document.getElementById('execute-query');
10+
const resultsElem = document.getElementById('results');
11+
const dbViewerContainer = document.getElementById('db-viewer-container');
12+
const queryStatusElem = document.getElementById('query-status');
13+
const themeToggle = document.getElementById('theme-toggle');
14+
15+
// --- Initialization on DOM Load ---
16+
document.addEventListener('DOMContentLoaded', () => {
17+
// Initialize the CodeMirror SQL editor
18+
editor = CodeMirror(document.getElementById('sqlQueryContainer'), {
19+
mode: 'text/x-sql',
20+
lineNumbers: true,
21+
theme: 'default',
22+
value: '-- Load a database and click a table to start exploring',
23+
readOnly: 'nocursor' // Initially disable the editor
24+
});
25+
26+
// Set up the theme toggler
27+
themeToggle.addEventListener('click', toggleTheme);
28+
29+
// Set initial theme based on user's system preference
30+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
31+
document.body.classList.add('dark-mode');
32+
updateTheme();
33+
}
34+
});
35+
36+
37+
// --- Event Listeners ---
38+
dbFileInput.addEventListener('change', handleFileSelect);
39+
executeQueryBtn.addEventListener('click', executeQuery);
40+
41+
42+
// --- Core Functions ---
43+
44+
/**
45+
* Toggles the color theme between light and dark mode.
46+
*/
47+
function toggleTheme() {
48+
document.body.classList.toggle('dark-mode');
49+
updateTheme();
50+
}
51+
52+
/**
53+
* Updates UI elements based on the current theme (e.g., editor, icon).
54+
*/
55+
function updateTheme() {
56+
const isDarkMode = document.body.classList.contains('dark-mode');
57+
editor.setOption('theme', isDarkMode ? 'material-darker' : 'default');
58+
themeToggle.innerHTML = isDarkMode ? '<i class="fa-solid fa-sun"></i>' : '<i class="fa-solid fa-moon"></i>';
59+
}
60+
61+
/**
62+
* Handles the database file selection, loading, and initialization.
63+
*/
64+
async function handleFileSelect(event) {
65+
const file = event.target.files[0];
66+
if (!file) return;
67+
68+
resetUI();
69+
messageElem.textContent = `Loading ${file.name}...`;
70+
71+
try {
72+
// Initialize sql.js library, pointing to the wasm file
73+
const SQL = await initSqlJs({ locateFile: () => `sql-wasm.wasm` });
74+
75+
const reader = new FileReader();
76+
reader.onload = async (e) => {
77+
try {
78+
const Uints = new Uint8Array(e.target.result);
79+
db = new SQL.Database(Uints);
80+
81+
// Enable UI elements now that the database is loaded
82+
editor.setOption('readOnly', false);
83+
executeQueryBtn.disabled = false;
84+
85+
messageElem.textContent = `${file.name} loaded successfully!`;
86+
messageElem.style.color = 'var(--color-success)';
87+
88+
loadTables();
89+
90+
} catch (err) {
91+
showError(`Error loading database: ${err.message}`);
92+
}
93+
};
94+
reader.readAsArrayBuffer(file);
95+
96+
} catch (err) {
97+
showError(`Failed to initialize SQL.js: ${err.message}. Ensure sql-wasm.wasm is in the same folder.`);
98+
}
99+
}
100+
101+
/**
102+
* Fetches and displays the list of tables from the loaded database.
103+
*/
104+
function loadTables() {
105+
if (!db) return;
106+
try {
107+
const stmt = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';");
108+
const tables = [];
109+
while (stmt.step()) {
110+
tables.push(stmt.get()[0]);
111+
}
112+
stmt.free();
113+
114+
tableListElem.innerHTML = '';
115+
if (tables.length === 0) {
116+
tableListElem.innerHTML = '<li class="no-data">No tables found.</li>';
117+
return;
118+
}
119+
120+
tables.forEach(tableName => {
121+
const li = document.createElement('li');
122+
li.innerHTML = `<i class="fa-solid fa-table"></i>${tableName}`;
123+
li.addEventListener('click', (e) => {
124+
document.querySelectorAll('#table-list li').forEach(item => item.classList.remove('active'));
125+
e.currentTarget.classList.add('active');
126+
editor.setValue(`SELECT * FROM "${tableName}" LIMIT 100;`);
127+
executeQuery();
128+
});
129+
tableListElem.appendChild(li);
130+
});
131+
132+
// Automatically click the first table to show its contents
133+
if (tables.length > 0) {
134+
tableListElem.firstElementChild.click();
135+
}
136+
137+
} catch (error) {
138+
showError(`Error loading tables: ${error.message}`);
139+
}
140+
}
141+
142+
/**
143+
* Executes the SQL query from the editor and displays the results.
144+
*/
145+
function executeQuery() {
146+
if (!db) {
147+
showError("No database loaded.");
148+
return;
149+
}
150+
151+
const query = editor.getValue().trim();
152+
if (!query) {
153+
showError("Please enter a SQL query.");
154+
return;
155+
}
156+
157+
resultsElem.innerHTML = '';
158+
queryStatusElem.textContent = 'Executing...';
159+
160+
// Use a small timeout to allow the UI to update before a long query blocks the main thread
161+
setTimeout(() => {
162+
try {
163+
const startTime = performance.now();
164+
const res = db.exec(query);
165+
const endTime = performance.now();
166+
const duration = (endTime - startTime).toFixed(2);
167+
168+
if (!res.length) {
169+
resultsElem.innerHTML = '<p class="no-data">Query executed successfully, but returned no data.</p>';
170+
queryStatusElem.textContent = `Query took ${duration} ms`;
171+
return;
172+
}
173+
174+
const { columns, values } = res[0];
175+
resultsElem.innerHTML = createTableHTML(columns, values);
176+
queryStatusElem.textContent = `${values.length} rows returned in ${duration} ms.`;
177+
178+
} catch (error) {
179+
resultsElem.innerHTML = `<p class="no-data" style="color: var(--color-danger);">${error.message}</p>`;
180+
queryStatusElem.textContent = `Error executing query.`;
181+
}
182+
}, 10);
183+
}
184+
185+
186+
// --- UI Helper Functions ---
187+
188+
/**
189+
* Resets the UI to its initial state when a new file is loaded.
190+
*/
191+
function resetUI() {
192+
db = null;
193+
tableListElem.innerHTML = '<li class="no-data">Load a database to see tables</li>';
194+
resultsElem.innerHTML = '<p class="no-data">Results will appear here</p>';
195+
queryStatusElem.textContent = '';
196+
editor.setValue('-- Load a database and click a table to start exploring');
197+
editor.setOption('readOnly', 'nocursor');
198+
executeQueryBtn.disabled = true;
199+
}
200+
201+
/**
202+
* Displays an error message to the user.
203+
* @param {string} message The error message to display.
204+
*/
205+
function showError(message) {
206+
messageElem.textContent = message;
207+
messageElem.style.color = 'var(--color-danger)';
208+
console.error(message);
209+
}
210+
211+
/**
212+
* Generates an HTML table from SQL query results.
213+
* @param {string[]} columns Array of column names.
214+
* @param {Array<any[]>} values 2D array of row values.
215+
* @returns {string} The HTML string for the results table.
216+
*/
217+
function createTableHTML(columns, values) {
218+
let tableHtml = '<table><thead><tr>';
219+
columns.forEach(col => tableHtml += `<th>${col}</th>`);
220+
tableHtml += '</tr></thead><tbody>';
221+
values.forEach(row => {
222+
tableHtml += '<tr>';
223+
row.forEach(cell => tableHtml += `<td>${cell !== null ? cell : 'NULL'}</td>`);
224+
tableHtml += '</tr>';
225+
});
226+
tableHtml += '</tbody></table>';
227+
return tableHtml;
228+
}

sql-wasm.wasm

644 KB
Binary file not shown.

0 commit comments

Comments
 (0)