diff --git a/www/html/_assets/js/studio.js b/www/html/_assets/js/studio.js
index ff50283..ee7d79b 100644
--- a/www/html/_assets/js/studio.js
+++ b/www/html/_assets/js/studio.js
@@ -1,6 +1,7 @@
ej.base.registerLicense('ORg4AjUWIQA/Gnt2XFhhQlJHfV5AQmBIYVp/TGpJfl96cVxMZVVBJAtUQF1hTH5VdkxhWnpWcHZcTmhfWkZ/')
+ej.spreadsheet.Spreadsheet.Inject(ej.spreadsheet.ExcelExport);
var studio = {_context:''}
studio.defaults = {
'duckdb':'SELECT * \nFROM INFORMATION_SCHEMA.TABLES',
@@ -58,6 +59,16 @@ studio.dbe.providers = function (_render){
// })
// }
+function triggerDownload(blob, filename) {
+ const url = URL.createObjectURL(blob); // create a temporary URL
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = filename; // suggest a filename
+ document.body.appendChild(a); // Firefox needs it in DOM
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url); // cleanup
+}
studio.grid = function (){
this.columns = function (_data){
var _columns = []
@@ -161,8 +172,9 @@ studio.grid = function (){
})
var spreadsheet = new ej.spreadsheet.Spreadsheet();
+ _sheet_name = _id.trim().replace(/[#,.]/g,'').replace(/ /g,'-').trim()
spreadsheet.sheets = [
- {name:_id.replace(/[#,.]/g,' '),
+ {name:_sheet_name,
ranges:[{dataSource:rows}],
}
@@ -170,7 +182,7 @@ studio.grid = function (){
if( $(_id)[0].spreadsheet ){
book = $(_id)[0].spreadsheet ;
book.sheets.forEach((sheet)=>{
- if(sheet.name != _id.replace(/[#,.]/g,' ')){
+ if(sheet.name != _sheet_name){
spreadsheet.sheets.push(sheet)
}
@@ -188,7 +200,8 @@ studio.grid = function (){
// }
// ]
-
+
+ spreadsheet.saveUrl = studio._context+'api/io/write'
spreadsheet.appendTo(_id)
$(_id)[0].spreadsheet = spreadsheet
@@ -254,7 +267,7 @@ studio.frame = function (_args){
}
this.open = function (_id,file){
var http = HttpClient.instance()
- _uri = 'api/io/open'
+ _uri = 'api/io/read'
var form = new FormData()
form.append('file',file)
http.setData(form)
@@ -295,7 +308,8 @@ studio.frame = function (_args){
})
}
- this.export = function(_label,spreadsheet){
+
+ this._export = function(_label,spreadsheet){
var uri = 'api/io/write'
var http = HttpClient.instance()
var _data = {}
@@ -357,12 +371,58 @@ studio.frame = function (_args){
})
}
}
-
+ this.export = function (_label,spreadsheet){
+ // spreadsheet.save({filename:_label+'-export.xlsx'})
+ uri = studio._context+'api/io/write'
+
+ filename = _label+'.xlsx'
+ spreadsheet.saveAsJson().then(response => {
+ // const fd = new FormData();
+ // fd.append('JSONData', JSON.stringify(response.jsonObject.Workbook));
+ // fd.append('fileName', filename);
+ form = {'JSONData':response.jsonObject.Workbook,'fileName':filename}
+ // r = fetch(uri, { method: 'POST', body: fd });
+ var http = HttpClient.instance()
+ http.setHeader('Content-Type','application/json')
+ http.setData ( JSON.stringify(form))
+ http.post(uri,(x)=>{
+ if(x.status == 200 && x.readyState == 4){
+ //
+ //
+ // console.log(x)
+ // var blob = new Blob([x.response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+ const byteCharacters = atob(x.responseText);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ const blob = new Blob([byteArray], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ });
+
+ const link = document.createElement("a");
+ link.href = URL.createObjectURL(blob);
+ link.download = _label+'-export.xlsx';
+ document.body.appendChild(link);
+ link.click();
+
+ // Cleanup
+ URL.revokeObjectURL(link.href);
+ document.body.removeChild(link);
+
+ }
+ })
+
+ });
+
+ }
this.render = function (){
var _args = this._args
var _importSheet = this.open
var _exportSheet = this.export
+
var _icon = $('
').attr('src',this._args.icon)
_text = _args.provider
if (_args.AI){
diff --git a/www/html/_plugins/io.py b/www/html/_plugins/io.py
index d1646fe..0e9bbad 100644
--- a/www/html/_plugins/io.py
+++ b/www/html/_plugins/io.py
@@ -11,25 +11,29 @@ import json
import base64
from flask import make_response, send_file
import copy
+import json
+import plugin_ix
+import os
-@cms.Plugin(mimetype="application/json",method="POST")
-def read(**_args) :
- _request = _args['request']
- _body = _request.json
- _data = {}
- try:
- reader = transport.get.reader(label = _body['label'])
- _query = _body['query']
- #
- # @TODO :
- # - support for mongodb and NoSQL ...
- # - limit to 10K
- _data = reader.apply(_query)
- _data.to_dict(orient='split')
- del _data['index']
- except Exception as e:
- print (e)
- return _data
+
+# @cms.Plugin(mimetype="application/json",method="POST")
+# def read(**_args) :
+# _request = _args['request']
+# _body = _request.json
+# _data = {}
+# try:
+# reader = transport.get.reader(label = _body['label'])
+# _query = _body['query']
+# #
+# # @TODO :
+# # - support for mongodb and NoSQL ...
+# # - limit to 10K
+# _data = reader.apply(_query)
+# _data.to_dict(orient='split')
+# del _data['index']
+# except Exception as e:
+# print (e)
+# return _data
def format (file,_mimetype) :
if 'csv' in _mimetype :
_df = pd.read_csv(file).fillna('n/a')
@@ -40,31 +44,43 @@ def format (file,_mimetype) :
return {'Workbook':{'sheets':sheets}}
-@cms.Plugin(mimetype="text/plain")
-def python (**_args) :
- _request = _args['request']
- _code ="""
- # generated code
-
- import pandas as pd
- import transport
-
-
- #
- # genereric data reader
- dreader = transport.get.reader(label='{_label}')
- _df = dreader.read(sql=_sql)
-
-"""
@cms.Plugin(mimetype="application/json",method="POST")
-def open(**_args):
+def read(**_args):
+ """
+ This function will in theory open a spreadsheet or csv file
+ """
_request = _args['request']
file = _request.files['file']
spreadsheet = format(file,file.mimetype)
return spreadsheet #{"Workbook":{"sheets":[{"usedRange":{"colIndex":1,"rowIndex":3},"name":"Sheet1","rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"steve"},{"value":"44"}]},{"cells":[{"value":"elon"},{"value":"9"}]},{"cells":[{"value":"nico"},{"value":"33"}]}],"standardHeight":20}]}})
@cms.Plugin(mimetype="application/octet-stream",method="POST")
-def write(**_args) :
+def write(**_args):
+ _request = _args['request']
+ _data = _request.json #json.loads(_request.form['JSONData'])
+ print (_data.keys())
+ # _data = json.loads(_request.form['JSONData'])
+ # _data = {"JSONData":_data}
+ _config = _args['config']
+ _path = os.sep.join([_config['layout']['location'],_config['layout']['root'],'_plugins'])
+ # if os.path.exists(_path) :
+ _filename = os.sep.join([_path,'ms-excel.py'])
+ _lx = plugin_ix.Loader(file=_filename)
+ _getExcelStream = _lx._modules.get('instance')
+ _stream = _getExcelStream(_data)
+ print (_stream.read())
+ _stream.seek(0)
+ return base64.b64encode(_stream.read()).decode('utf-8')
+ # response = make_response(_stream.getvalue())
+
+ # # Set headers for file download
+ # filename = _request.form['fileName']
+ # response.headers["Content-Disposition"] = f"attachment; filename={filename}"
+ # response.headers["Content-Type"] = "application/octet-stream" # Or a more specific MIME type like 'text/plain'
+ # return response
+
+
+def _write(**_args) :
"""
This functon generates an excel file, by returning the encoded 64bit stream
"""
diff --git a/www/html/_plugins/ms-excel.py b/www/html/_plugins/ms-excel.py
new file mode 100644
index 0000000..f7bb7a5
--- /dev/null
+++ b/www/html/_plugins/ms-excel.py
@@ -0,0 +1,94 @@
+import json
+import pandas as pd
+import io
+import xlsxwriter as xlx
+
+class Exporter :
+ def __init__(self,_data):
+ """
+ The data is passed by syncfusion
+ """
+ self._data = _data
+ self._sheets = _data['sheets'] if 'JSONData' not in _data else _data['JSONData']['sheets']
+ self._stream = io.BytesIO()
+ self._workbook = xlx.Workbook(self._stream)
+ def _getColRowIndex(self,_label):
+ """
+ This function will return column/row given an excel specification for instance C1 -> 2,0
+ """
+ _ndxCol = []
+ _ndxRow = []
+ for xchar in _label.strip() :
+ if xchar.isnumeric() :
+
+ _ndxRow.append( xchar )
+ else:
+ _ndxCol.append( ord(xchar) -65)
+
+ return sum(_ndxCol), int("".join(_ndxRow)) -1
+
+ def _initData (self):
+
+ self._charts = []
+ for _info in self._sheets :
+ _rows = _info['rows']
+ _name = _info['name'] #-- sheet name
+ _icol,_irow = self._getColRowIndex( _info.get('topLeftCell','A1'))
+ _worksheet = self._workbook.add_worksheet(_name)
+
+ for _item in _rows :
+ cells = _item['cells']
+
+ for _ndxcol in range(0, len(cells)) :
+ if not cells[_ndxcol] :
+ continue
+ if 'value' in cells[_ndxcol] :
+ _value = cells[_ndxcol]['value']
+ if 'formattedText' in cells[_ndxcol] :
+ _value = cells[_ndxcol]['formattedText']
+
+ _worksheet.write(_irow, _icol + _ndxcol, _value)
+
+ if 'chart' in cells[_ndxcol] :
+ for _chart in cells[_ndxcol]['chart'] :
+ offset = 0 if 'value' not in cells[_ndxcol] else 1
+ _xchar = chr( _ndxcol + _icol + 65 + offset)+str(_irow+1)
+ _meta = {'cell':_xchar, 'sheet_name':_name,'chart':_chart}
+ self._charts.append(_meta)
+
+ _irow += 1 # incrementing rows
+ def _initCharts(self):
+ for _meta in self._charts :
+ _sheet = self._workbook.get_worksheet_by_name(_meta['sheet_name'])
+ _chart = self._workbook.add_chart({'type':_meta['chart']['type'].lower()})
+ if 'range' in _meta['chart'] :
+ #
+ # Analysis of the range expression in case we have more than one series
+ _range = _meta['chart']['range'].split('!')[-1]
+ # #
+ # #
+ _series = _range.split(':')
+ _lowerColIndex,_lowerRowIndex = self._getColRowIndex(_series[-1])
+ _upperColIndex,_upperRowIndex = self._getColRowIndex(_series[0])
+
+ if _upperColIndex == _lowerColIndex :
+ _xchar = chr(_upperColIndex + 65)
+ _chart.add_series({'values':_meta['chart']['range'],'name':f"{_meta['sheet_name']}!{_xchar}{_upperRowIndex+1}"})
+ else:
+ for _i in range(_upperColIndex,_lowerColIndex + 1) :
+ _xchar = chr( _i + 65)
+ _range = f"{_meta['sheet_name']}!{_xchar}{_upperRowIndex+1}:{_xchar}{_lowerRowIndex+1}"
+ _chart.add_series({'values':_range,'name':f'{_meta["sheet_name"]}!{_xchar}{_upperRowIndex+1}'})
+ _sheet.insert_chart(_meta['cell'],_chart)
+ def get(self):
+ self._initData ()
+ self._initCharts()
+ self._workbook.close()
+ self._stream.seek(0)
+ return self._stream
+
+
+#
+# here is the interface to the calling code (in case it can)
+def instance (_data) :
+ return (Exporter(_data)).get()
\ No newline at end of file