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