diff --git a/Docify/README.md b/Docify/README.md index 666b1ca..91d3e5b 100644 --- a/Docify/README.md +++ b/Docify/README.md @@ -2,6 +2,7 @@ Simple PDF document manager ## Change logs +- v0.1.1-b: move PDF merge and document thumbnail generation to client side, remove server side lua script - v0.1.0-b: use libsqlite for database handling - v0.0.9-b: Adapt to support AntOS 2.0.x - v0.0.8-b: Allow upload files directly from the app diff --git a/Docify/api/api.lua b/Docify/api/api.lua deleted file mode 100644 index 54c172f..0000000 --- a/Docify/api/api.lua +++ /dev/null @@ -1,146 +0,0 @@ -local arg = ... - -ulib = require("ulib") -vfs = require("vfs") - -local handle = {} -local docpath = nil - -local result = function(data) - return { - error = false, - result = data - } -end - -local error = function(data) - return { - error = data, - result = false - } -end - -local mkdirp =function(p) - if not vfs.exists(p) then - if not vfs.mkdir(p) then - return false, error("Unable to create directory: "..p) - end - end - return true, nil -end - -handle.merge_files = function(data) - local firstfile = data.file[1] - local fpath = docpath.."/"..data.cid - local r, e = mkdirp(fpath) - if not r then return e end - fpath = fpath.."/"..os.date("%d-%m-%Y_%H_%M_%S")..".pdf" - -- concat the files - if #data.file > 1 then - local cmd = "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile="..vfs.ospath(fpath) - for i,v in ipairs(data.file) do - cmd = cmd.." \""..vfs.ospath(v).."\"" - end - os.execute(cmd) - if not vfs.exists(fpath) then - return error("Unable to merge PDF files") - end - cmd = "chmod 777 "..vfs.ospath(fpath) - os.execute(cmd) - else - local cmd = "mv \""..vfs.ospath(firstfile).."\" \""..vfs.ospath(fpath).."\"" - os.execute(cmd) - if not vfs.exists(fpath) then - return error("Unable to move PDF file") - end - end - -- move the thumb file to the cache folder - local thumb = docpath.."/cache/"..enc.sha1(firstfile:gsub(docpath, ""))..".png" - local desthumb = docpath.."/cache/"..enc.sha1(fpath:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.move(thumb, desthumb) - end - -- remove all other thumb files - for i,v in ipairs(data.file) do - thumb = docpath.."/cache/"..enc.sha1(v:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.delete(thumb) - end - -- delete all files - if vfs.exists(v) then - vfs.delete(v) - end - end - return result(fpath) -end - -handle.preview = function(path) - -- convert -resize 300x500 noel.pdf[0] thumb.png - local name = enc.sha1(path:gsub(docpath,""))..".png" - -- try to find the thumb - local tpath = docpath.."/cache/"..name - if not vfs.exists(tpath) then - -- regenerate thumb - local cmd = "convert -resize 250x500 \""..vfs.ospath(path).."\"[0] "..vfs.ospath(tpath) - LOG_ERROR(cmd) - os.execute(cmd) - end - - if vfs.exists(tpath) then - local cmd = "chmod 777 "..vfs.ospath(tpath) - os.execute(cmd) - return result(tpath) - else - return error("do not exist") - end -end - -handle.deletedoc = function(param) - -- move file to unclassified - local newfile = docpath.."/unclassified/"..utils.basename(param.file) - vfs.move(param.file, newfile) - -- delete thumb file - local thumb = docpath.."/cache/"..enc.sha1(param.file:gsub(docpath,""))..".png" - if vfs.exists(thumb) then - vfs.delete(thumb) - end - return result("Document entry deleted") -end - -handle.updatedoc = function(param) - local r = handle.merge_files(param.data) - if r.error then return r end - - if param.rm then - -- move ve the old file to unclassified - local newfile = docpath.."/unclassified/"..utils.basename(param.rm) - local cmd = "rm -f "..vfs.ospath(param.rm) - os.execute(cmd) - --if vfs.exists(param.rm) then - -- vfs.move(param.rm, newfile) - --end - -- move the thumb file if needed - local thumb = docpath.."/cache/"..enc.sha1(param.rm:gsub(docpath,""))..".png" - local newwthumb = docpath.."/cache/"..enc.sha1(newfile:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.move(thumb, newwthumb) - end - end - param.data.file = r.result - print(r.result) - param.data.mtime = os.time(os.date("!*t")) - return result(param.data) - --return handle.update({ - -- table = "docs", - -- data = param.data - --}) -end - -if arg.action and handle[arg.action] then - -- check if the database exits - docpath = arg.docpath - - return handle[arg.action](arg.args) -else - return error("Invalid action parameter") -end \ No newline at end of file diff --git a/Docify/assets/scheme.html b/Docify/assets/scheme.html index f3e98cd..0803db8 100644 --- a/Docify/assets/scheme.html +++ b/Docify/assets/scheme.html @@ -20,6 +20,7 @@
+
diff --git a/Docify/build.json b/Docify/build.json index 6ed4d77..0a63208 100644 --- a/Docify/build.json +++ b/Docify/build.json @@ -20,7 +20,7 @@ "name":"locale-gen", "data": { "src": "", - "exclude": ["build/", "api/", "css/", "coffees/"], + "exclude": ["build/", "css/", "coffees/"], "locale": "en_GB", "dest": "package.json" } @@ -70,8 +70,8 @@ "data": { "src": [ "assets/scheme.html", - "api/api.lua", "package.json", + "css/main.css", "README.md" ], "dest": "build/debug" diff --git a/Docify/build/debug/README.md b/Docify/build/debug/README.md index 666b1ca..91d3e5b 100644 --- a/Docify/build/debug/README.md +++ b/Docify/build/debug/README.md @@ -2,6 +2,7 @@ Simple PDF document manager ## Change logs +- v0.1.1-b: move PDF merge and document thumbnail generation to client side, remove server side lua script - v0.1.0-b: use libsqlite for database handling - v0.0.9-b: Adapt to support AntOS 2.0.x - v0.0.8-b: Allow upload files directly from the app diff --git a/Docify/build/debug/api.lua b/Docify/build/debug/api.lua deleted file mode 100644 index 54c172f..0000000 --- a/Docify/build/debug/api.lua +++ /dev/null @@ -1,146 +0,0 @@ -local arg = ... - -ulib = require("ulib") -vfs = require("vfs") - -local handle = {} -local docpath = nil - -local result = function(data) - return { - error = false, - result = data - } -end - -local error = function(data) - return { - error = data, - result = false - } -end - -local mkdirp =function(p) - if not vfs.exists(p) then - if not vfs.mkdir(p) then - return false, error("Unable to create directory: "..p) - end - end - return true, nil -end - -handle.merge_files = function(data) - local firstfile = data.file[1] - local fpath = docpath.."/"..data.cid - local r, e = mkdirp(fpath) - if not r then return e end - fpath = fpath.."/"..os.date("%d-%m-%Y_%H_%M_%S")..".pdf" - -- concat the files - if #data.file > 1 then - local cmd = "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile="..vfs.ospath(fpath) - for i,v in ipairs(data.file) do - cmd = cmd.." \""..vfs.ospath(v).."\"" - end - os.execute(cmd) - if not vfs.exists(fpath) then - return error("Unable to merge PDF files") - end - cmd = "chmod 777 "..vfs.ospath(fpath) - os.execute(cmd) - else - local cmd = "mv \""..vfs.ospath(firstfile).."\" \""..vfs.ospath(fpath).."\"" - os.execute(cmd) - if not vfs.exists(fpath) then - return error("Unable to move PDF file") - end - end - -- move the thumb file to the cache folder - local thumb = docpath.."/cache/"..enc.sha1(firstfile:gsub(docpath, ""))..".png" - local desthumb = docpath.."/cache/"..enc.sha1(fpath:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.move(thumb, desthumb) - end - -- remove all other thumb files - for i,v in ipairs(data.file) do - thumb = docpath.."/cache/"..enc.sha1(v:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.delete(thumb) - end - -- delete all files - if vfs.exists(v) then - vfs.delete(v) - end - end - return result(fpath) -end - -handle.preview = function(path) - -- convert -resize 300x500 noel.pdf[0] thumb.png - local name = enc.sha1(path:gsub(docpath,""))..".png" - -- try to find the thumb - local tpath = docpath.."/cache/"..name - if not vfs.exists(tpath) then - -- regenerate thumb - local cmd = "convert -resize 250x500 \""..vfs.ospath(path).."\"[0] "..vfs.ospath(tpath) - LOG_ERROR(cmd) - os.execute(cmd) - end - - if vfs.exists(tpath) then - local cmd = "chmod 777 "..vfs.ospath(tpath) - os.execute(cmd) - return result(tpath) - else - return error("do not exist") - end -end - -handle.deletedoc = function(param) - -- move file to unclassified - local newfile = docpath.."/unclassified/"..utils.basename(param.file) - vfs.move(param.file, newfile) - -- delete thumb file - local thumb = docpath.."/cache/"..enc.sha1(param.file:gsub(docpath,""))..".png" - if vfs.exists(thumb) then - vfs.delete(thumb) - end - return result("Document entry deleted") -end - -handle.updatedoc = function(param) - local r = handle.merge_files(param.data) - if r.error then return r end - - if param.rm then - -- move ve the old file to unclassified - local newfile = docpath.."/unclassified/"..utils.basename(param.rm) - local cmd = "rm -f "..vfs.ospath(param.rm) - os.execute(cmd) - --if vfs.exists(param.rm) then - -- vfs.move(param.rm, newfile) - --end - -- move the thumb file if needed - local thumb = docpath.."/cache/"..enc.sha1(param.rm:gsub(docpath,""))..".png" - local newwthumb = docpath.."/cache/"..enc.sha1(newfile:gsub(docpath, ""))..".png" - if vfs.exists(thumb) then - vfs.move(thumb, newwthumb) - end - end - param.data.file = r.result - print(r.result) - param.data.mtime = os.time(os.date("!*t")) - return result(param.data) - --return handle.update({ - -- table = "docs", - -- data = param.data - --}) -end - -if arg.action and handle[arg.action] then - -- check if the database exits - docpath = arg.docpath - - return handle[arg.action](arg.args) -else - return error("Invalid action parameter") -end \ No newline at end of file diff --git a/Docify/build/debug/main.css b/Docify/build/debug/main.css index 6b0de1d..1c69b86 100644 --- a/Docify/build/debug/main.css +++ b/Docify/build/debug/main.css @@ -1,4 +1,3 @@ - afx-app-window[data-id = "Docify"] .header .label-text { font-weight: bold; diff --git a/Docify/build/debug/main.js b/Docify/build/debug/main.js index b34f59c..e3feb4f 100644 --- a/Docify/build/debug/main.js +++ b/Docify/build/debug/main.js @@ -1 +1 @@ -var OS;!function(t){let e;!function(e){let i;!function(e){class i extends t.GUI.BasicDialog{constructor(){super("OwnerDialog",i.scheme)}main(){if(super.main(),this.oview=this.find("ownview"),!this.data.dbhandle)throw new Error(__("Unable to get owner data handle").__());return this.oview.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:async t=>{try{const t=await this.openDialog("PromptDialog",{title:__("Owner"),label:__("Name")});this.data.dbhandle.cache={name:t};const e=await this.data.dbhandle.write(void 0);if(e.error)throw new Error(e.error);await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}},{text:"",iconclass:"fa fa-minus-circle",onbtclick:async t=>{try{const t=this.oview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you realy want to delete: `{0}`",t.data.text)}))return;const e=t.data.$vfs;let i=await e.remove();if(i.error)throw new Error(i.error.toString());await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}},{text:"",iconclass:"fa fa-pencil-square-o",onbtclick:async t=>{try{const t=this.oview.selectedItem;if(!t)return;const e=await this.openDialog("PromptDialog",{title:__("Owner"),label:__("Name"),value:t.data.name}),i=t.data.$vfs;i.cache={name:e};const a=await i.write(void 0);if(a.error)throw new Error(a.error.toString());await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}}],this.owner_refresh()}async owner_refresh(){const t=await this.data.dbhandle.read();for(let e of t)e.text=e.name;this.oview.data=t}}e.OwnerDialog=i,i.scheme="\n \n \n \n ";class a extends t.GUI.BasicDialog{constructor(){super("DocDialog",a.scheme)}main(){let t;super.main(),this.flist=this.find("file-list"),this.dlist=this.find("dlist"),this.mlist=this.find("mlist"),this.ylist=this.find("ylist"),this.olist=this.find("olist");const e=this.parent;`sqlite://${e.setting.docpath.asFileHandle().genealogy.join("/")}/docify.db@owners`.asFileHandle().read().then(t=>{if(t.error)return this.error(t.error);for(let e of t)e.text=e.name,e.selected=this.data&&this.data.oid===e.id;return this.olist.data=t,this.olist.selectedItem?void 0:this.olist.selected=0}).catch(t=>this.error(__("Unable to fetch owner list: {0}",t.toString()),t)),this.dlist.push({text:"None",value:0});let i=0;for(t=1;t<=31;t++)this.dlist.push({text:""+t,value:t}),this.data&&parseInt(this.data.day)===t&&(i=t);for(this.dlist.selected=i,this.mlist.push({text:"None",value:0}),i=0,t=1;t<=12;t++)this.mlist.push({text:""+t,value:t}),this.data&&parseInt(this.data.month)===t&&(i=t);this.mlist.selected=i,this.ylist.push({text:"None",value:0}),this.ylist.selected=0;for(let t=1960,e=(new Date).getFullYear(),i=1960<=e;i?t<=e:t>=e;i?t++:t--)this.ylist.push({text:""+t,value:t,selected:this.data&&parseInt(this.data.year)===t});if(this.flist.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:t=>this.openDialog(new r,{app:e}).then(t=>(t.text=t.filename,this.flist.push(t)))},{text:"",iconclass:"fa fa-minus-circle",onbtclick:t=>{const e=this.flist.selectedItem;if(e)return this.flist.delete(e)}}],this.flist.onlistselect=async t=>await e.preview(t.data.item.data.path,this.find("preview-canvas")),this.find("btsave").onbtclick=t=>{const e={name:this.find("title").value.trim(),day:this.dlist.selectedItem.data.value,month:this.mlist.selectedItem.data.value,year:this.ylist.selectedItem.data.value,file:Array.from(this.flist.data).map(t=>t.path),note:this.find("note").value.trim(),tags:this.find("tag").value.trim(),oid:parseInt(this.olist.selectedItem.data.id)};return e.name&&""!==e.title?e.file.length>0?(this.handle&&this.handle(e),this.quit()):this.notify(__("Please attach files to the entry")):this.notify(__("Please enter title"))},!this.data)return;this.find("title").value=this.data.name,this.find("note").value=this.data.note,this.find("tag").value=this.data.tags;const a=this.data.file.asFileHandle();return a.text=a.filename,this.flist.data=[a]}}e.DocDialog=a,a.scheme='\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n
\n \n
\n
\n
\n
';class r extends t.GUI.BasicDialog{constructor(){super("FilePreviewDialog",r.scheme)}main(){super.main(),this.flist=this.find("file-list"),this.flist.buttons=[{text:"",iconclass:"fa fa-refresh",onbtclick:t=>this.refresh()}];const t=this.data.app;return this.flist.onlistselect=async e=>await t.preview(e.data.item.data.path,this.find("preview-canvas")),this.find("btok").onbtclick=t=>{const e=this.flist.selectedItem;return e?(this.handle&&this.handle(e.data),this.quit()):this.quit()},this.refresh()}async refresh(){try{const t=this.data.app,e=await(t.setting.docpath+"/unclassified").asFileHandle().read();if(e.error)return this.error(e.error);for(let t of e.result)t.text=t.filename;return this.flist.data=e.result.filter(t=>"."!==t.filename[0])}catch(t){return this.error(__("Unable to fetch unclassified file list: {0}",t.toString()),t)}}}e.FilePreviewDialog=r,r.scheme='\n \n \n \n \n \n \n
\n \n
\n
\n \n
\n \n
\n
\n
'}(i=e.docify||(e.docify={}))}(e=t.application||(t.application={}))}(OS||(OS={})),function(t){let e;!function(t){class e extends t.BaseApplication{constructor(t){super("Docify",t)}async init_db(){try{if(!this.setting.docpath)return this.error(__("No configured docpath"));const t=this.setting.docpath.asFileHandle();this.dbhandle=`sqlite://${t.genealogy.join("/")}/docify.db`.asFileHandle();const e=await this.dbhandle.read();await(""+this.setting.docpath).asFileHandle().mk("unclassified"),await(""+this.setting.docpath).asFileHandle().mk("cache");let i=void 0;if(this.catdb=(this.dbhandle.path+"@categories").asFileHandle(),!e.categories){if(this.dbhandle.cache={name:"TEXT"},i=await this.dbhandle.write("categories"),i.error)throw new Error(i.error);if(this.catdb.cache={name:"Uncategoried"},i=await this.catdb.write(void 0),i.error)throw new Error(i.error)}if(this.ownerdb=(this.dbhandle.path+"@owners").asFileHandle(),!e.owners){if(this.dbhandle.cache={name:"TEXT"},i=await this.dbhandle.write("owners"),i.error)throw new Error(i.error);if(this.ownerdb.cache={name:"None"},i=await this.ownerdb.write(void 0),i.error)throw new Error(i.error)}if(this.docdb=(this.dbhandle.path+"@docs").asFileHandle(),!e.docs&&(this.dbhandle.cache={name:"TEXT NOT NULL",ctime:"INTEGER",day:"INTEGER",month:"INTEGER",year:"INTEGER",cid:"INTEGER DEFAULT 0",oid:"INTEGER DEFAULT 0",file:"TEXT NOT NULL",tags:"TEXT",note:"TEXT",mtime:"INTEGER"},i=await this.dbhandle.write("docs"),i.error))throw new Error(i.error);return await this.cat_refresh()}catch(t){this.error(__("Unable to init database file: {0}",t.toString()),t),this.dbhandle=void 0}}main(){return this.setting.printer||(this.setting.printer=""),this.catview=this.find("catview"),this.docview=this.find("docview"),this.docpreview=this.find("preview-canvas"),this.docgrid=this.find("docgrid"),this.docgrid.header=[{text:"",width:100},{text:""}],this.find("btdld").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;await t.data.file.asFileHandle().download()}catch(t){this.error(__("Unable to download: {0}",t.toString()),t)}},this.find("btopen").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;const e=await t.data.file.asFileHandle().meta();if(e.error)throw new Error(e.error);return this._gui.openWith(e.result)}catch(t){this.error(__("Unable to open file: {0}",t.toString()),t)}},this.catview.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:async t=>{try{const t=await this.openDialog("PromptDialog",{title:__("Category"),label:__("Name")});this.catdb.cache={name:t};const e=await this.catdb.write(void 0);if(e.error)throw new Error(e.error.toString());return await this.cat_refresh()}catch(t){this.error(__("Unable to insert category: {0}",t.toString()),t)}}},{text:"",iconclass:"fa fa-minus-circle",onbtclick:async t=>{try{const t=this.catview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you realy want to delete: `{0}`",t.data.text)}))return;const e=await this.catdb.remove({where:{id:t.data.id}});if(e.error)throw new Error(e.error.toString());await this.cat_refresh()}catch(t){this.error(__("Unable delete category: {0}",t.toString()),t)}}},{text:"",iconclass:"fa fa-pencil-square-o",onbtclick:async t=>{try{const t=this.catview.selectedItem;if(!t)return;const e=t.data;if(!e)return;const i=await this.openDialog("PromptDialog",{title:__("Category"),label:__("Name"),value:t.data.name}),a=e.$vfs;a.cache={id:parseInt(t.data.id),name:i};const r=await a.write(void 0);if(r.error)throw new Error(r.error.toString());await this.cat_refresh()}catch(t){this.error(__("Unable to update category: {0}",t.toString()),t)}}}],this.docview.onlistselect=async t=>{try{this.clear_preview();const e=t.data.item;if(!e)return;const i=e.data.$vfs,a=await i.read();await this.preview(a.file,this.docpreview);const r=[],s={ctime:"Created on",mtime:"Modified on",note:"Note",tags:"Tags",name:"Title",owner:"Owner",edate:"Effective date",file:"File",size:"Size"};a.edate=`${a.day}/${a.month}/${a.year}`;for(let t in a){let e=a[t];const i=s[t];"ctime"!==t&&"mtime"!=t||(e=new Date(1e3*e).toDateString()),i&&r.push([{text:i},{text:e}])}return this.docgrid.rows=r}catch(t){this.error(__("Unable to fetch document detail: {0}",t.toString()),t)}},this.catview.onlistselect=t=>{this.clear_preview();const e=t.data.item;if(e)return this.update_doclist(e.data.id)},this.find("bt-add-doc").onbtclick=async e=>{try{const e=this.catview.selectedItem;if(!e)return this.notify(__("Please select a category"));const i=await this.openDialog(new t.docify.DocDialog);i.cid=parseInt(e.data.id);const a=Math.floor(Date.now()/1e3);i.ctime=a,i.mtime=a;const r=await this.exec("merge_files",i);if(r.error)throw new Error(r.error.toString());i.file=r.result,this.docdb.cache=i;const s=await this.docdb.write(void 0);if(s.error)throw new Error(s.error.toString());s.result&&this.toast(s.result),this.update_doclist(e.data.id),this.clear_preview()}catch(t){this.error(__("Unable to add document: {0}",t.toString()),t)}},this.find("bt-del-doc").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you really want to delete: `{0}`",t.data.name)}))return;let e=await this.docdb.remove({where:{id:t.data.id}});if(e.error)throw new Error(e.error.toString());if(e=await this.exec("deletedoc",{file:t.data.file}),e.error)throw new Error(e.error.toString());return this.notify(e.result.toString()),this.update_doclist(t.data.cid),this.clear_preview()}catch(t){this.error(__("Unable to delete document: {0}",t.tostring()),t)}},this.find("bt-upload-doc").onbtclick=async t=>{try{await(this.setting.docpath+"/unclassified").asFileHandle().upload(),this.toast(__("File uploaded"))}catch(t){this.error(__("Unable to upload document: {0}",t.toString()),t)}},this.find("bt-edit-doc").onbtclick=async e=>{try{const e=this.docview.selectedItem,i=this.catview.selectedItem;if(!e)return;const a=await this.openDialog(new t.docify.DocDialog,e.data);a.cid=parseInt(i.data.id),a.id=e.data.id;const r=Math.floor(Date.now()/1e3);a.mtime=r;let s=await this.exec("updatedoc",{data:a,rm:!a.file.includes(e.data.file)&&e.data.file});if(s.error)throw new Error(s.error);const n=e.data.$vfs;if(n.cache=s.result,s=await n.write(void 0),s.error)throw new Error(s.error);return s.result&&this.toast(s.result),this.update_doclist(i.data.id),this.clear_preview()}catch(t){this.error(__("Unable to edit document metadata: {0}",t.toString()))}},this.initialize()}async update_doclist(t){try{const e=await this.docdb.read({where:{cid:t},order:["year$desc","month$desc","day$desc"]});if(e.error)throw new Error(e.error);for(let t of e)t.text=t.name;return this.docview.data=e}catch(t){this.error(__("Unable to update document list: {0}",t.toString()),t)}}clear_preview(){return this.docpreview.getContext("2d").clearRect(0,0,this.docpreview.width,this.docpreview.height),this.docgrid.rows=[]}async preview(t,e){try{const i=await this.exec("preview",t);if(i.error)throw new Error(i.error);const a=i.result.asFileHandle(),r=await a.read("binary"),s=new Image;s.onload=()=>{const t=e.getContext("2d");return e.height=s.height,e.width=s.width,t.drawImage(s,0,0)};const n=new Blob([r],{type:a.info.mime});return s.src=URL.createObjectURL(n)}catch(t){this.error(__("Unable to generate document thumbnail: {0}",t.toString()),t)}}cat_refresh(){return new Promise(async(t,e)=>{try{this.docview.data=[],this.clear_preview();const t=await this.catdb.read();for(let e of t)e.text=e.name;return this.catview.data=t}catch(t){e(__e(t))}})}async initialize(){try{if(this.setting.docpath)return await this.init_db();{const t=await this.openDialog("FileDialog",{title:__("Please select a doc path"),type:"dir"});return this.setting.docpath=t.file.path,await this.init_db()}}catch(t){this.error(__("Error initialize database: {0}",t.toString()),t)}}exec(t,e){const i={path:this.path()+"/api.lua",parameters:{action:t,docpath:this.setting.docpath,args:e}};return this.call(i)}menu(){return[{text:"__(Options)",nodes:[{text:"__(Owners)",id:"owners"},{text:"__(Preview)",id:"preview"},{text:"__(Change doc path)",id:"setdocp"}],onchildselect:t=>this.fileMenuHandle(t.data.item.data.id)}]}fileMenuHandle(e){switch(e){case"owners":return this.openDialog(new t.docify.OwnerDialog,{title:__("Owners"),dbhandle:this.ownerdb});case"preview":return this.openDialog(new t.docify.FilePreviewDialog,{app:this}).then(t=>this.notify(t.path));case"setdocp":return this.setting.docpath=void 0,this.initialize()}}}t.Docify=e,e.dependencies=["pkg://SQLiteDB/libsqlite.js"]}(e=t.application||(t.application={}))}(OS||(OS={})); \ No newline at end of file +var OS;!function(t){let e;!function(e){let i;!function(e){class i extends t.GUI.BasicDialog{constructor(){super("OwnerDialog",i.scheme)}main(){if(super.main(),this.oview=this.find("ownview"),!this.data.dbhandle)throw new Error(__("Unable to get owner data handle").__());return this.oview.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:async t=>{try{const t=await this.openDialog("PromptDialog",{title:__("Owner"),label:__("Name")});this.data.dbhandle.cache={name:t};const e=await this.data.dbhandle.write(void 0);if(e.error)throw new Error(e.error);await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}},{text:"",iconclass:"fa fa-minus-circle",onbtclick:async t=>{try{const t=this.oview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you realy want to delete: `{0}`",t.data.text)}))return;const e=t.data.$vfs;let i=await e.remove();if(i.error)throw new Error(i.error.toString());await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}},{text:"",iconclass:"fa fa-pencil-square-o",onbtclick:async t=>{try{const t=this.oview.selectedItem;if(!t)return;const e=await this.openDialog("PromptDialog",{title:__("Owner"),label:__("Name"),value:t.data.name}),i=t.data.$vfs;i.cache={name:e};const a=await i.write(void 0);if(a.error)throw new Error(a.error.toString());await this.owner_refresh()}catch(t){this.error(t.toString(),t)}}}],this.owner_refresh()}async owner_refresh(){const t=await this.data.dbhandle.read();for(let e of t)e.text=e.name;this.oview.data=t}}e.OwnerDialog=i,i.scheme="\n \n \n \n ";class a extends t.GUI.BasicDialog{constructor(){super("DocDialog",a.scheme)}main(){let t;super.main(),this.flist=this.find("file-list"),this.dlist=this.find("dlist"),this.mlist=this.find("mlist"),this.ylist=this.find("ylist"),this.olist=this.find("olist");const e=this.parent;`sqlite://${e.setting.docpath.asFileHandle().genealogy.join("/")}/docify.db@owners`.asFileHandle().read().then(t=>{if(t.error)return this.error(t.error);for(let e of t)e.text=e.name,e.selected=this.data&&this.data.oid===e.id;return this.olist.data=t,this.olist.selectedItem?void 0:this.olist.selected=0}).catch(t=>this.error(__("Unable to fetch owner list: {0}",t.toString()),t)),this.dlist.push({text:"None",value:0});let i=0;for(t=1;t<=31;t++)this.dlist.push({text:""+t,value:t}),this.data&&parseInt(this.data.day)===t&&(i=t);for(this.dlist.selected=i,this.mlist.push({text:"None",value:0}),i=0,t=1;t<=12;t++)this.mlist.push({text:""+t,value:t}),this.data&&parseInt(this.data.month)===t&&(i=t);this.mlist.selected=i,this.ylist.push({text:"None",value:0}),this.ylist.selected=0;for(let t=1960,e=(new Date).getFullYear(),i=1960<=e;i?t<=e:t>=e;i?t++:t--)this.ylist.push({text:""+t,value:t,selected:this.data&&parseInt(this.data.year)===t});if(this.flist.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:t=>this.openDialog(new r,{app:e}).then(t=>(t.text=t.filename,this.flist.push(t)))},{text:"",iconclass:"fa fa-minus-circle",onbtclick:t=>{const e=this.flist.selectedItem;if(e)return this.flist.delete(e)}}],this.flist.onlistselect=async t=>await e.preview(t.data.item.data.path,this.find("preview-canvas")),this.find("btsave").onbtclick=t=>{const e={name:this.find("title").value.trim(),day:this.dlist.selectedItem.data.value,month:this.mlist.selectedItem.data.value,year:this.ylist.selectedItem.data.value,file:Array.from(this.flist.data).map(t=>t.path),note:this.find("note").value.trim(),tags:this.find("tag").value.trim(),oid:parseInt(this.olist.selectedItem.data.id)};return e.name&&""!==e.title?e.file.length>0?(this.handle&&this.handle(e),this.quit()):this.notify(__("Please attach files to the entry")):this.notify(__("Please enter title"))},!this.data)return;this.find("title").value=this.data.name,this.find("note").value=this.data.note,this.find("tag").value=this.data.tags;const a=this.data.file.asFileHandle();return a.text=a.filename,this.flist.data=[a]}}e.DocDialog=a,a.scheme='\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n
\n \n
\n
\n
\n
';class r extends t.GUI.BasicDialog{constructor(){super("FilePreviewDialog",r.scheme)}main(){super.main(),this.flist=this.find("file-list"),this.flist.buttons=[{text:"",iconclass:"fa fa-refresh",onbtclick:t=>this.refresh()}];const t=this.data.app;return this.flist.onlistselect=async e=>await t.preview(e.data.item.data.path,this.find("preview-canvas")),this.find("btok").onbtclick=t=>{const e=this.flist.selectedItem;return e?(this.handle&&this.handle(e.data),this.quit()):this.quit()},this.refresh()}async refresh(){try{const t=this.data.app,e=await(t.setting.docpath+"/unclassified").asFileHandle().read();if(e.error)return this.error(e.error);for(let t of e.result)t.text=t.filename;return this.flist.data=e.result.filter(t=>"."!==t.filename[0])}catch(t){return this.error(__("Unable to fetch unclassified file list: {0}",t.toString()),t)}}}e.FilePreviewDialog=r,r.scheme='\n \n \n \n \n \n \n
\n \n
\n
\n \n
\n \n
\n
\n
'}(i=e.docify||(e.docify={}))}(e=t.application||(t.application={}))}(OS||(OS={})),function(t){let e;!function(t){class e extends t.BaseApplication{constructor(t){super("Docify",t)}async init_db(){try{if(!this.setting.docpath)return this.error(__("No configured docpath"));const t=this.setting.docpath.asFileHandle();this.dbhandle=`sqlite://${t.genealogy.join("/")}/docify.db`.asFileHandle();const e=await this.dbhandle.read();await(""+this.setting.docpath).asFileHandle().mk("unclassified"),await(""+this.setting.docpath).asFileHandle().mk("cache");let i=void 0;if(this.catdb=(this.dbhandle.path+"@categories").asFileHandle(),!e.categories){if(this.dbhandle.cache={name:"TEXT"},i=await this.dbhandle.write("categories"),i.error)throw new Error(i.error);if(this.catdb.cache={name:"Uncategoried"},i=await this.catdb.write(void 0),i.error)throw new Error(i.error)}if(this.ownerdb=(this.dbhandle.path+"@owners").asFileHandle(),!e.owners){if(this.dbhandle.cache={name:"TEXT"},i=await this.dbhandle.write("owners"),i.error)throw new Error(i.error);if(this.ownerdb.cache={name:"None"},i=await this.ownerdb.write(void 0),i.error)throw new Error(i.error)}if(this.docdb=(this.dbhandle.path+"@docs").asFileHandle(),!e.docs&&(this.dbhandle.cache={name:"TEXT NOT NULL",ctime:"INTEGER",day:"INTEGER",month:"INTEGER",year:"INTEGER",cid:"INTEGER DEFAULT 0",oid:"INTEGER DEFAULT 0",file:"TEXT NOT NULL",tags:"TEXT",note:"TEXT",mtime:"INTEGER"},i=await this.dbhandle.write("docs"),i.error))throw new Error(i.error);return await this.cat_refresh()}catch(t){this.error(__("Unable to init database file: {0}",t.toString()),t),this.dbhandle=void 0}}main(){return this.setting.printer||(this.setting.printer=""),pdfjsLib.GlobalWorkerOptions.workerSrc="pkg://libpdfjs/pdf.worker.js".asFileHandle().getlink(),this.catview=this.find("catview"),this.docview=this.find("docview"),this.docpreview=this.find("preview-canvas"),this.docgrid=this.find("docgrid"),this.docgrid.header=[{text:"",width:100},{text:""}],this.find("btdld").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;await t.data.file.asFileHandle().download()}catch(t){this.error(__("Unable to download: {0}",t.toString()),t)}},this.find("btopen").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;const e=await t.data.file.asFileHandle().meta();if(e.error)throw new Error(e.error);return this._gui.openWith(e.result)}catch(t){this.error(__("Unable to open file: {0}",t.toString()),t)}},this.catview.buttons=[{text:"",iconclass:"fa fa-plus-circle",onbtclick:async t=>{try{const t=await this.openDialog("PromptDialog",{title:__("Category"),label:__("Name")});this.catdb.cache={name:t};const e=await this.catdb.write(void 0);if(e.error)throw new Error(e.error.toString());return await this.cat_refresh()}catch(t){this.error(__("Unable to insert category: {0}",t.toString()),t)}}},{text:"",iconclass:"fa fa-minus-circle",onbtclick:async t=>{try{const t=this.catview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you realy want to delete: `{0}`",t.data.text)}))return;const e=await this.catdb.remove({where:{id:t.data.id}});if(e.error)throw new Error(e.error.toString());await this.cat_refresh()}catch(t){this.error(__("Unable delete category: {0}",t.toString()),t)}}},{text:"",iconclass:"fa fa-pencil-square-o",onbtclick:async t=>{try{const t=this.catview.selectedItem;if(!t)return;const e=t.data;if(!e)return;const i=await this.openDialog("PromptDialog",{title:__("Category"),label:__("Name"),value:t.data.name}),a=e.$vfs;a.cache={id:parseInt(t.data.id),name:i};const r=await a.write(void 0);if(r.error)throw new Error(r.error.toString());await this.cat_refresh()}catch(t){this.error(__("Unable to update category: {0}",t.toString()),t)}}}],this.docview.onlistselect=async t=>{try{this.clear_preview();const e=t.data.item;if(!e)return;const i=e.data.$vfs,a=await i.read();await this.preview(a.file,this.docpreview);const r=[],s={ctime:"Created on",mtime:"Modified on",note:"Note",tags:"Tags",name:"Title",owner:"Owner",edate:"Effective date",file:"File",size:"Size"};a.edate=`${a.day}/${a.month}/${a.year}`;for(let t in a){let e=a[t];const i=s[t];"ctime"!==t&&"mtime"!=t||(e=new Date(1e3*e).toDateString()),i&&r.push([{text:i},{text:e}])}return this.docgrid.rows=r}catch(t){this.error(__("Unable to fetch document detail: {0}",t.toString()),t)}},this.catview.onlistselect=t=>{this.clear_preview();const e=t.data.item;if(e)return this.update_doclist(e.data.id)},this.find("bt-add-doc").onbtclick=async e=>{try{const e=this.catview.selectedItem;if(!e)return this.notify(__("Please select a category"));const i=await this.openDialog(new t.docify.DocDialog);i.cid=parseInt(e.data.id);const a=Math.floor(Date.now()/1e3);i.ctime=a,i.mtime=a;const r=await this.merge_files(i);i.file=r.path,this.docdb.cache=i;const s=await this.docdb.write(void 0);if(s.error)throw new Error(s.error.toString());s.result&&this.toast(s.result),this.update_doclist(e.data.id),this.clear_preview()}catch(t){this.error(__("Unable to add document: {0}",t.toString()),t)}},this.find("bt-del-doc").onbtclick=async t=>{try{const t=this.docview.selectedItem;if(!t)return;if(!await this.ask({text:__("Do you really want to delete: `{0}`",t.data.name)}))return;let e=await this.docdb.remove({where:{id:t.data.id}});if(e.error)throw new Error(e.error.toString());try{await t.data.file.asFileHandle().remove();const e=await this.get_thumb_path(t.data.file);await e.asFileHandle().remove()}catch(t){console.log(t)}return this.update_doclist(t.data.cid),this.clear_preview()}catch(t){this.error(__("Unable to delete document: {0}",t.tostring()),t)}},this.find("bt-upload-doc").onbtclick=async t=>{try{await(this.setting.docpath+"/unclassified").asFileHandle().upload(),this.toast(__("File uploaded"))}catch(t){this.error(__("Unable to upload document: {0}",t.toString()),t)}},this.find("bt-edit-doc").onbtclick=async e=>{try{const e=this.docview.selectedItem,i=this.catview.selectedItem;if(!e)return;const a=await this.openDialog(new t.docify.DocDialog,e.data);a.cid=parseInt(i.data.id),a.id=e.data.id;const r=Math.floor(Date.now()/1e3);if(a.mtime=r,a.file.includes(e.data.file)&&1==a.file.length)a.file=e.data.file;else{if(!a.file.includes(e.data.file))try{console.log("remove old file",e.data.file),await e.data.file.asFileHandle().remove();const t=await this.get_thumb_path(e.data.file);await t.asFileHandle().remove()}catch(t){console.log(t)}const t=await this.merge_files(a);a.file=t.path}a.mtime=Math.floor(Date.now()/1e3);const s=e.data.$vfs;s.cache=a;const n=await s.write(void 0);if(n.error)throw new Error(n.error);return this.toast(__("Document updated")),this.update_doclist(i.data.id),this.clear_preview()}catch(t){this.error(__("Unable to edit document metadata: {0}",t.toString()))}},this.initialize()}async get_thumb_path(t){const e=t.asFileHandle().path;let i=await this.sha1(e.replace(this.setting.docpath,""));return`${this.setting.docpath}/cache/${i}.png`}async merge_files(t){const e=t.file,i=t.cid.toFixed(1).toString(),a=`${this.setting.docpath}/${i}`.asFileHandle();try{await a.onready()}catch(t){const e=await a.parent().mk(i);if(e.error)throw new Error(e.error.toString());await a.onready()}const r=`${a.path}/${new Date(Date.now()).getTime().toString()}.pdf`.asFileHandle(),s=await PDFLib.PDFDocument.create();let n=[];for(const t of e){const e=await t.asFileHandle().read("binary"),i=await PDFLib.PDFDocument.load(e,{ignoreEncryption:!0}),a=await s.copyPages(i,i.getPageIndices());n=n.concat(a)}for(let t=0;tt.toString(16).padStart(2,"0")).join("")}async genthumb(t){const e=(await this.get_thumb_path(t)).asFileHandle();try{await e.onready()}catch(i){console.log("Try to generate thumb file for",t);const a=this.find("tmp-canvas"),r=a.getContext("2d"),s=await pdfjsLib.getDocument(t.asFileHandle().getlink()).promise,n=await s.getPage(1),o=n.getViewport({scale:.33}),l=window.devicePixelRatio||1;a.width=Math.floor(o.width*l),a.height=Math.floor(o.height*l),a.style.width=Math.floor(o.width)+"px",a.style.height=Math.floor(o.height)+"px";const d={canvasContext:r,transform:1!==l?[l,0,0,l,0,0]:null,viewport:o};await n.render(d).promise;const h=a.toDataURL("image/png");e.cache=h;const c=await e.write("base64");if(c.error)throw new Error(c.error.toString());await e.onready()}return e}async preview(t,e){try{const i=await this.genthumb(t),a=await i.read("binary"),r=new Image;r.onload=()=>{const t=e.getContext("2d");return e.height=r.height,e.width=r.width,t.drawImage(r,0,0)};const s=new Blob([a],{type:i.info.mime});return r.src=URL.createObjectURL(s)}catch(t){this.error(__("Unable to generate document thumbnail: {0}",t.toString()),t)}}cat_refresh(){return new Promise(async(t,e)=>{try{this.docview.data=[],this.clear_preview();const t=await this.catdb.read();for(let e of t)e.text=e.name;return this.catview.data=t}catch(t){e(__e(t))}})}async initialize(){try{if(this.setting.docpath)return await this.init_db();{const t=await this.openDialog("FileDialog",{title:__("Please select a doc path"),type:"dir"});return this.setting.docpath=t.file.path,await this.init_db()}}catch(t){this.error(__("Error initialize database: {0}",t.toString()),t)}}menu(){return[{text:"__(Options)",nodes:[{text:"__(Owners)",id:"owners"},{text:"__(Preview)",id:"preview"},{text:"__(Change doc path)",id:"setdocp"}],onchildselect:t=>this.fileMenuHandle(t.data.item.data.id)}]}fileMenuHandle(e){switch(e){case"owners":return this.openDialog(new t.docify.OwnerDialog,{title:__("Owners"),dbhandle:this.ownerdb});case"preview":return this.openDialog(new t.docify.FilePreviewDialog,{app:this}).then(t=>this.notify(t.path));case"setdocp":return this.setting.docpath=void 0,this.initialize()}}}t.Docify=e,e.dependencies=["pkg://SQLiteDB/libsqlite.js","pkg://libpdfjs/pdf.js","pkg://PDFLib/main.js"]}(e=t.application||(t.application={}))}(OS||(OS={})); \ No newline at end of file diff --git a/Docify/build/debug/package.json b/Docify/build/debug/package.json index 3f09d19..0848d86 100644 --- a/Docify/build/debug/package.json +++ b/Docify/build/debug/package.json @@ -7,14 +7,16 @@ "author": "Dany LE", "email": "mrsang@iohub.dev" }, - "version": "0.1.0-b", + "version": "0.1.1-b", "category": "Office", "iconclass": "bi bi-collection-fill", "mimes": [ "none" ], "dependencies": [ - "SQLiteDB@0.1.0-a" + "SQLiteDB@0.1.0-a", + "libpdfjs@2.6.347-r", + "PDFLib@1.17.1" ], "locale": {}, "locales": { diff --git a/Docify/build/debug/scheme.html b/Docify/build/debug/scheme.html index f3e98cd..0803db8 100644 --- a/Docify/build/debug/scheme.html +++ b/Docify/build/debug/scheme.html @@ -20,6 +20,7 @@
+
diff --git a/Docify/build/release/Docify.zip b/Docify/build/release/Docify.zip index 0a74a3b..53bd729 100644 Binary files a/Docify/build/release/Docify.zip and b/Docify/build/release/Docify.zip differ diff --git a/Docify/package.json b/Docify/package.json index 3f09d19..0848d86 100644 --- a/Docify/package.json +++ b/Docify/package.json @@ -7,14 +7,16 @@ "author": "Dany LE", "email": "mrsang@iohub.dev" }, - "version": "0.1.0-b", + "version": "0.1.1-b", "category": "Office", "iconclass": "bi bi-collection-fill", "mimes": [ "none" ], "dependencies": [ - "SQLiteDB@0.1.0-a" + "SQLiteDB@0.1.0-a", + "libpdfjs@2.6.347-r", + "PDFLib@1.17.1" ], "locale": {}, "locales": { diff --git a/Docify/ts/main.ts b/Docify/ts/main.ts index d468020..8c5f007 100644 --- a/Docify/ts/main.ts +++ b/Docify/ts/main.ts @@ -1,5 +1,7 @@ namespace OS { export namespace application { + declare var pdfjsLib; + declare var PDFLib; export class Docify extends BaseApplication { private catview: GUI.tag.ListViewTag; @@ -108,7 +110,8 @@ namespace OS { main() { if (!this.setting.printer) { this.setting.printer = ""; } - + pdfjsLib.GlobalWorkerOptions.workerSrc = "pkg://libpdfjs/pdf.worker.js".asFileHandle().getlink(); + this.catview = this.find("catview") as GUI.tag.ListViewTag; this.docview = this.find("docview") as GUI.tag.ListViewTag; this.docpreview = this.find("preview-canvas") as HTMLCanvasElement; @@ -288,12 +291,13 @@ namespace OS { const timestamp = Math.floor(Date.now() / 1000); data.ctime = timestamp; data.mtime = timestamp; - const r = await this.exec("merge_files", data); - if(r.error) + const r = await this.merge_files(data); + data.file = r.path; + /*if(r.error) { throw new Error(r.error.toString()); } - data.file = r.result; + data.file = r.result;*/ this.docdb.cache = data; const d = await this.docdb.write(undefined); if(d.error) @@ -326,12 +330,16 @@ namespace OS { { throw new Error(r.error.toString()); } - r = await this.exec("deletedoc", {file: item.data.file}); - if(r.error) - { - throw new Error(r.error.toString()); + // delete file + try { + await item.data.file.asFileHandle().remove(); + const thumb = await this.get_thumb_path(item.data.file); + await thumb.asFileHandle().remove(); + } + catch(e) + { + console.log(e); } - this.notify(r.result.toString()); this.update_doclist(item.data.cid); return this.clear_preview(); } @@ -362,22 +370,40 @@ namespace OS { data.id = item.data.id; const timestamp = Math.floor(Date.now() / 1000); data.mtime = timestamp; - let d = await this.exec("updatedoc", { - data, - rm: !data.file.includes(item.data.file) ? item.data.file : false - }); - if(d.error) + if(data.file.includes(item.data.file) && data.file.length == 1) { - throw new Error(d.error); + // nothing changes + data.file = item.data.file; } + else + { + if(!data.file.includes(item.data.file)) + { + // remove old file + try { + console.log("remove old file", item.data.file); + await item.data.file.asFileHandle().remove(); + const thumb = await this.get_thumb_path(item.data.file); + await thumb.asFileHandle().remove(); + } + catch(e) + { + console.log(e); + } + } + // merge all PDF file + const merged_file = await this.merge_files(data); + data.file = merged_file.path; + } + data.mtime = Math.floor(Date.now() / 1000); const handle = item.data.$vfs; - handle.cache = d.result; - d = await handle.write(undefined); + handle.cache = data; + const d = await handle.write(undefined); if(d.error) { throw new Error(d.error); } - if (d.result) { this.toast(d.result); } + this.toast(__("Document updated")); this.update_doclist(catiem.data.id); return this.clear_preview(); } @@ -389,6 +415,90 @@ namespace OS { return this.initialize(); } + private async get_thumb_path(filepath: string| API.VFS.BaseFileHandle) + { + const path = filepath.asFileHandle().path; + let thumb_name = await this.sha1(path.replace(this.setting.docpath,"")); + return `${this.setting.docpath}/cache/${thumb_name}.png`; + } + + private async merge_files(data) { + const paths: string[] = data.file; + const cat = data.cid.toFixed(1).toString(); + const dir = `${this.setting.docpath}/${cat}`.asFileHandle(); + try{ + await dir.onready(); + } + catch(_) + { + const ret = await dir.parent().mk(cat); + if(ret.error) + { + throw new Error(ret.error.toString()); + } + await dir.onready(); + } + const des_file = `${dir.path}/${new Date(Date.now()).getTime().toString()}.pdf`.asFileHandle(); + // concat the file + const pdfdoc = await PDFLib.PDFDocument.create(); + let pages = []; + for(const path of paths) + { + const arr = await path.asFileHandle().read("binary"); + const doc = await PDFLib.PDFDocument.load(arr, { ignoreEncryption: true }); + const copiedpages = await pdfdoc.copyPages(doc, doc.getPageIndices()); + pages = pages.concat(copiedpages); + } + for (let i = 0; i < pages.length; i++) { + await pdfdoc.insertPage(i, pages[i]); + } + const buffer = await pdfdoc.save(); + des_file.cache = new Blob([buffer]); + const ret = await des_file.write("binary"); + if(ret.error) + { + throw new Error(ret.error.toString()); + } + // move thumb file + let src_tfile = await this.get_thumb_path(paths[0]); + const dest_tfile = await this.get_thumb_path(des_file); + try { + console.log("Move", src_tfile, "to", dest_tfile); + const ret = await src_tfile.asFileHandle().move(dest_tfile); + if(ret.error) + { + console.log(ret.error); + } + } + catch(e) + { + console.log(e); + } + // remove other file and thumb file + for(const path of paths) + { + try{ + src_tfile = await this.get_thumb_path(path); + console.log("'Remove file", path, src_tfile); + let ret = await path.asFileHandle().remove(); + if(ret.error) + { + console.log(ret); + } + ret = await src_tfile.asFileHandle().remove(); + if(ret.error) + { + console.log(ret); + } + } + catch(e) + { + console.log(e); + } + } + return des_file; + } + private async update_doclist(cid: any) { try { @@ -399,7 +509,6 @@ namespace OS { order: ["year$desc", "month$desc", "day$desc"] }); - // this.exec("select",{table: "docs", cond:`cid = ${cid} ORDER BY year DESC, month DESC, day DESC`}); if(d.error) { throw new Error(d.error); @@ -420,14 +529,72 @@ namespace OS { this.docpreview.getContext('2d').clearRect(0,0,this.docpreview.width,this.docpreview.height); return this.docgrid.rows = []; } + + private async sha1(str) { + const enc = new TextEncoder(); + const hash = await crypto.subtle.digest('SHA-1', enc.encode(str)); + return Array.from(new Uint8Array(hash)) + .map(v => (v.toString(16) as any).padStart(2, '0')) + .join(''); + } + private async genthumb(path: string) + { + /** try to search if the thumb file exists, + * if it does not exist, generate it using + * pdfjs library and an hidden canvas + */ + const tpath = await this.get_thumb_path(path); + + const file = tpath.asFileHandle(); + try { + await file.onready(); + } + catch(e) + { + // generate thumb file + //data = await file.read("binary"); + console.log("Try to generate thumb file for", path); + const canvas = this.find("tmp-canvas") as HTMLCanvasElement; + const context = canvas.getContext('2d'); + const pdf = await pdfjsLib.getDocument(path.asFileHandle().getlink()).promise; + + const page = await pdf.getPage(1); + + const viewport = page.getViewport({ scale: 0.33}); + // Support HiDPI-screens. + const outputScale = window.devicePixelRatio || 1; + + canvas.width = Math.floor(viewport.width * outputScale); + canvas.height = Math.floor(viewport.height * outputScale); + canvas.style.width = Math.floor(viewport.width) + "px"; + canvas.style.height = Math.floor(viewport.height) + "px"; + + const transform = outputScale !== 1 + ? [outputScale, 0, 0, outputScale, 0, 0] + : null; + + const renderContext = { + canvasContext: context, + transform: transform, + viewport: viewport + }; + await page.render(renderContext).promise; + const url = canvas.toDataURL('image/png'); + file.cache = url; + const ret = await file.write("base64"); + if(ret.error) + { + throw new Error(ret.error.toString()); + } + await file.onready(); + } + return file; + } + async preview(path: any, canvas: HTMLCanvasElement) { try { - const d = await this.exec("preview", path); - if (d.error) { - throw new Error(d.error); - } - const file = d.result.asFileHandle(); + const file = await this.genthumb(path); const data = await file.read("binary"); const img = new Image(); //($ me.view).append img @@ -435,7 +602,6 @@ namespace OS { const context = canvas.getContext('2d'); canvas.height = img.height; canvas.width = img.width; - //console.log canvas.width, canvas.height return context.drawImage(img, 0, 0); }; @@ -492,18 +658,6 @@ namespace OS { } } - exec(action: string, args?: GenericObject) { - const cmd = { - path: `${this.path()}/api.lua`, - parameters: { - action, - docpath: this.setting.docpath, - args - } - }; - return this.call(cmd); - } - menu() { return [ { @@ -538,6 +692,10 @@ namespace OS { } } } - Docify.dependencies = ["pkg://SQLiteDB/libsqlite.js"]; + Docify.dependencies = [ + "pkg://SQLiteDB/libsqlite.js", + "pkg://libpdfjs/pdf.js", + "pkg://PDFLib/main.js" + ]; } } \ No newline at end of file diff --git a/MarkOn/build/release/MarkOn.zip b/MarkOn/build/release/MarkOn.zip index ebd4f2e..ab65e85 100644 Binary files a/MarkOn/build/release/MarkOn.zip and b/MarkOn/build/release/MarkOn.zip differ diff --git a/packages.json b/packages.json index 4ecbeaa..9b8722c 100644 --- a/packages.json +++ b/packages.json @@ -69,6 +69,16 @@ "dependencies": ["Antunnel@0.2.0-b"], "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/AntunnelPlugins/build/release/AntunnelPlugins.zip" }, + { + "pkgname": "AntunnelTestClient", + "name": "AntunnelTestClient", + "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/AntunnelTestClient/README.md", + "category": "Development", + "author": "Dany LE", + "version": "0.1.0-a", + "dependencies": ["Antunnel@0.2.1-b"], + "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/AntunnelTestClient/build/release/AntunnelTestClient.zip" + }, { "pkgname": "Archive", "name": "Archive", @@ -145,8 +155,8 @@ "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Docify/README.md", "category": "Office", "author": "Dany LE", - "version": "0.1.0-b", - "dependencies": ["SQLiteDB@0.1.0-a"], + "version": "0.1.1-b", + "dependencies": ["SQLiteDB@0.1.0-a","libpdfjs@2.6.347-r","PDFLib@1.17.1"], "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Docify/build/release/Docify.zip" }, { diff --git a/release.lua b/release.lua index ac06adb..4e22418 100644 --- a/release.lua +++ b/release.lua @@ -41,7 +41,7 @@ end local packages = {} -for i,v in ipairs(r) do +for i,v in pairs(r) do if v.type == "dir" then local ar_file = v.path.."/build/release/"..v.filename..".zip" local meta_file = v.path.."/package.json" @@ -74,6 +74,12 @@ for i,v in ipairs(r) do download = release_url..v.filename..".zip" } table.insert(packages, pkg) + --else + -- if not ulib.exists(ar_file) then + -- output("AR file not found "..ar_file) + -- else + -- output("Meta file not found "..meta_file) + -- end end end end diff --git a/release/Docify.md b/release/Docify.md index 666b1ca..91d3e5b 100644 --- a/release/Docify.md +++ b/release/Docify.md @@ -2,6 +2,7 @@ Simple PDF document manager ## Change logs +- v0.1.1-b: move PDF merge and document thumbnail generation to client side, remove server side lua script - v0.1.0-b: use libsqlite for database handling - v0.0.9-b: Adapt to support AntOS 2.0.x - v0.0.8-b: Allow upload files directly from the app diff --git a/release/Docify.zip b/release/Docify.zip index 0a74a3b..53bd729 100644 Binary files a/release/Docify.zip and b/release/Docify.zip differ diff --git a/release/MarkOn.zip b/release/MarkOn.zip index ebd4f2e..ab65e85 100644 Binary files a/release/MarkOn.zip and b/release/MarkOn.zip differ diff --git a/release/libjpeg.md b/release/libjpeg.md new file mode 100644 index 0000000..0d94227 --- /dev/null +++ b/release/libjpeg.md @@ -0,0 +1,4 @@ +# libjpeg +Simple JPEG/DCT data decoder in JavaScript. Also this project includes JPEG 2000 and JBIG2 decoders. + +Github page: [https://github.com/notmasteryet/jpgjs](https://github.com/notmasteryet/jpgjs) \ No newline at end of file diff --git a/release/libjpeg.zip b/release/libjpeg.zip new file mode 100644 index 0000000..1718403 Binary files /dev/null and b/release/libjpeg.zip differ diff --git a/release/packages.json b/release/packages.json index 505a018..9cf6de5 100644 --- a/release/packages.json +++ b/release/packages.json @@ -1 +1 @@ -[{"name":"PDF JS library","dependencies":[],"pkgname":"libpdfjs","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libpdfjs.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libpdfjs.md","version":"2.6.347-r","author":"Xuan Sang LE","category":"Library"},{"name":"Antunnel Plugins","dependencies":["Antunnel@0.2.0-b"],"pkgname":"AntunnelPlugins","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelPlugins.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelPlugins.md","version":"0.1.2-a","author":"Dany LE","category":"Library"},{"name":"PDFLib","dependencies":[],"pkgname":"PDFLib","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/PDFLib.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/PDFLib.md","version":"1.17.1","author":"pdf-lib.js.org","category":"Library"},{"name":"xTerm Library","dependencies":[],"pkgname":"xTerm","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/xTerm.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/xTerm.md","version":"5.1.0-r","author":"","category":"Library"},{"name":"Server log monitor","dependencies":["Antunnel@0.2.1-b"],"pkgname":"ServerLogClient","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ServerLogClient.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ServerLogClient.md","version":"0.1.3-b","author":"","category":"System"},{"name":"Viz editor","dependencies":["ACECore@1.4.12-r"],"pkgname":"VizApp","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/VizApp.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/VizApp.md","version":"0.1.0-a","author":"Xuan Sang LE","category":"Graphics"},{"name":"LuaPlayground","dependencies":["ACECore@1.4.12-r"],"pkgname":"LuaPlayground","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LuaPlayground.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LuaPlayground.md","version":"0.1.1-a","author":"Xuan Sang LE","category":"Development"},{"name":"Clipper","dependencies":[],"pkgname":"Clipper","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Clipper.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Clipper.md","version":"0.1.4-a","author":"Xuan Sang LE","category":"Utility"},{"name":"Fabric.js library","dependencies":[],"pkgname":"libfabric","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libfabric.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libfabric.md","version":"4.4.0-r","author":"","category":"Library"},{"name":"Image editor","dependencies":["libfabric@4.4.0-r"],"pkgname":"ImageEditor","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ImageEditor.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ImageEditor.md","version":"0.1.0-a","author":"Xuan Sang LE","category":"Graphics"},{"name":"Antos Editor","dependencies":["MonacoCore@0.33.0-r"],"pkgname":"Antedit","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antedit.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antedit.md","version":"0.2.5-b","author":"Xuan Sang LE","category":"Development"},{"name":"Monaco editor core","dependencies":[],"pkgname":"MonacoCore","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MonacoCore.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MonacoCore.md","version":"0.33.0-r","author":"","category":"Library"},{"name":"Office Suite","dependencies":[],"pkgname":"OnlyOffice","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/OnlyOffice.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/OnlyOffice.md","version":"0.1.8-a","author":"Xuan Sang LE","category":"Office"},{"name":"AceDiff addon library","dependencies":["ACECore@1.4.12-r"],"pkgname":"AceDiff","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AceDiff.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AceDiff.md","version":"3.0.3-r","author":"","category":"Library"},{"name":"Tiny editor","dependencies":[],"pkgname":"TinyEditor","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/TinyEditor.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/TinyEditor.md","version":"0.0.4-a","author":"Xuan Sang LE","category":"Other"},{"name":"WVNC remote desktop","dependencies":[],"pkgname":"RemoteDesktop","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteDesktop.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteDesktop.md","version":"0.1.16-b","author":"Dany LE","category":"Internet"},{"name":"ACE Editor core","dependencies":[],"pkgname":"ACECore","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ACECore.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ACECore.md","version":"1.4.12-r","author":"","category":"Library"},{"name":"Generic Purpose client","dependencies":[],"pkgname":"GPClient","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GPClient.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GPClient.md","version":"0.1.4-a","author":"Xuan Sang LE","category":"Internet"},{"name":"Preview","dependencies":["libpdfjs@2.6.347-r"],"pkgname":"Preview","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Preview.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Preview.md","version":"0.1.3-a","author":"Xuan Sang LE","category":"Graphics"},{"name":"Blogging application","dependencies":["SimpleMDE@2.18.0-r","Katex@0.11.1-r","SQLiteDB@0.1.0-a"],"pkgname":"Blogger","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Blogger.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Blogger.md","version":"0.2.12-a","author":"Xuan Sang LE","category":"Internet"},{"name":"Remote Camera","dependencies":["libjpeg@0.1.1-a","Antunnel@0.1.8-a"],"pkgname":"RemoteCamera","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteCamera.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteCamera.md","version":"0.1.5-a","author":"","category":"Graphics"},{"name":"Docify","dependencies":["SQLiteDB@0.1.0-a"],"pkgname":"Docify","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Docify.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Docify.md","version":"0.1.0-b","author":"Dany LE","category":"Office"},{"name":"Libre Office Online","dependencies":[],"pkgname":"LibreOffice","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LibreOffice.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LibreOffice.md","version":"0.1.4-a","author":"Dany LE","category":"Office"},{"name":"Activity monitor","dependencies":[],"pkgname":"ActivityMonitor","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ActivityMonitor.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ActivityMonitor.md","version":"0.0.8-b","author":"Xuan Sang LE","category":"System"},{"name":"Virtual Terminal","dependencies":["Antunnel@0.2.1-b","xTerm@5.1.0-r"],"pkgname":"vTerm","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vTerm.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vTerm.md","version":"0.1.20-a","author":"Xuan Sang LE","category":"System"},{"name":"AntOS VFS handles","dependencies":[],"pkgname":"vfsx","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vfsx.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vfsx.md","version":"0.1.1-b","author":"Dany LE","category":"Library"},{"name":"About AntOS","dependencies":[],"pkgname":"About","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/About.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/About.md","version":"0.1.2-b","author":"Xuan Sang LE","category":"Utility"},{"name":"libwvnc","dependencies":["libjpeg@0.1.1-a"],"pkgname":"libwvnc","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libwvnc.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libwvnc.md","version":"0.1.2-a","author":"","category":"Library"},{"name":"Code","dependencies":["ACECore@1.4.12-r"],"pkgname":"CodePad","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/CodePad.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/CodePad.md","version":"0.1.7-b","author":"Xuan Sang LE","category":"Development"},{"name":"Booklet","dependencies":["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],"pkgname":"Booklet","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Booklet.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Booklet.md","version":"0.2.5-a","author":"Xuan Sang LE","category":"Office"},{"name":"Antunnel","dependencies":[],"pkgname":"Antunnel","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antunnel.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antunnel.md","version":"0.2.1-b","author":"Xuan Sang LE","category":"Library"},{"name":"Archive","dependencies":[],"pkgname":"Archive","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Archive.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Archive.md","version":"0.0.4-a","author":"Xuan Sang LE","category":"Utility"},{"name":"ShowCase","dependencies":[],"pkgname":"ShowCase","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShowCase.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShowCase.md","version":"0.0.7-a","author":"Xuan Sang LE","category":"Utility"},{"name":"libthreejs","dependencies":[],"pkgname":"libthreejs","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libthreejs.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libthreejs.md","version":"0.0.129-r","author":"","category":"Library"},{"name":"Graph Editor","dependencies":["ACECore@1.4.12-r"],"pkgname":"GraphEditor","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GraphEditor.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GraphEditor.md","version":"0.1.2-a","author":"Xuan Sang LE","category":"Graphics"},{"name":"System monitoring","dependencies":["Antunnel@0.2.1-b"],"pkgname":"SystemControl","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SystemControl.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SystemControl.md","version":"0.1.12-a","author":"","category":"System"},{"name":"OpenGL Shader Playground","dependencies":["libthreejs@0.0.129-r"],"pkgname":"ShaderPlayground","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShaderPlayground.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShaderPlayground.md","version":"0.0.4-a","author":"Xuan Sang LE","category":"Development"},{"name":"Katex","dependencies":[],"pkgname":"Katex","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Katex.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Katex.md","version":"0.11.1-r","author":"","category":"Library"},{"name":"Plotly","dependencies":[],"pkgname":"libplotly","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libplotly.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libplotly.md","version":"2.6.2-r","author":"Dany LE","category":"Library"},{"name":"DBDecoder","dependencies":[],"pkgname":"DBDecoder","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DBDecoder.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DBDecoder.md","version":"0.0.2-a","author":"","category":"Other"},{"name":"EasyMDE","dependencies":[],"pkgname":"SimpleMDE","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SimpleMDE.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SimpleMDE.md","version":"2.18.0-r","author":"","category":"Library"},{"name":"AntOS SDK builder","dependencies":[],"pkgname":"libantosdk","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libantosdk.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libantosdk.md","version":"0.1.2-b","author":"Xuan Sang LE","category":"Development"},{"name":"GIT Visualization","dependencies":[],"pkgname":"GitGraph","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GitGraph.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GitGraph.md","version":"0.1.5-b","author":"Dany LE","category":"Development"},{"name":"Diff Editor","dependencies":["AceDiff@3.0.3-r"],"pkgname":"DiffEditor","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DiffEditor.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DiffEditor.md","version":"0.1.6-a","author":"","category":"Development"},{"name":"SQLite3 Browser","dependencies":[],"pkgname":"SQLiteDB","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SQLiteDB.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SQLiteDB.md","version":"0.1.0-a","author":"Dany LE","category":"Library"},{"name":"Markdown editor","dependencies":["SimpleMDE@2.18.0-r"],"pkgname":"MarkOn","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MarkOn.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MarkOn.md","version":"0.1.1-a","author":"Xuan Sang LE","category":"Office"},{"name":"Remote Docker Manager","dependencies":[],"pkgname":"Dockman","download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Dockman.zip","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Dockman.md","version":"0.1.1-b","author":"Xuan Sang LE","category":"Development"}] \ No newline at end of file +[{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Archive.zip","category":"Utility","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Archive.md","dependencies":[],"name":"Archive","pkgname":"Archive","version":"0.0.4-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libwvnc.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libwvnc.md","dependencies":["libjpeg@0.1.1-a"],"name":"libwvnc","pkgname":"libwvnc","version":"0.1.2-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/About.zip","category":"Utility","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/About.md","dependencies":[],"name":"About AntOS","pkgname":"About","version":"0.1.2-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Blogger.zip","category":"Internet","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Blogger.md","dependencies":["SimpleMDE@2.18.0-r","Katex@0.11.1-r","SQLiteDB@0.1.0-a"],"name":"Blogging application","pkgname":"Blogger","version":"0.2.12-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GitGraph.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GitGraph.md","dependencies":[],"name":"GIT Visualization","pkgname":"GitGraph","version":"0.1.5-b","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LibreOffice.zip","category":"Office","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LibreOffice.md","dependencies":[],"name":"Libre Office Online","pkgname":"LibreOffice","version":"0.1.4-a","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AceDiff.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AceDiff.md","dependencies":["ACECore@1.4.12-r"],"name":"AceDiff addon library","pkgname":"AceDiff","version":"3.0.3-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/OnlyOffice.zip","category":"Office","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/OnlyOffice.md","dependencies":[],"name":"Office Suite","pkgname":"OnlyOffice","version":"0.1.8-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vfsx.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vfsx.md","dependencies":[],"name":"AntOS VFS handles","pkgname":"vfsx","version":"0.1.1-b","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/PDFLib.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/PDFLib.md","dependencies":[],"name":"PDFLib","pkgname":"PDFLib","version":"1.17.1","author":"pdf-lib.js.org"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antedit.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antedit.md","dependencies":["MonacoCore@0.33.0-r"],"name":"Antos Editor","pkgname":"Antedit","version":"0.2.5-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Booklet.zip","category":"Office","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Booklet.md","dependencies":["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],"name":"Booklet","pkgname":"Booklet","version":"0.2.5-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Docify.zip","category":"Office","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Docify.md","dependencies":["SQLiteDB@0.1.0-a","libpdfjs@2.6.347-r","PDFLib@1.17.1"],"name":"Docify","pkgname":"Docify","version":"0.1.1-b","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelPlugins.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelPlugins.md","dependencies":["Antunnel@0.2.0-b"],"name":"Antunnel Plugins","pkgname":"AntunnelPlugins","version":"0.1.2-a","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShowCase.zip","category":"Utility","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShowCase.md","dependencies":[],"name":"ShowCase","pkgname":"ShowCase","version":"0.0.7-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteDesktop.zip","category":"Internet","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteDesktop.md","dependencies":[],"name":"WVNC remote desktop","pkgname":"RemoteDesktop","version":"0.1.16-b","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Clipper.zip","category":"Utility","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Clipper.md","dependencies":[],"name":"Clipper","pkgname":"Clipper","version":"0.1.4-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antunnel.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Antunnel.md","dependencies":[],"name":"Antunnel","pkgname":"Antunnel","version":"0.2.1-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteCamera.zip","category":"Graphics","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/RemoteCamera.md","dependencies":["libjpeg@0.1.1-a","Antunnel@0.1.8-a"],"name":"Remote Camera","pkgname":"RemoteCamera","version":"0.1.5-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DiffEditor.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DiffEditor.md","dependencies":["AceDiff@3.0.3-r"],"name":"Diff Editor","pkgname":"DiffEditor","version":"0.1.6-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libthreejs.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libthreejs.md","dependencies":[],"name":"libthreejs","pkgname":"libthreejs","version":"0.0.129-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SimpleMDE.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SimpleMDE.md","dependencies":[],"name":"EasyMDE","pkgname":"SimpleMDE","version":"2.18.0-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SQLiteDB.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SQLiteDB.md","dependencies":[],"name":"SQLite3 Browser","pkgname":"SQLiteDB","version":"0.1.0-a","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GPClient.zip","category":"Internet","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GPClient.md","dependencies":[],"name":"Generic Purpose client","pkgname":"GPClient","version":"0.1.4-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vTerm.zip","category":"System","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/vTerm.md","dependencies":["Antunnel@0.2.1-b","xTerm@5.1.0-r"],"name":"Virtual Terminal","pkgname":"vTerm","version":"0.1.20-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ActivityMonitor.zip","category":"System","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ActivityMonitor.md","dependencies":[],"name":"Activity monitor","pkgname":"ActivityMonitor","version":"0.0.8-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShaderPlayground.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ShaderPlayground.md","dependencies":["libthreejs@0.0.129-r"],"name":"OpenGL Shader Playground","pkgname":"ShaderPlayground","version":"0.0.4-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MonacoCore.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MonacoCore.md","dependencies":[],"name":"Monaco editor core","pkgname":"MonacoCore","version":"0.33.0-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LuaPlayground.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/LuaPlayground.md","dependencies":["ACECore@1.4.12-r"],"name":"LuaPlayground","pkgname":"LuaPlayground","version":"0.1.1-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/VizApp.zip","category":"Graphics","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/VizApp.md","dependencies":["ACECore@1.4.12-r"],"name":"Viz editor","pkgname":"VizApp","version":"0.1.0-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ServerLogClient.zip","category":"System","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ServerLogClient.md","dependencies":["Antunnel@0.2.1-b"],"name":"Server log monitor","pkgname":"ServerLogClient","version":"0.1.3-b","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Dockman.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Dockman.md","dependencies":[],"name":"Remote Docker Manager","pkgname":"Dockman","version":"0.1.1-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SystemControl.zip","category":"System","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/SystemControl.md","dependencies":["Antunnel@0.2.1-b"],"name":"System monitoring","pkgname":"SystemControl","version":"0.1.12-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libpdfjs.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libpdfjs.md","dependencies":[],"name":"PDF JS library","pkgname":"libpdfjs","version":"2.6.347-r","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ImageEditor.zip","category":"Graphics","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ImageEditor.md","dependencies":["libfabric@4.4.0-r"],"name":"Image editor","pkgname":"ImageEditor","version":"0.1.0-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DBDecoder.zip","category":"Other","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/DBDecoder.md","dependencies":[],"name":"DBDecoder","pkgname":"DBDecoder","version":"0.0.2-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Katex.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Katex.md","dependencies":[],"name":"Katex","pkgname":"Katex","version":"0.11.1-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libplotly.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libplotly.md","dependencies":[],"name":"Plotly","pkgname":"libplotly","version":"2.6.2-r","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/CodePad.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/CodePad.md","dependencies":["ACECore@1.4.12-r"],"name":"Code","pkgname":"CodePad","version":"0.1.7-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelTestClient.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/AntunnelTestClient.md","dependencies":["Antunnel@0.2.1-b"],"name":"AntunnelTestClient","pkgname":"AntunnelTestClient","version":"0.1.0-a","author":"Dany LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libjpeg.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libjpeg.md","dependencies":[],"name":"libjpeg","pkgname":"libjpeg","version":"0.1.1-a","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libfabric.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libfabric.md","dependencies":[],"name":"Fabric.js library","pkgname":"libfabric","version":"4.4.0-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libantosdk.zip","category":"Development","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/libantosdk.md","dependencies":[],"name":"AntOS SDK builder","pkgname":"libantosdk","version":"0.1.2-b","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/xTerm.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/xTerm.md","dependencies":[],"name":"xTerm Library","pkgname":"xTerm","version":"5.1.0-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/TinyEditor.zip","category":"Other","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/TinyEditor.md","dependencies":[],"name":"Tiny editor","pkgname":"TinyEditor","version":"0.0.4-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ACECore.zip","category":"Library","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/ACECore.md","dependencies":[],"name":"ACE Editor core","pkgname":"ACECore","version":"1.4.12-r","author":""},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Preview.zip","category":"Graphics","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/Preview.md","dependencies":["libpdfjs@2.6.347-r"],"name":"Preview","pkgname":"Preview","version":"0.1.3-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GraphEditor.zip","category":"Graphics","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/GraphEditor.md","dependencies":["ACECore@1.4.12-r"],"name":"Graph Editor","pkgname":"GraphEditor","version":"0.1.2-a","author":"Xuan Sang LE"},{"download":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MarkOn.zip","category":"Office","description":"https://ci.iohub.dev/public/antos-release/packages/2.0.x/MarkOn.md","dependencies":["SimpleMDE@2.18.0-r"],"name":"Markdown editor","pkgname":"MarkOn","version":"0.1.1-a","author":"Xuan Sang LE"}] \ No newline at end of file