mirror of
https://github.com/antos-rde/antosdk-apps.git
synced 2024-12-24 19:28:21 +01:00
SQLiteDB: first working version
This commit is contained in:
parent
cf21ef60e0
commit
04050f124f
@ -164,25 +164,27 @@ namespace OS {
|
|||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
|
|
||||||
insert(table_name:string, record: GenericObject<any>): Promise<any>
|
insert(table_name:string, record: GenericObject<any>, pk:string): Promise<any>
|
||||||
{
|
{
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'insert',
|
action: 'insert',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(table_name:string, record: GenericObject<any>): Promise<any>
|
update(table_name:string, record: GenericObject<any>, pk:string ): Promise<any>
|
||||||
{
|
{
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
@ -317,20 +319,33 @@ namespace OS {
|
|||||||
try {
|
try {
|
||||||
await this._handle.init();
|
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)
|
if(this._table_name)
|
||||||
{
|
{
|
||||||
const data = await this._handle.get_table_scheme(this._table_name);
|
const data = await this._handle.get_table_scheme(this._table_name);
|
||||||
if(data.length == 0)
|
if(data.length == 0)
|
||||||
{
|
{
|
||||||
d.result.scheme = undefined
|
d.result.schema = undefined
|
||||||
}
|
}
|
||||||
else
|
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)
|
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<any> {
|
protected _rd(user_data: any): Promise<any> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try{
|
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).__());
|
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").__());
|
throw new Error(__("No data to submit to remote database, please check the `cache` field").__());
|
||||||
}
|
}
|
||||||
|
await this.onready();
|
||||||
if(this._id && this._table_name)
|
if(this._id && this._table_name)
|
||||||
{
|
{
|
||||||
this.cache.id = this._id;
|
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});
|
resolve({result:ret, error: false});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this._table_name)
|
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});
|
resolve({result:ret, error: false});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -458,7 +474,7 @@ namespace OS {
|
|||||||
protected _rm(user_data: any): Promise<RequestResult> {
|
protected _rm(user_data: any): Promise<RequestResult> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
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).__());
|
throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
# SQLiteDB
|
# 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
|
||||||
|
@ -44,7 +44,7 @@ handle.update = function(data)
|
|||||||
local tb = {}
|
local tb = {}
|
||||||
local gen = SQLQueryGenerator:new({})
|
local gen = SQLQueryGenerator:new({})
|
||||||
for k,v in pairs(data.record) do
|
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})))
|
table.insert(tb, string.format("%s=%s", k, gen:parse_value(v, {[type(v)] = true})))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -92,7 +92,7 @@ handle.insert = function(data)
|
|||||||
local vals = {}
|
local vals = {}
|
||||||
local gen = SQLQueryGenerator:new({})
|
local gen = SQLQueryGenerator:new({})
|
||||||
for k,v in pairs(data.record) do
|
for k,v in pairs(data.record) do
|
||||||
if k ~= "id" then
|
if k ~= data.pk then
|
||||||
table.insert(keys,k)
|
table.insert(keys,k)
|
||||||
table.insert(vals,gen:parse_value(v, {[type(v)] = true}))
|
table.insert(vals,gen:parse_value(v, {[type(v)] = true}))
|
||||||
end
|
end
|
||||||
@ -119,7 +119,9 @@ handle.create_table = function(data)
|
|||||||
end
|
end
|
||||||
local tb = {}
|
local tb = {}
|
||||||
for k,v in pairs(data.scheme) do
|
for k,v in pairs(data.scheme) do
|
||||||
table.insert(tb, k.." "..v)
|
if k ~= "id" then
|
||||||
|
table.insert(tb, k.." "..v)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local sql = string.format("CREATE TABLE IF NOT EXISTS %s(id INTEGER PRIMARY KEY,%s)", data.table_name, table.concat(tb,","))
|
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)
|
LOG_DEBUG("Execute query: [%s]", sql)
|
||||||
@ -186,7 +188,7 @@ handle.table_scheme = function(data)
|
|||||||
if not db then
|
if not db then
|
||||||
return error("table_scheme: Unable to open sqlite db file")
|
return error("table_scheme: Unable to open sqlite db file")
|
||||||
end
|
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);
|
local ret, err = sqlite.query(db, sql);
|
||||||
sqlite.dbclose(db)
|
sqlite.dbclose(db)
|
||||||
if not ret then
|
if not ret then
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
# SQLiteDB
|
# 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
|
||||||
|
@ -44,7 +44,7 @@ handle.update = function(data)
|
|||||||
local tb = {}
|
local tb = {}
|
||||||
local gen = SQLQueryGenerator:new({})
|
local gen = SQLQueryGenerator:new({})
|
||||||
for k,v in pairs(data.record) do
|
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})))
|
table.insert(tb, string.format("%s=%s", k, gen:parse_value(v, {[type(v)] = true})))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -92,7 +92,7 @@ handle.insert = function(data)
|
|||||||
local vals = {}
|
local vals = {}
|
||||||
local gen = SQLQueryGenerator:new({})
|
local gen = SQLQueryGenerator:new({})
|
||||||
for k,v in pairs(data.record) do
|
for k,v in pairs(data.record) do
|
||||||
if k ~= "id" then
|
if k ~= data.pk then
|
||||||
table.insert(keys,k)
|
table.insert(keys,k)
|
||||||
table.insert(vals,gen:parse_value(v, {[type(v)] = true}))
|
table.insert(vals,gen:parse_value(v, {[type(v)] = true}))
|
||||||
end
|
end
|
||||||
@ -119,7 +119,9 @@ handle.create_table = function(data)
|
|||||||
end
|
end
|
||||||
local tb = {}
|
local tb = {}
|
||||||
for k,v in pairs(data.scheme) do
|
for k,v in pairs(data.scheme) do
|
||||||
table.insert(tb, k.." "..v)
|
if k ~= "id" then
|
||||||
|
table.insert(tb, k.." "..v)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local sql = string.format("CREATE TABLE IF NOT EXISTS %s(id INTEGER PRIMARY KEY,%s)", data.table_name, table.concat(tb,","))
|
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)
|
LOG_DEBUG("Execute query: [%s]", sql)
|
||||||
@ -186,7 +188,7 @@ handle.table_scheme = function(data)
|
|||||||
if not db then
|
if not db then
|
||||||
return error("table_scheme: Unable to open sqlite db file")
|
return error("table_scheme: Unable to open sqlite db file")
|
||||||
end
|
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);
|
local ret, err = sqlite.query(db, sql);
|
||||||
sqlite.dbclose(db)
|
sqlite.dbclose(db)
|
||||||
if not ret then
|
if not ret then
|
||||||
|
@ -130,22 +130,24 @@ var OS;
|
|||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
insert(table_name, record) {
|
insert(table_name, record, pk) {
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'insert',
|
action: 'insert',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
update(table_name, record) {
|
update(table_name, record, pk) {
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
@ -270,16 +272,29 @@ var OS;
|
|||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
await this._handle.init();
|
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) {
|
if (this._table_name) {
|
||||||
const data = await this._handle.get_table_scheme(this._table_name);
|
const data = await this._handle.get_table_scheme(this._table_name);
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
d.result.scheme = undefined;
|
d.result.schema = undefined;
|
||||||
}
|
}
|
||||||
else {
|
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) {
|
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) {
|
_rd(user_data) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
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).__());
|
throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__());
|
||||||
}
|
}
|
||||||
if (!this._table_name) {
|
if (!this._table_name) {
|
||||||
@ -361,14 +376,15 @@ var OS;
|
|||||||
if (!this.cache) {
|
if (!this.cache) {
|
||||||
throw new Error(__("No data to submit to remote database, please check the `cache` field").__());
|
throw new Error(__("No data to submit to remote database, please check the `cache` field").__());
|
||||||
}
|
}
|
||||||
|
await this.onready();
|
||||||
if (this._id && this._table_name) {
|
if (this._id && this._table_name) {
|
||||||
this.cache.id = this._id;
|
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 });
|
resolve({ result: ret, error: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._table_name) {
|
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 });
|
resolve({ result: ret, error: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -392,7 +408,7 @@ var OS;
|
|||||||
_rm(user_data) {
|
_rm(user_data) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
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).__());
|
throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__());
|
||||||
}
|
}
|
||||||
if (!this._table_name) {
|
if (!this._table_name) {
|
||||||
|
@ -6,12 +6,12 @@ var OS;
|
|||||||
(function (application) {
|
(function (application) {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @class SQLiteDB
|
* @class SQLiteDBBrowser
|
||||||
* @extends {BaseApplication}
|
* @extends {BaseApplication}
|
||||||
*/
|
*/
|
||||||
class SQLiteDB extends application.BaseApplication {
|
class SQLiteDBBrowser extends application.BaseApplication {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
super("SQLiteDB", args);
|
super("SQLiteDBBrowser", args);
|
||||||
}
|
}
|
||||||
menu() {
|
menu() {
|
||||||
return [
|
return [
|
||||||
@ -19,12 +19,12 @@ var OS;
|
|||||||
text: "__(File)",
|
text: "__(File)",
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
text: "__(New)",
|
text: "__(New database)",
|
||||||
dataid: "new",
|
dataid: "new",
|
||||||
shortcut: 'A-N'
|
shortcut: 'A-N'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "__(Open)",
|
text: "__(Open database)",
|
||||||
dataid: "open",
|
dataid: "open",
|
||||||
shortcut: 'A-O'
|
shortcut: 'A-O'
|
||||||
},
|
},
|
||||||
@ -53,7 +53,7 @@ var OS;
|
|||||||
}
|
}
|
||||||
this.tbl_list.data = list;
|
this.tbl_list.data = list;
|
||||||
if (list.length > 0) {
|
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.container.selectedIndex == 0) {
|
||||||
if (!this.tbl_list.selectedItem)
|
if (!this.tbl_list.selectedItem)
|
||||||
return;
|
return;
|
||||||
const scheme = this.tbl_list.selectedItem.data.handle.info.scheme;
|
const schema = this.tbl_list.selectedItem.data.handle.info.schema;
|
||||||
if (!scheme)
|
if (!schema)
|
||||||
return;
|
return;
|
||||||
const data = [];
|
const data = [];
|
||||||
for (let k in scheme) {
|
for (let k in schema.types) {
|
||||||
data.push([
|
data.push([
|
||||||
{ text: k },
|
{ text: k },
|
||||||
{ text: scheme[k] }
|
{ text: schema.types[k] }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this.grid_scheme.rows = data;
|
this.grid_scheme.rows = data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.find("bt-add-table").onbtclick = (e) => {
|
this.find("bt-rm-table").onbtclick = async (e) => {
|
||||||
if (!this.filehandle) {
|
try {
|
||||||
return this.notify(__("Please open a database file"));
|
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(), {
|
catch (e) {
|
||||||
title: __("Create new table")
|
this.error(__("Unable to execute action table delete: {0}", e.toString()), e);
|
||||||
})
|
}
|
||||||
.then((data) => {
|
};
|
||||||
console.log(data);
|
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) => {
|
this.btn_loadmore.onbtclick = async (e) => {
|
||||||
try {
|
try {
|
||||||
@ -153,13 +189,14 @@ var OS;
|
|||||||
const handle = this.tbl_list.selectedItem.data.handle;
|
const handle = this.tbl_list.selectedItem.data.handle;
|
||||||
await handle.onready();
|
await handle.onready();
|
||||||
this.last_max_id = 0;
|
this.last_max_id = 0;
|
||||||
this.grid_table.rows = [];
|
const headers = handle.info.schema.fields.map((e) => {
|
||||||
const headers = Object.getOwnPropertyNames(handle.info.scheme).map((e) => {
|
|
||||||
return { text: e };
|
return { text: e };
|
||||||
});
|
});
|
||||||
this.grid_table.header = headers;
|
this.grid_table.header = headers;
|
||||||
|
this.grid_table.rows = [];
|
||||||
const records = await handle.read({ fields: ["COUNT(*)"] });
|
const records = await handle.read({ fields: ["COUNT(*)"] });
|
||||||
this.n_records = records[0]["(COUNT(*))"];
|
this.n_records = records[0]["(COUNT(*))"];
|
||||||
|
this.btn_loadmore.text = `0/${this.n_records}`;
|
||||||
await this.load_table();
|
await this.load_table();
|
||||||
this.container.selectedIndex = 1;
|
this.container.selectedIndex = 1;
|
||||||
}
|
}
|
||||||
@ -167,24 +204,119 @@ var OS;
|
|||||||
this.error(__("Error reading table: {0}", e.toString()), e);
|
this.error(__("Error reading table: {0}", e.toString()), e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.grid_table.oncelldbclick = async (e) => {
|
||||||
|
this.edit_record();
|
||||||
|
};
|
||||||
this.openFile();
|
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() {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.tbl_list.selectedItem)
|
if (!this.tbl_list.selectedItem)
|
||||||
return;
|
return;
|
||||||
const handle = this.tbl_list.selectedItem.data.handle;
|
const handle = this.tbl_list.selectedItem.data.handle;
|
||||||
await handle.onready();
|
await handle.onready();
|
||||||
const headers = Object.getOwnPropertyNames(handle.info.scheme).map((e) => {
|
const headers = handle.info.schema.fields.map((e) => {
|
||||||
return { text: e };
|
return { text: e };
|
||||||
});
|
});
|
||||||
// read all records
|
// read all records
|
||||||
const records = await handle.read({
|
const filter = { where: {}, limit: 10 };
|
||||||
where: { id$gt: this.last_max_id },
|
filter.where[`${handle.info.schema.pk}\$gt`] = this.last_max_id;
|
||||||
limit: 10
|
const records = await handle.read(filter);
|
||||||
});
|
|
||||||
if (records && records.length > 0) {
|
if (records && records.length > 0) {
|
||||||
for (let e of records) {
|
for (let e of records) {
|
||||||
const row = [];
|
const row = [];
|
||||||
@ -208,7 +340,10 @@ var OS;
|
|||||||
this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`;
|
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 {
|
class NewTableDialog extends OS.GUI.BasicDialog {
|
||||||
/**
|
/**
|
||||||
* Creates an instance of NewTableDialog.
|
* Creates an instance of NewTableDialog.
|
||||||
@ -227,7 +362,6 @@ var OS;
|
|||||||
this.container = this.find("container");
|
this.container = this.find("container");
|
||||||
this.find("btnCancel").onbtclick = (e) => this.quit();
|
this.find("btnCancel").onbtclick = (e) => this.quit();
|
||||||
this.find("btnAdd").onbtclick = (e) => this.addField();
|
this.find("btnAdd").onbtclick = (e) => this.addField();
|
||||||
$(this.find("wrapper"));
|
|
||||||
$(this.container)
|
$(this.container)
|
||||||
.css("overflow-y", "auto");
|
.css("overflow-y", "auto");
|
||||||
this.addField();
|
this.addField();
|
||||||
@ -236,6 +370,7 @@ var OS;
|
|||||||
if (!input.value || input.value == "") {
|
if (!input.value || input.value == "") {
|
||||||
return this.notify(__("Please enter table name"));
|
return this.notify(__("Please enter table name"));
|
||||||
}
|
}
|
||||||
|
const tblname = input.value;
|
||||||
const inputs = $("input", this.container);
|
const inputs = $("input", this.container);
|
||||||
const lists = $("afx-list-view", this.container);
|
const lists = $("afx-list-view", this.container);
|
||||||
if (inputs.length == 0) {
|
if (inputs.length == 0) {
|
||||||
@ -253,7 +388,7 @@ var OS;
|
|||||||
cdata[key] = lists[i].selectedItem.data.text;
|
cdata[key] = lists[i].selectedItem.data.text;
|
||||||
}
|
}
|
||||||
if (this.handle)
|
if (this.handle)
|
||||||
this.handle(cdata);
|
this.handle({ name: tblname, schema: cdata });
|
||||||
this.quit();
|
this.quit();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -304,13 +439,78 @@ var OS;
|
|||||||
<afx-label text="__(Fields in table:)" data-height="30"></afx-label>
|
<afx-label text="__(Fields in table:)" data-height="30"></afx-label>
|
||||||
<div data-id="container" style="position:relative;"></div>
|
<div data-id="container" style="position:relative;"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "35" ></afx-button>
|
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "content" ></afx-button>
|
||||||
<div style = "text-align: right;">
|
<div style = "text-align: right;">
|
||||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||||
</div>
|
</div>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
|
</afx-app-window>`;
|
||||||
|
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 = $("<afx-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 = `\
|
||||||
|
<afx-app-window width='550' height='500'>
|
||||||
|
<afx-vbox padding = "5">
|
||||||
|
<div data-id="container" style="row-gap: 5px;"></div>
|
||||||
|
<afx-hbox data-height="35">
|
||||||
|
<div></div>
|
||||||
|
<div data-width="content">
|
||||||
|
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||||
|
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
</afx-app-window>`;
|
</afx-app-window>`;
|
||||||
})(application = OS.application || (OS.application = {}));
|
})(application = OS.application || (OS.application = {}));
|
||||||
})(OS || (OS = {}));
|
})(OS || (OS = {}));
|
||||||
@ -447,22 +647,24 @@ var OS;
|
|||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
insert(table_name, record) {
|
insert(table_name, record, pk) {
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'insert',
|
action: 'insert',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
}
|
}
|
||||||
update(table_name, record) {
|
update(table_name, record, pk) {
|
||||||
let rq = {
|
let rq = {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
args: {
|
args: {
|
||||||
table_name,
|
table_name,
|
||||||
record
|
record,
|
||||||
|
pk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return this.request(rq);
|
return this.request(rq);
|
||||||
@ -587,16 +789,29 @@ var OS;
|
|||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
await this._handle.init();
|
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) {
|
if (this._table_name) {
|
||||||
const data = await this._handle.get_table_scheme(this._table_name);
|
const data = await this._handle.get_table_scheme(this._table_name);
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
d.result.scheme = undefined;
|
d.result.schema = undefined;
|
||||||
}
|
}
|
||||||
else {
|
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) {
|
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) {
|
_rd(user_data) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
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).__());
|
throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__());
|
||||||
}
|
}
|
||||||
if (!this._table_name) {
|
if (!this._table_name) {
|
||||||
@ -678,14 +893,15 @@ var OS;
|
|||||||
if (!this.cache) {
|
if (!this.cache) {
|
||||||
throw new Error(__("No data to submit to remote database, please check the `cache` field").__());
|
throw new Error(__("No data to submit to remote database, please check the `cache` field").__());
|
||||||
}
|
}
|
||||||
|
await this.onready();
|
||||||
if (this._id && this._table_name) {
|
if (this._id && this._table_name) {
|
||||||
this.cache.id = this._id;
|
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 });
|
resolve({ result: ret, error: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._table_name) {
|
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 });
|
resolve({ result: ret, error: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -709,7 +925,7 @@ var OS;
|
|||||||
_rm(user_data) {
|
_rm(user_data) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
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).__());
|
throw new Error(__("Table `{0}` does not exists in database: {1}", this._table_name, this.path).__());
|
||||||
}
|
}
|
||||||
if (!this._table_name) {
|
if (!this._table_name) {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"pkgname": "SQLiteDB",
|
"pkgname": "SQLiteDB",
|
||||||
"app":"SQLiteDB",
|
"app":"SQLiteDBBrowser",
|
||||||
"name":"SQLite3 VFS API",
|
"name":"SQLite3 Browser",
|
||||||
"description":"API for manipulate SQLite database as VFS handle",
|
"description":"API for manipulate SQLite database as VFS handle",
|
||||||
"info":{
|
"info":{
|
||||||
"author": "",
|
"author": "Dany LE",
|
||||||
"email": ""
|
"email": "mrsang@iohub.dev"
|
||||||
},
|
},
|
||||||
"version":"0.1.0-a",
|
"version":"0.1.0-a",
|
||||||
"category":"Other",
|
"category":"Library",
|
||||||
"iconclass":"fa fa-adn",
|
"iconclass":"bi bi-database",
|
||||||
"mimes":[".*"],
|
"mimes":["application/vnd.sqlite3"],
|
||||||
"dependencies":[],
|
"dependencies":[],
|
||||||
"locale": {}
|
"locale": {}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
<afx-vbox padding="5">
|
<afx-vbox padding="5">
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button data-id = "bt-add-table" iconclass = "bi bi-plus-lg" data-width="content"></afx-button>
|
<afx-button data-id = "bt-add-table" iconclass = "bi bi-plus-lg" data-width="content"></afx-button>
|
||||||
|
<afx-button data-id = "bt-rm-table" iconclass = "bi bi-dash-lg" data-width="content"></afx-button>
|
||||||
<afx-list-view dropdown = "true" data-id="tbl-list"></afx-list-view>
|
<afx-list-view dropdown = "true" data-id="tbl-list"></afx-list-view>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
|
|
||||||
@ -13,12 +14,13 @@
|
|||||||
<afx-hbox tabname = "__(Browse data)" iconclass = "bi bi-table">
|
<afx-hbox tabname = "__(Browse data)" iconclass = "bi bi-table">
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
||||||
|
<div data-height="5"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
||||||
<div></div>
|
<div></div>
|
||||||
<afx-button iconclass="bi bi-plus-lg" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
||||||
<afx-button iconclass="bi bi-pencil-square" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-pencil-square" data-id="btn-edit-record" data-width="content"></afx-button>
|
||||||
<afx-button iconclass="bi bi-trash-fill" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-trash-fill" data-id= "btn-delete-record" data-width="content"></afx-button>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
|
337
SQLiteDB/main.ts
337
SQLiteDB/main.ts
@ -1,12 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Define missing API in Array interface
|
||||||
|
*
|
||||||
|
* @interface Array
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
interface Array<T> {
|
||||||
|
/**
|
||||||
|
* Check if the array includes an element
|
||||||
|
*
|
||||||
|
* @param {T} element to check
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Array
|
||||||
|
*/
|
||||||
|
includes(el: T):boolean;
|
||||||
|
}
|
||||||
|
|
||||||
namespace OS {
|
namespace OS {
|
||||||
export namespace application {
|
export namespace application {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @class SQLiteDB
|
* @class SQLiteDBBrowser
|
||||||
* @extends {BaseApplication}
|
* @extends {BaseApplication}
|
||||||
*/
|
*/
|
||||||
export class SQLiteDB extends BaseApplication {
|
export class SQLiteDBBrowser extends BaseApplication {
|
||||||
|
|
||||||
private filehandle: API.VFS.BaseFileHandle;
|
private filehandle: API.VFS.BaseFileHandle;
|
||||||
private tbl_list: GUI.tag.ListViewTag;
|
private tbl_list: GUI.tag.ListViewTag;
|
||||||
@ -17,7 +34,7 @@ namespace OS {
|
|||||||
private n_records: number;
|
private n_records: number;
|
||||||
private btn_loadmore: GUI.tag.ButtonTag;
|
private btn_loadmore: GUI.tag.ButtonTag;
|
||||||
constructor(args: AppArgumentsType[]) {
|
constructor(args: AppArgumentsType[]) {
|
||||||
super("SQLiteDB", args);
|
super("SQLiteDBBrowser", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu() {
|
menu() {
|
||||||
@ -26,12 +43,12 @@ namespace OS {
|
|||||||
text: "__(File)",
|
text: "__(File)",
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
text: "__(New)",
|
text: "__(New database)",
|
||||||
dataid: "new",
|
dataid: "new",
|
||||||
shortcut: 'A-N'
|
shortcut: 'A-N'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "__(Open)",
|
text: "__(Open database)",
|
||||||
dataid: "open",
|
dataid: "open",
|
||||||
shortcut: 'A-O'
|
shortcut: 'A-O'
|
||||||
},
|
},
|
||||||
@ -63,7 +80,7 @@ namespace OS {
|
|||||||
this.tbl_list.data = list;
|
this.tbl_list.data = list;
|
||||||
if(list.length > 0)
|
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)
|
if(!this.tbl_list.selectedItem)
|
||||||
return;
|
return;
|
||||||
const scheme = this.tbl_list.selectedItem.data.handle.info.scheme;
|
const schema = this.tbl_list.selectedItem.data.handle.info.schema;
|
||||||
if(!scheme)
|
if(!schema)
|
||||||
return;
|
return;
|
||||||
const data = [];
|
const data = [];
|
||||||
for(let k in scheme)
|
for(let k in schema.types)
|
||||||
{
|
{
|
||||||
data.push([
|
data.push([
|
||||||
{ text: k},
|
{ text: k},
|
||||||
{text: scheme[k]}
|
{text: schema.types[k]}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
this.grid_scheme.rows = data;
|
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(ret)
|
||||||
if(!this.filehandle)
|
{
|
||||||
{
|
await this.filehandle.remove(table);
|
||||||
return this.notify(__("Please open a database file"));
|
this.list_tables();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.openDialog(new NewTableDialog(), {
|
catch(e)
|
||||||
title: __("Create new table")
|
{
|
||||||
})
|
this.error(__("Unable to execute action table delete: {0}", e.toString()), e);
|
||||||
.then((data) => {
|
}
|
||||||
console.log(data);
|
|
||||||
});
|
}
|
||||||
|
(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) => {
|
this.btn_loadmore.onbtclick = async (e) => {
|
||||||
try
|
try
|
||||||
@ -173,14 +233,14 @@ namespace OS {
|
|||||||
const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle;
|
const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle;
|
||||||
await handle.onready();
|
await handle.onready();
|
||||||
this.last_max_id = 0;
|
this.last_max_id = 0;
|
||||||
this.grid_table.rows = [];
|
const headers = handle.info.schema.fields.map((e) => {
|
||||||
const headers =
|
return {text: e}
|
||||||
Object.getOwnPropertyNames(handle.info.scheme).map((e)=>{
|
});
|
||||||
return {text: e}
|
|
||||||
});
|
|
||||||
this.grid_table.header = headers;
|
this.grid_table.header = headers;
|
||||||
|
this.grid_table.rows = [];
|
||||||
const records = await handle.read({fields:["COUNT(*)"]});
|
const records = await handle.read({fields:["COUNT(*)"]});
|
||||||
this.n_records = records[0]["(COUNT(*))"];
|
this.n_records = records[0]["(COUNT(*))"];
|
||||||
|
this.btn_loadmore.text = `0/${this.n_records}`;
|
||||||
await this.load_table();
|
await this.load_table();
|
||||||
this.container.selectedIndex = 1;
|
this.container.selectedIndex = 1;
|
||||||
}
|
}
|
||||||
@ -189,12 +249,126 @@ namespace OS {
|
|||||||
this.error(__("Error reading table: {0}", e.toString()),e);
|
this.error(__("Error reading table: {0}", e.toString()),e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.grid_table.oncelldbclick = async (e) => {
|
||||||
|
this.edit_record();
|
||||||
|
}
|
||||||
this.openFile();
|
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()
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@ -202,15 +376,13 @@ namespace OS {
|
|||||||
return;
|
return;
|
||||||
const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle;
|
const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle;
|
||||||
await handle.onready();
|
await handle.onready();
|
||||||
const headers =
|
const headers = handle.info.schema.fields.map((e) => {
|
||||||
Object.getOwnPropertyNames(handle.info.scheme).map((e)=>{
|
return {text: e}
|
||||||
return {text: e}
|
|
||||||
});
|
|
||||||
// read all records
|
|
||||||
const records = await handle.read({
|
|
||||||
where:{ id$gt: this.last_max_id },
|
|
||||||
limit: 10
|
|
||||||
});
|
});
|
||||||
|
// 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)
|
if(records && records.length > 0)
|
||||||
{
|
{
|
||||||
@ -234,13 +406,16 @@ namespace OS {
|
|||||||
}
|
}
|
||||||
this.grid_table.push(row, false);
|
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}`;
|
this.btn_loadmore.text = `${this.grid_table.rows.length}/${this.n_records}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SQLiteDBBrowser.dependencies = [
|
||||||
|
"pkg://SQLiteDB/libsqlite.js"
|
||||||
|
]
|
||||||
|
|
||||||
class NewTableDialog extends GUI.BasicDialog {
|
class NewTableDialog extends GUI.BasicDialog {
|
||||||
/**
|
/**
|
||||||
@ -270,7 +445,6 @@ namespace OS {
|
|||||||
this.container = this.find("container") as HTMLDivElement;
|
this.container = this.find("container") as HTMLDivElement;
|
||||||
(this.find("btnCancel") as GUI.tag.ButtonTag).onbtclick = (e) => this.quit();
|
(this.find("btnCancel") as GUI.tag.ButtonTag).onbtclick = (e) => this.quit();
|
||||||
(this.find("btnAdd") as GUI.tag.ButtonTag).onbtclick = (e) => this.addField();
|
(this.find("btnAdd") as GUI.tag.ButtonTag).onbtclick = (e) => this.addField();
|
||||||
$(this.find("wrapper"))
|
|
||||||
$(this.container)
|
$(this.container)
|
||||||
.css("overflow-y", "auto");
|
.css("overflow-y", "auto");
|
||||||
this.addField();
|
this.addField();
|
||||||
@ -281,7 +455,7 @@ namespace OS {
|
|||||||
{
|
{
|
||||||
return this.notify(__("Please enter table name"));
|
return this.notify(__("Please enter table name"));
|
||||||
}
|
}
|
||||||
|
const tblname = input.value;
|
||||||
const inputs = $("input", this.container) as JQuery<HTMLInputElement>;
|
const inputs = $("input", this.container) as JQuery<HTMLInputElement>;
|
||||||
const lists = $("afx-list-view", this.container) as JQuery<GUI.tag.ListViewTag>;
|
const lists = $("afx-list-view", this.container) as JQuery<GUI.tag.ListViewTag>;
|
||||||
if(inputs.length == 0)
|
if(inputs.length == 0)
|
||||||
@ -301,7 +475,7 @@ namespace OS {
|
|||||||
cdata[key] = lists[i].selectedItem.data.text;
|
cdata[key] = lists[i].selectedItem.data.text;
|
||||||
}
|
}
|
||||||
if (this.handle)
|
if (this.handle)
|
||||||
this.handle(cdata);
|
this.handle({ name: tblname, schema: cdata});
|
||||||
this.quit();
|
this.quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,7 +530,7 @@ namespace OS {
|
|||||||
<afx-label text="__(Fields in table:)" data-height="30"></afx-label>
|
<afx-label text="__(Fields in table:)" data-height="30"></afx-label>
|
||||||
<div data-id="container" style="position:relative;"></div>
|
<div data-id="container" style="position:relative;"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "35" ></afx-button>
|
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "content" ></afx-button>
|
||||||
<div style = "text-align: right;">
|
<div style = "text-align: right;">
|
||||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||||
@ -364,5 +538,90 @@ namespace OS {
|
|||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-app-window>`;
|
</afx-app-window>`;
|
||||||
|
|
||||||
|
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 = $("<afx-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<GUI.tag.InputTag>;
|
||||||
|
const data = {};
|
||||||
|
for(let input of inputs)
|
||||||
|
{
|
||||||
|
data[input.label.__()] = input.value;
|
||||||
|
}
|
||||||
|
if (this.handle)
|
||||||
|
this.handle(data);
|
||||||
|
this.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheme definition
|
||||||
|
*/
|
||||||
|
RecordEditDialog.scheme = `\
|
||||||
|
<afx-app-window width='550' height='500'>
|
||||||
|
<afx-vbox padding = "5">
|
||||||
|
<div data-id="container" style="row-gap: 5px;"></div>
|
||||||
|
<afx-hbox data-height="35">
|
||||||
|
<div></div>
|
||||||
|
<div data-width="content">
|
||||||
|
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||||
|
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-app-window>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"pkgname": "SQLiteDB",
|
"pkgname": "SQLiteDB",
|
||||||
"app":"SQLiteDB",
|
"app":"SQLiteDBBrowser",
|
||||||
"name":"SQLite3 VFS API",
|
"name":"SQLite3 Browser",
|
||||||
"description":"API for manipulate SQLite database as VFS handle",
|
"description":"API for manipulate SQLite database as VFS handle",
|
||||||
"info":{
|
"info":{
|
||||||
"author": "",
|
"author": "Dany LE",
|
||||||
"email": ""
|
"email": "mrsang@iohub.dev"
|
||||||
},
|
},
|
||||||
"version":"0.1.0-a",
|
"version":"0.1.0-a",
|
||||||
"category":"Other",
|
"category":"Library",
|
||||||
"iconclass":"fa fa-adn",
|
"iconclass":"bi bi-database",
|
||||||
"mimes":[".*"],
|
"mimes":["application/vnd.sqlite3"],
|
||||||
"dependencies":[],
|
"dependencies":[],
|
||||||
"locale": {}
|
"locale": {}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
<afx-vbox padding="5">
|
<afx-vbox padding="5">
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button data-id = "bt-add-table" iconclass = "bi bi-plus-lg" data-width="content"></afx-button>
|
<afx-button data-id = "bt-add-table" iconclass = "bi bi-plus-lg" data-width="content"></afx-button>
|
||||||
|
<afx-button data-id = "bt-rm-table" iconclass = "bi bi-dash-lg" data-width="content"></afx-button>
|
||||||
<afx-list-view dropdown = "true" data-id="tbl-list"></afx-list-view>
|
<afx-list-view dropdown = "true" data-id="tbl-list"></afx-list-view>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
|
|
||||||
@ -13,12 +14,13 @@
|
|||||||
<afx-hbox tabname = "__(Browse data)" iconclass = "bi bi-table">
|
<afx-hbox tabname = "__(Browse data)" iconclass = "bi bi-table">
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
||||||
|
<div data-height="5"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
||||||
<div></div>
|
<div></div>
|
||||||
<afx-button iconclass="bi bi-plus-lg" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
||||||
<afx-button iconclass="bi bi-pencil-square" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-pencil-square" data-id="btn-edit-record" data-width="content"></afx-button>
|
||||||
<afx-button iconclass="bi bi-trash-fill" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-trash-fill" data-id= "btn-delete-record" data-width="content"></afx-button>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
|
Loading…
Reference in New Issue
Block a user