diff --git a/SQLiteDB/LibSQLite.ts b/SQLiteDB/LibSQLite.ts index d7b4c04..79e0ae9 100644 --- a/SQLiteDB/LibSQLite.ts +++ b/SQLiteDB/LibSQLite.ts @@ -164,25 +164,27 @@ namespace OS { return this.request(rq); } - insert(table_name:string, record: GenericObject): Promise + insert(table_name:string, record: GenericObject, pk:string): Promise { let rq = { action: 'insert', args: { table_name, - record + record, + pk } } return this.request(rq); } - update(table_name:string, record: GenericObject): Promise + update(table_name:string, record: GenericObject, pk:string ): Promise { let rq = { action: 'update', args: { table_name, - record + record, + pk } } return this.request(rq); @@ -317,20 +319,33 @@ namespace OS { try { await this._handle.init(); - const d = {result: this._handle.fileinfo(), error: false}; + let d = { + result: { + file:this._handle.fileinfo(), + schema: undefined + }, error: false}; if(this._table_name) { const data = await this._handle.get_table_scheme(this._table_name); if(data.length == 0) { - d.result.scheme = undefined + d.result.schema = undefined } else { - d.result.scheme = {} + d.result.schema = { + fields: [], + types: {}, + pk: undefined + } + d.result.schema.fields = data.map(e=>e.name); for(let v of data) { - d.result.scheme[v.name] = v.type; + d.result.schema.types[v.name] = v.type; + if(v.pk) + { + d.result.schema.pk = v.name; + } } } } @@ -358,7 +373,7 @@ namespace OS { protected _rd(user_data: any): Promise { return new Promise(async (resolve, reject) => { try{ - if(this._table_name && ! this.info.scheme) + if(this._table_name && ! this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } @@ -422,17 +437,18 @@ namespace OS { { throw new Error(__("No data to submit to remote database, please check the `cache` field").__()); } + await this.onready(); if(this._id && this._table_name) { this.cache.id = this._id; - const ret = await this._handle.update(this._table_name, this.cache); + const ret = await this._handle.update(this._table_name, this.cache, this.info.schema.pk); resolve({result:ret, error: false}); return } if(this._table_name) { - const ret = await this._handle.insert(this._table_name, this.cache); + const ret = await this._handle.insert(this._table_name, this.cache, this.info.schema.pk); resolve({result:ret, error: false}); return } @@ -458,7 +474,7 @@ namespace OS { protected _rm(user_data: any): Promise { return new Promise(async (resolve, reject) => { try { - if(this._table_name && ! this.info.scheme) + if(this._table_name && ! this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } diff --git a/SQLiteDB/README.md b/SQLiteDB/README.md index 819ff91..598b8f3 100644 --- a/SQLiteDB/README.md +++ b/SQLiteDB/README.md @@ -1,3 +1,11 @@ # SQLiteDB -"mimes":["application/vnd.sqlite3"], +This package contains the SQLiteDB API binding for AntOS applications +and a simple sqlite3 browser application that uses the library as reference + +Note: in AntOS, file with extension `.db` is considered as sqlite3 database +file and has the following mimetype `application/vnd.sqlite3`. Applications +shall use this mime in `package.json` + +## Change logs +- v0.1.0a: initial version with functioning library binding diff --git a/SQLiteDB/api/api.lua b/SQLiteDB/api/api.lua index d623f64..4c038fe 100644 --- a/SQLiteDB/api/api.lua +++ b/SQLiteDB/api/api.lua @@ -44,7 +44,7 @@ handle.update = function(data) local tb = {} local gen = SQLQueryGenerator:new({}) for k,v in pairs(data.record) do - if k ~= "id" then + if k ~= data.pk then table.insert(tb, string.format("%s=%s", k, gen:parse_value(v, {[type(v)] = true}))) end end @@ -92,7 +92,7 @@ handle.insert = function(data) local vals = {} local gen = SQLQueryGenerator:new({}) for k,v in pairs(data.record) do - if k ~= "id" then + if k ~= data.pk then table.insert(keys,k) table.insert(vals,gen:parse_value(v, {[type(v)] = true})) end @@ -119,7 +119,9 @@ handle.create_table = function(data) end local tb = {} for k,v in pairs(data.scheme) do - table.insert(tb, k.." "..v) + if k ~= "id" then + table.insert(tb, k.." "..v) + end end local sql = string.format("CREATE TABLE IF NOT EXISTS %s(id INTEGER PRIMARY KEY,%s)", data.table_name, table.concat(tb,",")) LOG_DEBUG("Execute query: [%s]", sql) @@ -186,7 +188,7 @@ handle.table_scheme = function(data) if not db then return error("table_scheme: Unable to open sqlite db file") end - local sql = string.format("SELECT p.name, p.type FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type ='table' AND m.name = '%s'", data.table_name) + local sql = string.format("SELECT p.name, p.type, p.pk FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type ='table' AND m.name = '%s'", data.table_name) local ret, err = sqlite.query(db, sql); sqlite.dbclose(db) if not ret then diff --git a/SQLiteDB/build/debug/README.md b/SQLiteDB/build/debug/README.md index 819ff91..598b8f3 100644 --- a/SQLiteDB/build/debug/README.md +++ b/SQLiteDB/build/debug/README.md @@ -1,3 +1,11 @@ # SQLiteDB -"mimes":["application/vnd.sqlite3"], +This package contains the SQLiteDB API binding for AntOS applications +and a simple sqlite3 browser application that uses the library as reference + +Note: in AntOS, file with extension `.db` is considered as sqlite3 database +file and has the following mimetype `application/vnd.sqlite3`. Applications +shall use this mime in `package.json` + +## Change logs +- v0.1.0a: initial version with functioning library binding diff --git a/SQLiteDB/build/debug/api/api.lua b/SQLiteDB/build/debug/api/api.lua index d623f64..4c038fe 100644 --- a/SQLiteDB/build/debug/api/api.lua +++ b/SQLiteDB/build/debug/api/api.lua @@ -44,7 +44,7 @@ handle.update = function(data) local tb = {} local gen = SQLQueryGenerator:new({}) for k,v in pairs(data.record) do - if k ~= "id" then + if k ~= data.pk then table.insert(tb, string.format("%s=%s", k, gen:parse_value(v, {[type(v)] = true}))) end end @@ -92,7 +92,7 @@ handle.insert = function(data) local vals = {} local gen = SQLQueryGenerator:new({}) for k,v in pairs(data.record) do - if k ~= "id" then + if k ~= data.pk then table.insert(keys,k) table.insert(vals,gen:parse_value(v, {[type(v)] = true})) end @@ -119,7 +119,9 @@ handle.create_table = function(data) end local tb = {} for k,v in pairs(data.scheme) do - table.insert(tb, k.." "..v) + if k ~= "id" then + table.insert(tb, k.." "..v) + end end local sql = string.format("CREATE TABLE IF NOT EXISTS %s(id INTEGER PRIMARY KEY,%s)", data.table_name, table.concat(tb,",")) LOG_DEBUG("Execute query: [%s]", sql) @@ -186,7 +188,7 @@ handle.table_scheme = function(data) if not db then return error("table_scheme: Unable to open sqlite db file") end - local sql = string.format("SELECT p.name, p.type FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type ='table' AND m.name = '%s'", data.table_name) + local sql = string.format("SELECT p.name, p.type, p.pk FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type ='table' AND m.name = '%s'", data.table_name) local ret, err = sqlite.query(db, sql); sqlite.dbclose(db) if not ret then diff --git a/SQLiteDB/build/debug/libsqlite.js b/SQLiteDB/build/debug/libsqlite.js index 4a7497d..4f32bd8 100644 --- a/SQLiteDB/build/debug/libsqlite.js +++ b/SQLiteDB/build/debug/libsqlite.js @@ -130,22 +130,24 @@ var OS; }; return this.request(rq); } - insert(table_name, record) { + insert(table_name, record, pk) { let rq = { action: 'insert', args: { table_name, - record + record, + pk } }; return this.request(rq); } - update(table_name, record) { + update(table_name, record, pk) { let rq = { action: 'update', args: { table_name, - record + record, + pk } }; return this.request(rq); @@ -270,16 +272,29 @@ var OS; return new Promise(async (resolve, reject) => { try { await this._handle.init(); - const d = { result: this._handle.fileinfo(), error: false }; + let d = { + result: { + file: this._handle.fileinfo(), + schema: undefined + }, error: false + }; if (this._table_name) { const data = await this._handle.get_table_scheme(this._table_name); if (data.length == 0) { - d.result.scheme = undefined; + d.result.schema = undefined; } else { - d.result.scheme = {}; + d.result.schema = { + fields: [], + types: {}, + pk: undefined + }; + d.result.schema.fields = data.map(e => e.name); for (let v of data) { - d.result.scheme[v.name] = v.type; + d.result.schema.types[v.name] = v.type; + if (v.pk) { + d.result.schema.pk = v.name; + } } } } @@ -307,7 +322,7 @@ var OS; _rd(user_data) { return new Promise(async (resolve, reject) => { try { - if (this._table_name && !this.info.scheme) { + if (this._table_name && !this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } if (!this._table_name) { @@ -361,14 +376,15 @@ var OS; if (!this.cache) { throw new Error(__("No data to submit to remote database, please check the `cache` field").__()); } + await this.onready(); if (this._id && this._table_name) { this.cache.id = this._id; - const ret = await this._handle.update(this._table_name, this.cache); + const ret = await this._handle.update(this._table_name, this.cache, this.info.schema.pk); resolve({ result: ret, error: false }); return; } if (this._table_name) { - const ret = await this._handle.insert(this._table_name, this.cache); + const ret = await this._handle.insert(this._table_name, this.cache, this.info.schema.pk); resolve({ result: ret, error: false }); return; } @@ -392,7 +408,7 @@ var OS; _rm(user_data) { return new Promise(async (resolve, reject) => { try { - if (this._table_name && !this.info.scheme) { + if (this._table_name && !this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } if (!this._table_name) { diff --git a/SQLiteDB/build/debug/main.js b/SQLiteDB/build/debug/main.js index f66a559..74186ee 100644 --- a/SQLiteDB/build/debug/main.js +++ b/SQLiteDB/build/debug/main.js @@ -6,12 +6,12 @@ var OS; (function (application) { /** * - * @class SQLiteDB + * @class SQLiteDBBrowser * @extends {BaseApplication} */ - class SQLiteDB extends application.BaseApplication { + class SQLiteDBBrowser extends application.BaseApplication { constructor(args) { - super("SQLiteDB", args); + super("SQLiteDBBrowser", args); } menu() { return [ @@ -19,12 +19,12 @@ var OS; text: "__(File)", nodes: [ { - text: "__(New)", + text: "__(New database)", dataid: "new", shortcut: 'A-N' }, { - text: "__(Open)", + text: "__(Open database)", dataid: "open", shortcut: 'A-O' }, @@ -53,7 +53,7 @@ var OS; } this.tbl_list.data = list; if (list.length > 0) { - this.tbl_list.selected = 0; + this.tbl_list.selected = list.length - 1; } }); } @@ -114,29 +114,65 @@ var OS; if (this.container.selectedIndex == 0) { if (!this.tbl_list.selectedItem) return; - const scheme = this.tbl_list.selectedItem.data.handle.info.scheme; - if (!scheme) + const schema = this.tbl_list.selectedItem.data.handle.info.schema; + if (!schema) return; const data = []; - for (let k in scheme) { + for (let k in schema.types) { data.push([ { text: k }, - { text: scheme[k] } + { text: schema.types[k] } ]); } this.grid_scheme.rows = data; } }; - this.find("bt-add-table").onbtclick = (e) => { - if (!this.filehandle) { - return this.notify(__("Please open a database file")); + this.find("bt-rm-table").onbtclick = async (e) => { + try { + if (!this.filehandle) { + return this.notify(__("Please open a database file")); + } + if (this.tbl_list.selectedItem == undefined) { + return; + } + const table = this.tbl_list.selectedItem.data.name; + const ret = await this.openDialog("YesNoDialog", { + title: __("Confirm delete?"), + text: __("Do you realy want to delete table: {0}", table) + }); + if (ret) { + await this.filehandle.remove(table); + this.list_tables(); + } } - this.openDialog(new NewTableDialog(), { - title: __("Create new table") - }) - .then((data) => { - console.log(data); - }); + catch (e) { + this.error(__("Unable to execute action table delete: {0}", e.toString()), e); + } + }; + this.find("bt-add-table").onbtclick = async (e) => { + try { + if (!this.filehandle) { + return this.notify(__("Please open a database file")); + } + const data = await this.openDialog(new NewTableDialog(), { + title: __("Create new table") + }); + this.filehandle.cache = data.schema; + await this.filehandle.write(data.name); + this.list_tables(); + } + catch (e) { + this.error(__("Unable to create table: {0}", e.toString()), e); + } + }; + this.find("btn-edit-record").onbtclick = async (e) => { + this.edit_record(); + }; + this.find("btn-add-record").onbtclick = async (e) => { + this.add_record(); + }; + this.find("btn-delete-record").onbtclick = async (e) => { + this.remove_record(); }; this.btn_loadmore.onbtclick = async (e) => { try { @@ -153,13 +189,14 @@ var OS; const handle = this.tbl_list.selectedItem.data.handle; await handle.onready(); this.last_max_id = 0; - this.grid_table.rows = []; - const headers = Object.getOwnPropertyNames(handle.info.scheme).map((e) => { + const headers = handle.info.schema.fields.map((e) => { return { text: e }; }); this.grid_table.header = headers; + this.grid_table.rows = []; const records = await handle.read({ fields: ["COUNT(*)"] }); this.n_records = records[0]["(COUNT(*))"]; + this.btn_loadmore.text = `0/${this.n_records}`; await this.load_table(); this.container.selectedIndex = 1; } @@ -167,24 +204,119 @@ var OS; this.error(__("Error reading table: {0}", e.toString()), e); } }; + this.grid_table.oncelldbclick = async (e) => { + this.edit_record(); + }; this.openFile(); } + async add_record() { + try { + const table_handle = this.tbl_list.selectedItem; + if (!table_handle) { + return; + } + const file_hd = table_handle.data.handle; + const schema = table_handle.data.handle.info.schema; + const model = {}; + for (let k in schema.types) { + if (["INTEGER", "REAL", "NUMERIC"].includes(schema.types[k])) { + model[k] = 0; + } + else { + model[k] = ""; + } + } + console.log(model); + const data = await this.openDialog(new RecordEditDialog(), { + title: __("New record"), + schema: schema, + record: model + }); + file_hd.cache = data; + await file_hd.write(undefined); + this.n_records += 1; + await this.load_table(); + } + catch (e) { + this.error(__("Error edit/view record: {0}", e.toString()), e); + } + } + async remove_record() { + try { + const cell = this.grid_table.selectedCell; + const row = this.grid_table.selectedRow; + const table_handle = this.tbl_list.selectedItem; + if (!cell || !table_handle) { + return; + } + const pk_id = cell.data.record[table_handle.data.handle.info.schema.pk]; + const ret = await this.openDialog("YesNoDialog", { + title: __("Delete record"), + text: __("Do you realy want to delete record {0}", pk_id) + }); + if (!ret) { + return; + } + const file_hd = `${table_handle.data.handle.path}@${pk_id}`.asFileHandle(); + await file_hd.remove(); + this.n_records--; + // remove the target row + this.grid_table.delete(row); + this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`; + } + catch (e) { + this.error(__("Error deleting record: {0}", e.toString()), e); + } + } + async edit_record() { + try { + const cell = this.grid_table.selectedCell; + const row = this.grid_table.selectedRow; + const table_handle = this.tbl_list.selectedItem; + if (!cell || !table_handle) { + return; + } + const data = await this.openDialog(new RecordEditDialog(), { + title: __("View/edit record"), + schema: table_handle.data.handle.info.schema, + record: cell.data.record + }); + const pk_id = cell.data.record[table_handle.data.handle.info.schema.pk]; + const file_hd = `${table_handle.data.handle.path}@${pk_id}`.asFileHandle(); + file_hd.cache = data; + await file_hd.write(undefined); + const row_data = []; + for (let k of file_hd.info.schema.fields) { + let text = data[k]; + if (text.length > 100) { + text = text.substring(0, 100); + } + row_data.push({ + text: text, + record: data + }); + } + row.data = row_data; + } + catch (e) { + this.error(__("Error edit/view record: {0}", e.toString()), e); + } + } async load_table() { - if (this.grid_table.rows.length >= this.n_records) { + if (this.grid_table.rows && this.grid_table.rows.length >= this.n_records) { return; } if (!this.tbl_list.selectedItem) return; const handle = this.tbl_list.selectedItem.data.handle; await handle.onready(); - const headers = Object.getOwnPropertyNames(handle.info.scheme).map((e) => { + const headers = handle.info.schema.fields.map((e) => { return { text: e }; }); // read all records - const records = await handle.read({ - where: { id$gt: this.last_max_id }, - limit: 10 - }); + const filter = { where: {}, limit: 10 }; + filter.where[`${handle.info.schema.pk}\$gt`] = this.last_max_id; + const records = await handle.read(filter); if (records && records.length > 0) { for (let e of records) { const row = []; @@ -208,7 +340,10 @@ var OS; this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`; } } - application.SQLiteDB = SQLiteDB; + application.SQLiteDBBrowser = SQLiteDBBrowser; + SQLiteDBBrowser.dependencies = [ + "pkg://SQLiteDB/libsqlite.js" + ]; class NewTableDialog extends OS.GUI.BasicDialog { /** * Creates an instance of NewTableDialog. @@ -227,7 +362,6 @@ var OS; this.container = this.find("container"); this.find("btnCancel").onbtclick = (e) => this.quit(); this.find("btnAdd").onbtclick = (e) => this.addField(); - $(this.find("wrapper")); $(this.container) .css("overflow-y", "auto"); this.addField(); @@ -236,6 +370,7 @@ var OS; if (!input.value || input.value == "") { return this.notify(__("Please enter table name")); } + const tblname = input.value; const inputs = $("input", this.container); const lists = $("afx-list-view", this.container); if (inputs.length == 0) { @@ -253,7 +388,7 @@ var OS; cdata[key] = lists[i].selectedItem.data.text; } if (this.handle) - this.handle(cdata); + this.handle({ name: tblname, schema: cdata }); this.quit(); }; } @@ -304,13 +439,78 @@ var OS;
- +
+`; + class RecordEditDialog extends OS.GUI.BasicDialog { + /** + * Creates an instance of RecordEditDialog. + * @memberof RecordEditDialog + */ + constructor() { + super("RecordEditDialog"); + } + /** + * Main entry point + * + * @memberof RecordEditDialog + */ + main() { + super.main(); + this.container = this.find("container"); + this.find("btnCancel").onbtclick = (e) => this.quit(); + $(this.container) + .css("overflow-y", "auto"); + if (!this.data || !this.data.schema) { + throw new Error(__("No data provided for dialog").__()); + } + for (let k in this.data.schema.types) { + const input = $("").appendTo(this.container)[0]; + input.uify(this.observable); + input.label = k; + if (k == this.data.schema.pk) { + input.disable = true; + } + if (this.data.schema.types[k] == "TEXT") { + input.verbose = true; + $(input).css("height", "100px"); + } + if (this.data.record[k] != undefined) { + input.value = this.data.record[k]; + } + } + this.find("btnOk").onbtclick = (e) => { + const inputs = $("afx-input", this.container); + const data = {}; + for (let input of inputs) { + data[input.label.__()] = input.value; + } + if (this.handle) + this.handle(data); + this.quit(); + }; + } + } + /** + * Scheme definition + */ + RecordEditDialog.scheme = `\ + + +
+ +
+
+ + +
+
+
`; })(application = OS.application || (OS.application = {})); })(OS || (OS = {})); @@ -447,22 +647,24 @@ var OS; }; return this.request(rq); } - insert(table_name, record) { + insert(table_name, record, pk) { let rq = { action: 'insert', args: { table_name, - record + record, + pk } }; return this.request(rq); } - update(table_name, record) { + update(table_name, record, pk) { let rq = { action: 'update', args: { table_name, - record + record, + pk } }; return this.request(rq); @@ -587,16 +789,29 @@ var OS; return new Promise(async (resolve, reject) => { try { await this._handle.init(); - const d = { result: this._handle.fileinfo(), error: false }; + let d = { + result: { + file: this._handle.fileinfo(), + schema: undefined + }, error: false + }; if (this._table_name) { const data = await this._handle.get_table_scheme(this._table_name); if (data.length == 0) { - d.result.scheme = undefined; + d.result.schema = undefined; } else { - d.result.scheme = {}; + d.result.schema = { + fields: [], + types: {}, + pk: undefined + }; + d.result.schema.fields = data.map(e => e.name); for (let v of data) { - d.result.scheme[v.name] = v.type; + d.result.schema.types[v.name] = v.type; + if (v.pk) { + d.result.schema.pk = v.name; + } } } } @@ -624,7 +839,7 @@ var OS; _rd(user_data) { return new Promise(async (resolve, reject) => { try { - if (this._table_name && !this.info.scheme) { + if (this._table_name && !this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } if (!this._table_name) { @@ -678,14 +893,15 @@ var OS; if (!this.cache) { throw new Error(__("No data to submit to remote database, please check the `cache` field").__()); } + await this.onready(); if (this._id && this._table_name) { this.cache.id = this._id; - const ret = await this._handle.update(this._table_name, this.cache); + const ret = await this._handle.update(this._table_name, this.cache, this.info.schema.pk); resolve({ result: ret, error: false }); return; } if (this._table_name) { - const ret = await this._handle.insert(this._table_name, this.cache); + const ret = await this._handle.insert(this._table_name, this.cache, this.info.schema.pk); resolve({ result: ret, error: false }); return; } @@ -709,7 +925,7 @@ var OS; _rm(user_data) { return new Promise(async (resolve, reject) => { try { - if (this._table_name && !this.info.scheme) { + if (this._table_name && !this.info.schema) { throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__()); } if (!this._table_name) { diff --git a/SQLiteDB/build/debug/package.json b/SQLiteDB/build/debug/package.json index ce6fcaa..1545d82 100644 --- a/SQLiteDB/build/debug/package.json +++ b/SQLiteDB/build/debug/package.json @@ -1,16 +1,16 @@ { "pkgname": "SQLiteDB", - "app":"SQLiteDB", - "name":"SQLite3 VFS API", + "app":"SQLiteDBBrowser", + "name":"SQLite3 Browser", "description":"API for manipulate SQLite database as VFS handle", "info":{ - "author": "", - "email": "" + "author": "Dany LE", + "email": "mrsang@iohub.dev" }, "version":"0.1.0-a", - "category":"Other", - "iconclass":"fa fa-adn", - "mimes":[".*"], + "category":"Library", + "iconclass":"bi bi-database", + "mimes":["application/vnd.sqlite3"], "dependencies":[], "locale": {} } \ No newline at end of file diff --git a/SQLiteDB/build/debug/scheme.html b/SQLiteDB/build/debug/scheme.html index 3b9996c..5e8c268 100644 --- a/SQLiteDB/build/debug/scheme.html +++ b/SQLiteDB/build/debug/scheme.html @@ -2,6 +2,7 @@ + @@ -13,12 +14,13 @@ +
- - - + + +
diff --git a/SQLiteDB/main.ts b/SQLiteDB/main.ts index 9b87313..1a792b5 100644 --- a/SQLiteDB/main.ts +++ b/SQLiteDB/main.ts @@ -1,12 +1,29 @@ +/** + * Define missing API in Array interface + * + * @interface Array + * @template T + */ +interface Array { + /** + * Check if the array includes an element + * + * @param {T} element to check + * @returns {boolean} + * @memberof Array + */ + includes(el: T):boolean; +} + namespace OS { export namespace application { /** * - * @class SQLiteDB + * @class SQLiteDBBrowser * @extends {BaseApplication} */ - export class SQLiteDB extends BaseApplication { + export class SQLiteDBBrowser extends BaseApplication { private filehandle: API.VFS.BaseFileHandle; private tbl_list: GUI.tag.ListViewTag; @@ -17,7 +34,7 @@ namespace OS { private n_records: number; private btn_loadmore: GUI.tag.ButtonTag; constructor(args: AppArgumentsType[]) { - super("SQLiteDB", args); + super("SQLiteDBBrowser", args); } menu() { @@ -26,12 +43,12 @@ namespace OS { text: "__(File)", nodes: [ { - text: "__(New)", + text: "__(New database)", dataid: "new", shortcut: 'A-N' }, { - text: "__(Open)", + text: "__(Open database)", dataid: "open", shortcut: 'A-O' }, @@ -63,7 +80,7 @@ namespace OS { this.tbl_list.data = list; if(list.length > 0) { - this.tbl_list.selected = 0; + this.tbl_list.selected = list.length - 1; } }) } @@ -128,32 +145,75 @@ namespace OS { { if(!this.tbl_list.selectedItem) return; - const scheme = this.tbl_list.selectedItem.data.handle.info.scheme; - if(!scheme) + const schema = this.tbl_list.selectedItem.data.handle.info.schema; + if(!schema) return; const data = []; - for(let k in scheme) + for(let k in schema.types) { data.push([ { text: k}, - {text: scheme[k]} + {text: schema.types[k]} ]) } this.grid_scheme.rows = data; } } + (this.find("bt-rm-table") as GUI.tag.ButtonTag).onbtclick = async (e) => { + try { + if(!this.filehandle) + { + return this.notify(__("Please open a database file")); + } + if(this.tbl_list.selectedItem == undefined) + { + return; + } + const table = this.tbl_list.selectedItem.data.name; + const ret = await this.openDialog("YesNoDialog", { + title: __("Confirm delete?"), + text: __("Do you realy want to delete table: {0}", table) + }); - (this.find("bt-add-table") as GUI.tag.ButtonTag).onbtclick = (e) => { - if(!this.filehandle) - { - return this.notify(__("Please open a database file")); + if(ret) + { + await this.filehandle.remove(table); + this.list_tables(); + } } - this.openDialog(new NewTableDialog(), { - title: __("Create new table") - }) - .then((data) => { - console.log(data); - }); + catch(e) + { + this.error(__("Unable to execute action table delete: {0}", e.toString()), e); + } + + } + (this.find("bt-add-table") as GUI.tag.ButtonTag).onbtclick = async (e) => { + try + { + if(!this.filehandle) + { + return this.notify(__("Please open a database file")); + } + const data = await this.openDialog(new NewTableDialog(), { + title: __("Create new table") + }); + this.filehandle.cache = data.schema; + await this.filehandle.write(data.name); + this.list_tables(); + } + catch(e) + { + this.error(__("Unable to create table: {0}", e.toString()), e); + } + } + (this.find("btn-edit-record") as GUI.tag.ButtonTag).onbtclick = async (e) => { + this.edit_record(); + } + (this.find("btn-add-record") as GUI.tag.ButtonTag).onbtclick = async (e) => { + this.add_record(); + } + (this.find("btn-delete-record") as GUI.tag.ButtonTag).onbtclick = async (e) => { + this.remove_record(); } this.btn_loadmore.onbtclick = async (e) => { try @@ -173,14 +233,14 @@ namespace OS { const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle; await handle.onready(); this.last_max_id = 0; - this.grid_table.rows = []; - const headers = - Object.getOwnPropertyNames(handle.info.scheme).map((e)=>{ - return {text: e} - }); + const headers = handle.info.schema.fields.map((e) => { + return {text: e} + }); this.grid_table.header = headers; + this.grid_table.rows = []; const records = await handle.read({fields:["COUNT(*)"]}); this.n_records = records[0]["(COUNT(*))"]; + this.btn_loadmore.text = `0/${this.n_records}`; await this.load_table(); this.container.selectedIndex = 1; } @@ -189,12 +249,126 @@ namespace OS { this.error(__("Error reading table: {0}", e.toString()),e); } } + this.grid_table.oncelldbclick = async (e) => { + this.edit_record(); + } this.openFile(); } + private async add_record() + { + try + { + const table_handle = this.tbl_list.selectedItem; + if(!table_handle) + { + return; + } + const file_hd = table_handle.data.handle; + const schema = table_handle.data.handle.info.schema; + const model = {}; + for(let k in schema.types) + { + if(["INTEGER", "REAL", "NUMERIC"].includes(schema.types[k])) + { + model[k] = 0; + } + else + { + model[k] = ""; + } + } + console.log(model); + const data = await this.openDialog(new RecordEditDialog(), { + title: __("New record"), + schema: schema, + record: model + }); + file_hd.cache = data; + await file_hd.write(undefined); + this.n_records += 1; + await this.load_table(); + } + catch (e) + { + this.error(__("Error edit/view record: {0}", e.toString()), e); + } + } + private async remove_record() + { + try + { + const cell = this.grid_table.selectedCell; + const row = this.grid_table.selectedRow; + const table_handle = this.tbl_list.selectedItem; + if(!cell || !table_handle) + { + return; + } + const pk_id = cell.data.record[table_handle.data.handle.info.schema.pk]; + const ret = await this.openDialog("YesNoDialog", { + title: __("Delete record"), + text: __("Do you realy want to delete record {0}",pk_id) + }); + if(!ret) + { + return; + } + const file_hd = `${table_handle.data.handle.path}@${pk_id}`.asFileHandle(); + await file_hd.remove(); + this.n_records--; + // remove the target row + this.grid_table.delete(row); + this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`; + } + catch(e) + { + this.error(__("Error deleting record: {0}", e.toString()),e); + } + } + private async edit_record() + { + try + { + const cell = this.grid_table.selectedCell; + const row = this.grid_table.selectedRow; + const table_handle = this.tbl_list.selectedItem; + if(!cell || !table_handle) + { + return; + } + const data = await this.openDialog(new RecordEditDialog(), { + title: __("View/edit record"), + schema: table_handle.data.handle.info.schema, + record: cell.data.record + }); + const pk_id = cell.data.record[table_handle.data.handle.info.schema.pk]; + const file_hd = `${table_handle.data.handle.path}@${pk_id}`.asFileHandle(); + file_hd.cache = data; + await file_hd.write(undefined); + const row_data = []; + for(let k of file_hd.info.schema.fields) + { + let text:string = data[k]; + if(text.length > 100) + { + text = text.substring(0,100); + } + row_data.push({ + text: text, + record: data + }); + } + row.data = row_data; + } + catch (e) + { + this.error(__("Error edit/view record: {0}", e.toString()), e); + } + } private async load_table() { - if(this.grid_table.rows.length >= this.n_records) + if(this.grid_table.rows && this.grid_table.rows.length >= this.n_records) { return; } @@ -202,15 +376,13 @@ namespace OS { return; const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle; await handle.onready(); - const headers = - Object.getOwnPropertyNames(handle.info.scheme).map((e)=>{ - return {text: e} - }); - // read all records - const records = await handle.read({ - where:{ id$gt: this.last_max_id }, - limit: 10 + const headers = handle.info.schema.fields.map((e) => { + return {text: e} }); + // read all records + const filter = { where: {}, limit: 10} + filter.where[`${handle.info.schema.pk}\$gt`] = this.last_max_id; + const records = await handle.read(filter); if(records && records.length > 0) { @@ -234,13 +406,16 @@ namespace OS { } this.grid_table.push(row, false); } - (this.grid_table as any).scroll_to_bottom(); + this.grid_table.scroll_to_bottom(); } this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`; } } + SQLiteDBBrowser.dependencies = [ + "pkg://SQLiteDB/libsqlite.js" + ] class NewTableDialog extends GUI.BasicDialog { /** @@ -270,7 +445,6 @@ namespace OS { this.container = this.find("container") as HTMLDivElement; (this.find("btnCancel") as GUI.tag.ButtonTag).onbtclick = (e) => this.quit(); (this.find("btnAdd") as GUI.tag.ButtonTag).onbtclick = (e) => this.addField(); - $(this.find("wrapper")) $(this.container) .css("overflow-y", "auto"); this.addField(); @@ -281,7 +455,7 @@ namespace OS { { return this.notify(__("Please enter table name")); } - + const tblname = input.value; const inputs = $("input", this.container) as JQuery; const lists = $("afx-list-view", this.container) as JQuery; if(inputs.length == 0) @@ -301,7 +475,7 @@ namespace OS { cdata[key] = lists[i].selectedItem.data.text; } if (this.handle) - this.handle(cdata); + this.handle({ name: tblname, schema: cdata}); this.quit(); } } @@ -356,7 +530,7 @@ namespace OS {
- +
@@ -364,5 +538,90 @@ namespace OS { `; + + class RecordEditDialog extends GUI.BasicDialog + { + /** + * Reference to the form container + * + * @private + * @type {HTMLDivElement} + * @memberof RecordEditDialog + */ + private container: HTMLDivElement; + /** + * Creates an instance of RecordEditDialog. + * @memberof RecordEditDialog + */ + constructor() { + super("RecordEditDialog"); + } + + /** + * Main entry point + * + * @memberof RecordEditDialog + */ + main(): void { + super.main(); + this.container = this.find("container") as HTMLDivElement; + (this.find("btnCancel") as GUI.tag.ButtonTag).onbtclick = (e) => this.quit(); + $(this.container) + .css("overflow-y", "auto"); + if(!this.data || !this.data.schema) + { + throw new Error(__("No data provided for dialog").__()); + } + for(let k in this.data.schema.types) + { + const input = $("").appendTo(this.container)[0] as GUI.tag.InputTag; + input.uify(this.observable); + input.label = k; + if(k == this.data.schema.pk) + { + input.disable = true; + } + if(this.data.schema.types[k] == "TEXT") + { + input.verbose = true; + $(input).css("height", "100px"); + } + if(this.data.record[k] != undefined) + { + input.value = this.data.record[k]; + } + } + (this.find("btnOk") as GUI.tag.ButtonTag).onbtclick = (e) => { + const inputs = $("afx-input", this.container) as JQuery; + const data = {}; + for(let input of inputs) + { + data[input.label.__()] = input.value; + } + if (this.handle) + this.handle(data); + this.quit(); + } + } + } + + /** + * Scheme definition + */ + RecordEditDialog.scheme = `\ + + +
+ +
+
+ + +
+
+
+
`; } + + } \ No newline at end of file diff --git a/SQLiteDB/package.json b/SQLiteDB/package.json index ce6fcaa..1545d82 100644 --- a/SQLiteDB/package.json +++ b/SQLiteDB/package.json @@ -1,16 +1,16 @@ { "pkgname": "SQLiteDB", - "app":"SQLiteDB", - "name":"SQLite3 VFS API", + "app":"SQLiteDBBrowser", + "name":"SQLite3 Browser", "description":"API for manipulate SQLite database as VFS handle", "info":{ - "author": "", - "email": "" + "author": "Dany LE", + "email": "mrsang@iohub.dev" }, "version":"0.1.0-a", - "category":"Other", - "iconclass":"fa fa-adn", - "mimes":[".*"], + "category":"Library", + "iconclass":"bi bi-database", + "mimes":["application/vnd.sqlite3"], "dependencies":[], "locale": {} } \ No newline at end of file diff --git a/SQLiteDB/scheme.html b/SQLiteDB/scheme.html index 3b9996c..5e8c268 100644 --- a/SQLiteDB/scheme.html +++ b/SQLiteDB/scheme.html @@ -2,6 +2,7 @@ + @@ -13,12 +14,13 @@ +
- - - + + +