From a1918f92fe5ded58c0f0ae269416236d8529dda3 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Wed, 4 Feb 2026 14:53:13 +0100 Subject: [PATCH 01/11] feat: Update the dependency version, so the pod is usable --- Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile b/Podfile index a4c28aa..49ce6fa 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ target 'SQLiteViewer' do use_frameworks! # Pods for SQLiteViewer - pod 'Http.swift', '~> 2.1' + pod 'Http.swift', '~> 2.2.1' pod 'SQLite.swift', '~> 0.11.5' target 'SQLiteViewerTests' do From b4eef0fbfe4d5d1e4443600df50880ffd9ab8b43 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Wed, 4 Feb 2026 15:00:57 +0100 Subject: [PATCH 02/11] fix: We need to update another file too --- SQLite.viewer.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.viewer.podspec b/SQLite.viewer.podspec index 3549237..7cd98ef 100644 --- a/SQLite.viewer.podspec +++ b/SQLite.viewer.podspec @@ -12,6 +12,6 @@ Pod::Spec.new do |s| s.source_files = 'Sources/*.swift' s.resource_bundles = { 'com.biatoms.sqlite-viewer.assets' => ['Sources/**/*.{js,css,ico,html}'] } - s.dependency 'Http.swift', '~> 2.1.1' + s.dependency 'Http.swift', '~> 2.2.1' s.dependency 'SQLite.swift', '>= 0.11.5' end From aeca4df173b820818865be404150b786204b5c9f Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Wed, 4 Feb 2026 17:34:12 +0100 Subject: [PATCH 03/11] fix: iOS 26 compatibility --- Sources/SQLiteViewer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLiteViewer.swift b/Sources/SQLiteViewer.swift index 909a76e..e6dfdcb 100644 --- a/Sources/SQLiteViewer.swift +++ b/Sources/SQLiteViewer.swift @@ -10,7 +10,7 @@ import HttpSwift import SQLite open class SQLiteViewer { - open static var shared = SQLiteViewer() + public static var shared = SQLiteViewer() public var assetDir: String = "" public var dbDir: String = "" { From 42798990cc12e9f71278692f7b93f301a7731a38 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Wed, 4 Feb 2026 17:34:36 +0100 Subject: [PATCH 04/11] feat: Add ability to show Blob data as base64-encoded string --- Sources/DatabaseController.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/DatabaseController.swift b/Sources/DatabaseController.swift index 889add1..dd39c9c 100644 --- a/Sources/DatabaseController.swift +++ b/Sources/DatabaseController.swift @@ -70,10 +70,19 @@ open class DatabaseController { func toArray(_ elements: S) -> Array { // resolves ambiguity return Array(elements) } - + var rows = toArray(statement) + if !JSONSerialization.isValidJSONObject(rows) { + for (rowIndex, row) in rows.enumerated() { + for (cellIndex, cell) in row.enumerated() { + if let cellBlob = cell as? SQLite.Blob { + rows[rowIndex][cellIndex] = Data(cellBlob.bytes).base64EncodedString() + } + } + } + } return [ "columns": statement.columnNames, - "rows": toArray(statement) + "rows": rows ] } } From 14041bb7820bed23835c9eaee5dae2d1f2b533ed Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 10:29:43 +0100 Subject: [PATCH 05/11] Feat: Add ability to edit existing values, add missing blob key And also improve the styling --- Sources/Assets/css/app.css | 50 ++++++++++++++++++++++++++++++++ Sources/Assets/index.html | 13 ++++----- Sources/Assets/js/app.js | 43 ++++++++++++++++++++------- Sources/DatabaseController.swift | 5 ++++ 4 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 Sources/Assets/css/app.css diff --git a/Sources/Assets/css/app.css b/Sources/Assets/css/app.css new file mode 100644 index 0000000..e54077e --- /dev/null +++ b/Sources/Assets/css/app.css @@ -0,0 +1,50 @@ +#query-container { + position: fixed; + left: 0px; + top: 0px; + width: 100%; + height: 50px; + z-index: 2; + background: white; +} +#query-container>div { + display: flex; + vertical-align: middle; +} +#query-container>div>textarea { + flex-grow: 1; + min-width: 50%; +} +.row { + margin-top: 50px; +} +table thead { + position: sticky; + top: 50px; + z-index: 1; +} +table thead td, table thead th { + background: white; +} +table tr td { + position: relative; +} +table tr td .edit-item { + opacity: 0; + position: absolute; + top: 0px; + right: 0px; +} +table tr:hover td .edit-item { + opacity: 1; +} +table tbody td span { + display: inline-block; +} +table tbody td.is-long span { + width: 50vw; + word-break: break-all; +} +table tbody td.is-null { + color: gray; +} diff --git a/Sources/Assets/index.html b/Sources/Assets/index.html index f188d35..3e3b742 100755 --- a/Sources/Assets/index.html +++ b/Sources/Assets/index.html @@ -8,19 +8,16 @@ + + -
+
-
- -
+
-
- -
@@ -28,6 +25,7 @@
+
@@ -46,6 +44,5 @@
- diff --git a/Sources/Assets/js/app.js b/Sources/Assets/js/app.js index c505719..26d9e46 100644 --- a/Sources/Assets/js/app.js +++ b/Sources/Assets/js/app.js @@ -57,11 +57,11 @@ function refreshTableList() { function refreshTableData() { $('#table-data').empty(); Api.getTableData(currentDatabase, currentTable, function (data) { - displayTable(data); + displayTable(data, currentTable); }) } -function displayTable(data) { +function displayTable(data, tablename) { var elem = $('#table-data'); elem.empty(); var table = $(''); @@ -78,28 +78,51 @@ function displayTable(data) { data.rows.forEach(function (row) { var rowElem = $(''); tableBody.append(rowElem); - row.forEach(function (data) { - rowElem.append($('
').text(data)); + row.forEach(function (value, index) { + let origValue = value; + if (data.blobs.indexOf(index) !== -1 && value) { + value = 'blob: ' + value; + } + let cell = $('').append($('').text(value ?? 'null')); + if (value == null) { + cell.addClass('is-null'); + } else if (value?.length > 200) { + cell.addClass('is-long'); + } + rowElem.append(cell); + if (tablename && data.columns[0] === 'Z_PK') { + // This is a CoreData helper + cell.append($('').addClass('edit-item').text('edit').attr('href', '#').on('click', function(e) { + $('#query').val('UPDATE '+tablename+' SET '+data.columns[index]+' = \''+escapeSqlite(origValue)+'\' WHERE '+data.columns[0]+' = \''+escapeSqlite(row[0])+'\''); + message('SQL updated'); + e.preventDefault(true); + e.stopPropagation(true); + return false; + })); + } }) }); } - +function escapeSqlite(string) { + return string.replace(/\'/g, '\'\''); +} function executeQuery(query) { Api.executeQuery(currentDatabase, query, function (data) { if (data.hasOwnProperty('affected_rows')) { - message(data.affected_rows + " rows are affected."); + message(data.affected_rows + ' rows are affected.'); } else { displayTable(data); - message("Query completed."); + message('Query completed.'); } }); } function download() { Api.downloadDatabase(currentDatabase, function() { - message(currentDatabase + " is downloaded.") + message(currentDatabase + ' is downloaded.'); }); } - -init(); +$(document).ready(function () { + init(); +}) diff --git a/Sources/DatabaseController.swift b/Sources/DatabaseController.swift index dd39c9c..3ef2459 100644 --- a/Sources/DatabaseController.swift +++ b/Sources/DatabaseController.swift @@ -70,11 +70,15 @@ open class DatabaseController { func toArray(_ elements: S) -> Array { // resolves ambiguity return Array(elements) } + var blobs: [Int] = [] var rows = toArray(statement) if !JSONSerialization.isValidJSONObject(rows) { for (rowIndex, row) in rows.enumerated() { for (cellIndex, cell) in row.enumerated() { if let cellBlob = cell as? SQLite.Blob { + if !blobs.contains(cellIndex) { + blobs.append(cellIndex) + } rows[rowIndex][cellIndex] = Data(cellBlob.bytes).base64EncodedString() } } @@ -82,6 +86,7 @@ open class DatabaseController { } return [ "columns": statement.columnNames, + "blobs": blobs, "rows": rows ] } From d51fb44782dce62f0b97e2064b5a5320d8d9c1cb Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 10:38:50 +0100 Subject: [PATCH 06/11] fix: Not all is a string --- Sources/Assets/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Assets/js/app.js b/Sources/Assets/js/app.js index 26d9e46..6b2304e 100644 --- a/Sources/Assets/js/app.js +++ b/Sources/Assets/js/app.js @@ -104,7 +104,7 @@ function displayTable(data, tablename) { }); } function escapeSqlite(string) { - return string.replace(/\'/g, '\'\''); + return (''+string).replace(/\'/g, '\'\''); } function executeQuery(query) { From 0783bc41ecaccaaf759a1bfcb2d58841182e51b7 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 10:57:46 +0100 Subject: [PATCH 07/11] fix: Page overflowing --- Sources/Assets/css/app.css | 21 ++++++++++++++++++++- Sources/Assets/index.html | 6 +++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Sources/Assets/css/app.css b/Sources/Assets/css/app.css index e54077e..a46984f 100644 --- a/Sources/Assets/css/app.css +++ b/Sources/Assets/css/app.css @@ -15,8 +15,27 @@ flex-grow: 1; min-width: 50%; } +.panel { + border: 1px solid #ddd; + background: white; + margin-bottom: 20px; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} .row { - margin-top: 50px; + margin: 50px 0 0; + display: grid; + grid-template-columns: 1fr 1fr 8fr; + gap: 10px; +} +.row>div.databases { + grid-area: 1/1/1/1; +} +.row>div.tables { + grid-area: 1/2/1/2; +} +.row>div.data { + grid-area: 1/3/1/3; } table thead { position: sticky; diff --git a/Sources/Assets/index.html b/Sources/Assets/index.html index 3e3b742..3bbfc49 100755 --- a/Sources/Assets/index.html +++ b/Sources/Assets/index.html @@ -19,7 +19,7 @@
-
+
Databases
@@ -27,7 +27,7 @@
-
+
Tables
@@ -35,7 +35,7 @@
-
+
Data
From a1720e5c083ab9dc4cecb480b0819c902c11b180 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 11:01:53 +0100 Subject: [PATCH 08/11] fix: We don't want to override bootstrap, if it won't be dropped --- Sources/Assets/css/app.css | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/Assets/css/app.css b/Sources/Assets/css/app.css index a46984f..479e8a5 100644 --- a/Sources/Assets/css/app.css +++ b/Sources/Assets/css/app.css @@ -15,13 +15,6 @@ flex-grow: 1; min-width: 50%; } -.panel { - border: 1px solid #ddd; - background: white; - margin-bottom: 20px; - border-radius: 4px; - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} .row { margin: 50px 0 0; display: grid; From 8b32125ce8f4eb17611450e6504921dd05993fef Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 11:05:05 +0100 Subject: [PATCH 09/11] fix: the button has query all-lowercase --- Sources/Assets/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Assets/index.html b/Sources/Assets/index.html index 3bbfc49..e1b5415 100755 --- a/Sources/Assets/index.html +++ b/Sources/Assets/index.html @@ -14,7 +14,7 @@
- +
From 3b6745ad9a265e36f0650927f6d22a69efa9e000 Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 11:11:59 +0100 Subject: [PATCH 10/11] fix: The scripts should have 4 spaces. --- Sources/Assets/js/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Assets/js/app.js b/Sources/Assets/js/app.js index 6b2304e..074b55d 100644 --- a/Sources/Assets/js/app.js +++ b/Sources/Assets/js/app.js @@ -104,7 +104,7 @@ function displayTable(data, tablename) { }); } function escapeSqlite(string) { - return (''+string).replace(/\'/g, '\'\''); + return (''+string).replace(/\'/g, '\'\''); } function executeQuery(query) { @@ -124,5 +124,5 @@ function download() { }); } $(document).ready(function () { - init(); + init(); }) From 50bde1dff98d0cec951aa62e39e35d2629f0179b Mon Sep 17 00:00:00 2001 From: Martin Letacek Date: Mon, 9 Feb 2026 11:16:56 +0100 Subject: [PATCH 11/11] fix: the replace was not workin --- Sources/Assets/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Assets/js/app.js b/Sources/Assets/js/app.js index 074b55d..ab6ce90 100644 --- a/Sources/Assets/js/app.js +++ b/Sources/Assets/js/app.js @@ -104,7 +104,7 @@ function displayTable(data, tablename) { }); } function escapeSqlite(string) { - return (''+string).replace(/\'/g, '\'\''); + return (''+string).replace(/'/g, '\'\''); } function executeQuery(query) {