diff --git a/Doxyversion b/Doxyversion index a5c785da0..0ce9fcc52 100644 --- a/Doxyversion +++ b/Doxyversion @@ -1 +1 @@ -PROJECT_NAME=Minsky: 3.19.0-beta.1 +PROJECT_NAME=Minsky: 3.19.0-beta.2 diff --git a/Makefile b/Makefile index 446e619b5..3f075ba63 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ PREFIX=/usr/local # directory MODLINK=$(LIBMODS:%=$(ECOLAB_HOME)/lib/%) MODEL_OBJS=autoLayout.o cairoItems.o canvas.o CSVDialog.o dataOp.o equationDisplay.o godleyIcon.o godleyTable.o godleyTableWindow.o grid.o group.o item.o intOp.o lasso.o lock.o minsky.o operation.o operationRS.o operationRS1.o operationRS2.o phillipsDiagram.o plotWidget.o port.o pubTab.o ravelWrap.o renderNativeWindow.o selection.o sheet.o SVGItem.o switchIcon.o userFunction.o userFunction_units.o variableInstanceList.o variable.o variablePane.o windowInformation.o wire.o -ENGINE_OBJS=clipboard.o derivative.o equationDisplayRender.o equations.o evalGodley.o evalOp.o flowCoef.o \ +ENGINE_OBJS=clipboard.o databaseIngestor.o derivative.o equationDisplayRender.o equations.o evalGodley.o evalOp.o flowCoef.o \ godleyExport.o latexMarkup.o valueId.o variableValue.o node_latex.o node_matlab.o CSVParser.o \ minskyTensorOps.o mdlReader.o saver.o rungeKutta.o SCHEMA_OBJS=schema3.o schema2.o schema1.o schema0.o schemaHelper.o variableType.o \ @@ -305,7 +305,7 @@ endif LIBS+= -LRavelCAPI -lravelCAPI -LRavelCAPI/civita -lcivita \ -lboost_system$(BOOST_EXT) -lboost_regex$(BOOST_EXT) \ -lboost_date_time$(BOOST_EXT) -lboost_program_options$(BOOST_EXT) \ - -lboost_filesystem$(BOOST_EXT) -lboost_thread$(BOOST_EXT) -lgsl -lgslcblas -lssl -lcrypto + -lboost_filesystem$(BOOST_EXT) -lboost_thread$(BOOST_EXT) -lsoci_core -lgsl -lgslcblas -lssl -lcrypto ifdef MXE LIBS+=-lcrypt32 -lbcrypt -lshcore diff --git a/RESTService/addon.cc b/RESTService/addon.cc index 460a64490..c088b68b4 100644 --- a/RESTService/addon.cc +++ b/RESTService/addon.cc @@ -242,6 +242,7 @@ namespace minsky if (reset_flag()) requestReset(); civita::ITensor::cancel(false); + ravelCAPI::Ravel::cancel(false); // disable quoting wide characters in UTF-8 strings auto result=write(registry.process(command, arguments)->asBuffer(),json5_parser::raw_utf8); commandHook(command,arguments); @@ -594,6 +595,7 @@ struct MinskyAddon: public Addon Value cancelProgress(const Napi::CallbackInfo& info) { *addOnMinsky.progressState.cancel=true; civita::ITensor::cancel(true); + ravelCAPI::Ravel::cancel(true); return info.Env().Null(); } diff --git a/RESTService/pyminsky.cc b/RESTService/pyminsky.cc index 699b139ad..63a5e93d1 100644 --- a/RESTService/pyminsky.cc +++ b/RESTService/pyminsky.cc @@ -97,6 +97,8 @@ namespace pyminsky } CLASSDESC_ADD_FUNCTION(findObject); CLASSDESC_ADD_FUNCTION(findVariable); + using minsky::DataSpec; + CLASSDESC_DECLARE_TYPE(DataSpec); } CLASSDESC_PYTHON_MODULE(pyminsky); diff --git a/RESTService/typescriptAPI.cc b/RESTService/typescriptAPI.cc index e61565dd8..454ecc496 100644 --- a/RESTService/typescriptAPI.cc +++ b/RESTService/typescriptAPI.cc @@ -7,15 +7,21 @@ #include "bookmark.h" #include "bookmark.tcd" #include "cairoSurfaceImage.tcd" +#include "cairoRenderer.tcd" #include "callableFunction.tcd" #include "canvas.tcd" +#define CLASSDESC_typescriptAPI___CAPIRenderer +#include "capiRenderer.tcd" #include "CSVDialog.tcd" #include "CSVParser.tcd" +#include "CSVTools.tcd" #include "constMap.tcd" #include "dataSpecSchema.tcd" #include "dataOp.h" #include "dataOp.tcd" +#include "databaseIngestor.tcd" #include "dimension.tcd" +#include "dynamicRavelCAPI.tcd" #include "engNotation.tcd" #include "equationDisplay.tcd" #include "evalGodley.tcd" @@ -242,10 +248,12 @@ int main() api.addClass(); api.addClass(); api.addClass(); + api.addClass(); api.addClass(); api.addClass(); api.addClass(); api.addClass(); + api.addClass(); api.addClass(); api.addClass(); api.addClass(); @@ -254,8 +262,11 @@ int main() api.addClass(); api.addClass(); api.addClass(); + api.addClass(); api.addClass(); api.addClass(); + api.addClass(); + api.addClass(); api.addClass(); api.addClass(); api.addClass(); @@ -299,6 +310,7 @@ int main() cout << "class minsky__GodleyIcon__MoveCellArgs {}\n"; cout << "class minsky__RenderNativeWindow__RenderFrameArgs {}\n"; cout << "class minsky__VariableType__TypeT {}\n"; + cout << "class CAPIRenderer {}\n"; cout << "class civita__ITensor__Args {}\n"; cout << "class classdesc__json_pack_t {}\n"; cout << "class classdesc__pack_t {}\n"; diff --git a/RavelCAPI b/RavelCAPI index 2112a6fc2..755b1c5b6 160000 --- a/RavelCAPI +++ b/RavelCAPI @@ -1 +1 @@ -Subproject commit 2112a6fc2f4a337777d271916e79415f7df36353 +Subproject commit 755b1c5b68eb9798fe003c7ec3e261735e3e1448 diff --git a/doc/version.tex b/doc/version.tex index 68416fcaf..a6c80fa6c 100644 --- a/doc/version.tex +++ b/doc/version.tex @@ -1 +1 @@ -\author{Version 3.19.0-beta.1} +\author{Version 3.19.0-beta.2} diff --git a/engine/CSVParser.cc b/engine/CSVParser.cc index d2518f489..922f83fb1 100644 --- a/engine/CSVParser.cc +++ b/engine/CSVParser.cc @@ -20,6 +20,7 @@ #include "minsky.h" #include "CSVParser.h" +#include "CSVTools.rcd" #include "CSVParser.rcd" #include "dataSpecSchema.rcd" #include "dimension.rcd" @@ -36,156 +37,15 @@ using namespace minsky; using namespace std; +using ravel::Parser; +using ravel::SpaceSeparatorParser; +using ravel::getWholeLine; #include #include #include #include -namespace escapedListSeparator -{ - // pinched from boost::escape_list_separator, and modified to not throw - template ::traits_type > - class EscapedListSeparator { - - private: - typedef std::basic_string string_type; - struct char_eq { - Char e_; - char_eq(Char e):e_(e) { } - bool operator()(Char c) { - return Traits::eq(e_,c); - } - }; - string_type escape_; - string_type c_; - string_type quote_; - bool last_; - - bool is_escape(Char e) { - const char_eq f(e); - return std::find_if(escape_.begin(),escape_.end(),f)!=escape_.end(); - } - bool is_c(Char e) { - const char_eq f(e); - return std::find_if(c_.begin(),c_.end(),f)!=c_.end(); - } - bool is_quote(Char e) { - const char_eq f(e); - return std::find_if(quote_.begin(),quote_.end(),f)!=quote_.end(); - } - template - void do_escape(iterator& next,iterator end,Token& tok) { - if (++next >= end) - // don't throw, but pass on verbatim - tok+=escape_.front(); - if (Traits::eq(*next,'n')) { - tok+='\n'; - return; - } - if (is_quote(*next)) { - tok+=*next; - return; - } - if (is_c(*next)) { - tok+=*next; - return; - } - if (is_escape(*next)) { - tok+=*next; - return; - } - // don't throw, but pass on verbatim - tok+=escape_.front()+*next; - } - - public: - - explicit EscapedListSeparator(Char e = '\\', - Char c = ',',Char q = '\"') - : escape_(1,e), c_(1,c), quote_(1,q), last_(false) { } - - EscapedListSeparator(string_type e, string_type c, string_type q) - : escape_(e), c_(c), quote_(q), last_(false) { } - - void reset() {last_=false;} - - template - bool operator()(InputIterator& next,InputIterator end,Token& tok) { - bool bInQuote = false; - tok = Token(); - - if (next >= end) { - next=end; // reset next in case it has adavanced beyond - if (last_) { - last_ = false; - return true; - } - return false; - } - last_ = false; - while (next < end) { - if (is_escape(*next)) { - do_escape(next,end,tok); - } - else if (is_c(*next)) { - if (!bInQuote) { - // If we are not in quote, then we are done - ++next; - // The last character was a c, that means there is - // 1 more blank field - last_ = true; - return true; - } - tok+=*next; - } - else if (is_quote(*next)) { - bInQuote=!bInQuote; - } - else { - tok += *next; - } - ++next; - } - return true; - } - }; -} -using Parser=escapedListSeparator::EscapedListSeparator; - -typedef boost::tokenizer Tokenizer; - -struct SpaceSeparatorParser -{ - char escape, quote; - SpaceSeparatorParser(char escape='\\', char sep=' ', char quote='"'): - escape(escape), quote(quote) {} - template - bool operator()(I& next, I end, std::string& tok) - { - tok.clear(); - bool quoted=false; - while (next!=end) - { - if (*next==escape) - tok+=*(++next); - else if (*next==quote) - quoted=!quoted; - else if (!quoted && isspace(*next)) - { - while (isspace(*next)) ++next; - return true; - } - else - tok+=*next; - ++next; - } - return !tok.empty(); - } - void reset() {} -}; - namespace { /// An any with cached hash @@ -383,6 +243,7 @@ void DataSpec::setDataArea(size_t row, size_t col) dataCols.erase(i); for (unsigned i=m_nColAxes; i1 && - ((line[i-2]!=spec.quote && line[i-2]!=spec.escape && - (line[i-2]!=spec.separator || i==line.size()-1|| line[i+1]!=spec.quote)) // deal with ,'' - || // deal with "" middle or end - (line[i-2]==spec.quote && (i==2 || line[i-3]==spec.separator || line[i-3]==spec.escape)))))) // deal with leading """ - line[i-1]=spec.escape; - } - /// handle reporting errors in loadValueFromCSVFileT when loading files struct OnError { diff --git a/engine/CSVParser.h b/engine/CSVParser.h index 6e34a79dd..eeaf9da9c 100644 --- a/engine/CSVParser.h +++ b/engine/CSVParser.h @@ -83,6 +83,21 @@ namespace minsky guessFromStream(is, std::filesystem::file_size(fileName)); } + // converts this to the ravel data spec. TODO - can we eliminate, or at least eviscerate this type? + operator ravel::DataSpec() const { + ravel::DataSpec r; static_cast(r)=*this; + r.dataRowOffset=dataRowOffset; // TODO: non-normalisation between dataRowOffset and m_nRowAxes causes problems... + r.headerRow=headerRow; + r.mergeDelimiters=mergeDelimiters; + r.counter=counter; + r.dontFail=dontFail; + r.dimensionCols=dimensionCols; + r.dataCols=dataCols; + for (size_t i=0; i + +using namespace std; +namespace minsky +{ + namespace + { + ProgressUpdater* ingestorProgress; + + void progress(const char* filename, double fraction) + { + if (ingestorProgress) ingestorProgress->setProgress(fraction); + } + } + + void DatabaseIngestor::importFromCSV(const std::vector& filenames) + { + auto& m=minsky(); + ProgressUpdater pu(m.progressState,"Importing",filenames.size()); + // set the custom progress callback if global Minsky object is derived only + if (typeid(m)!=typeid(Minsky)) + db.loadDatabaseCallback(progress); + for (auto& f: filenames) + { + filesystem::path p(f); + ProgressUpdater pu(m.progressState,"Importing: "+p.filename().string(),100); + ingestorProgress=&pu; + db.loadDatabase({f}, spec); + ++m.progressState; + } + ingestorProgress=nullptr; + } +} diff --git a/engine/databaseIngestor.h b/engine/databaseIngestor.h new file mode 100644 index 000000000..d256acf85 --- /dev/null +++ b/engine/databaseIngestor.h @@ -0,0 +1,39 @@ +/* + @copyright Steve Keen 2025 + @author Russell Standish + This file is part of Minsky. + + Minsky is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Minsky is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Minsky. If not, see . +*/ + + +#ifndef DATABASEINGESTOR_H +#define DATABASEINGESTOR_H + +#include "CSVDialog.h" +#include "dynamicRavelCAPI.h" + +namespace minsky +{ + class DatabaseIngestor: public CSVDialog + { + public: + ravelCAPI::Database db; + void createTable(const std::string& filename) + {db.createTable(filename,spec);} + void importFromCSV(const std::vector&) override; + }; +} + +#endif diff --git a/engine/equations.cc b/engine/equations.cc index f7011a6df..5186e18e9 100644 --- a/engine/equations.cc +++ b/engine/equations.cc @@ -208,6 +208,7 @@ namespace MathDAG { if (!visited.insert(this).second) return false; // cycle detected, break + if (type()==OperationType::ravel) return true; switch (OperationType::classify(type())) { case reduction: case scan: case tensor: case statistics: diff --git a/engine/minskyTensorOps.cc b/engine/minskyTensorOps.cc index 014562e8a..ef0eff670 100644 --- a/engine/minskyTensorOps.cc +++ b/engine/minskyTensorOps.cc @@ -1507,13 +1507,14 @@ namespace minsky CLASSDESC_ACCESS(Ravel); public: - RavelTensor(const Ravel& ravel): ravel(ravel) {} + RavelTensor(const Ravel& ravel): ravel(ravel) { + if (ravel.db) chain=const_cast(ravel).createChain(nullptr); + } void setArgument(const TensorPtr& a,const Args&) override { - // not sure how to avoid this const cast here arg=a; - const_cast(ravel).populateHypercube(a->hypercube()); - chain=ravel::createRavelChain(ravel.getState(), a); + // not sure how to avoid this const cast here + chain=const_cast(ravel).createChain(a); } double operator[](size_t i) const override { diff --git a/engine/saver.cc b/engine/saver.cc index 7c926fa7c..407d1efc6 100644 --- a/engine/saver.cc +++ b/engine/saver.cc @@ -19,6 +19,8 @@ #include "saver.h" #include "schema3.h" +#include "CSVTools.xcd" +#include "dynamicRavelCAPI.xcd" #include "minsky_epilogue.h" namespace minsky diff --git a/engine/variableValue.cc b/engine/variableValue.cc index bd93c9aac..fe123990e 100644 --- a/engine/variableValue.cc +++ b/engine/variableValue.cc @@ -85,6 +85,16 @@ namespace minsky static VariableValuePtr s_one(make_shared("constant:one","1")); return s_one; } + + void VariableValue::importFromCSV(const std::vector& filenames) + { + if (!filenames.empty()) + url=filenames[0]; + loadValueFromCSVFile(*this, filenames, spec); + minsky().populateMissingDimensionsFromVariable(*this); + if (!hypercube().dimsAreDistinct()) + throw runtime_error("Axes of imported data should all have distinct names"); + } bool VariableValue::idxInRange() const {return m_type==undefined || idx()+size()<= diff --git a/engine/variableValue.h b/engine/variableValue.h index dca5b089e..7bd74a0e7 100644 --- a/engine/variableValue.h +++ b/engine/variableValue.h @@ -41,7 +41,7 @@ namespace minsky typedef std::shared_ptr GroupPtr; using namespace civita; - struct VariableValueData: public civita::ITensorVal, public Slider + struct VariableValueData: public civita::ITensorVal, public Slider, public CSVDialog { using ITensorVal::operator=; @@ -61,10 +61,6 @@ namespace minsky bool godleyOverridden=false; std::string name; // name of this variable classdesc::Exclude> m_scope; - - /// for importing CSV files - CSVDialog csvDialog; - }; class VariableValue: public VariableType, public VariableValueData @@ -177,6 +173,8 @@ namespace minsky std::string valueId() const {return valueIdFromScope(m_scope.lock(),canonicalName(name));} + void importFromCSV(const std::vector&) override; + /// export this to a CSV file. If \a comment is non-empty, it is written as the first line of the file. If tabular is false, there is one data point per line, if true, the the longest dimension is written as a series on the line. void exportAsCSV(const std::string& filename, const std::string& comment="", bool tabular=false) const; diff --git a/gui-js/apps/minsky-electron/src/app/backend-init.ts b/gui-js/apps/minsky-electron/src/app/backend-init.ts index 02c577ba6..b2138d2f5 100644 --- a/gui-js/apps/minsky-electron/src/app/backend-init.ts +++ b/gui-js/apps/minsky-electron/src/app/backend-init.ts @@ -61,12 +61,12 @@ export async function backend(command: string, ...args: any[]): Promise { if (typeof(error)!=="string") error=error?.message; log.error('Async Rest API: ',command,arg,'=>Exception caught: ' + error); if (!dialog) throw error; // rethrow to force error in jest environment - if (error && command !== 'minsky.canvas.item.importFromCSV') - dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ - message: error, - type: 'error', - }); - return error; + if (error && !command.endsWith('importFromCSV')) + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: error, + type: 'error', + }); + return error; } } @@ -147,6 +147,10 @@ restService.setBusyCursorCallback(function (busy: boolean) { if (!initProgressBar && busy) initProgressBar=setTimeout(()=>{ progress.browserWindow={parent: WindowManager.getMainWindow(), height: 200}; + if (progressBar) { + progressBar.setCompleted(); + progressBar.close(); + } progressBar=new ProgressBar(progress); progressBar.on('ready',()=>{progressBar._window?.webContents.executeJavaScript(injectCancelButton);}); progressBar.value=progress.value; diff --git a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts index b1a93a3bc..e5f398992 100644 --- a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts +++ b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts @@ -17,6 +17,7 @@ import { ImportStockPayload, GodleyIcon, DownloadCSVPayload, + VariableBase, } from '@minsky/shared'; import { BrowserWindow, dialog, ipcMain } from 'electron'; import { BookmarkManager } from '../managers/BookmarkManager'; @@ -68,7 +69,7 @@ ipcMain.handle(events.CLOSE_WINDOW, (event) => { }); ipcMain.handle(events.OPEN_FILE_DIALOG, async (event, options) => { - const fileDialog = await dialog.showOpenDialog(options); + const fileDialog = await WindowManager.showOpenDialog(options); if (fileDialog.canceled || !fileDialog.filePaths) return ""; if (options?.properties?.includes('multiSelections')) @@ -77,7 +78,7 @@ ipcMain.handle(events.OPEN_FILE_DIALOG, async (event, options) => { }); ipcMain.handle(events.SAVE_FILE_DIALOG, async (event, options) => { - const fileDialog = await dialog.showSaveDialog(options); + const fileDialog = await WindowManager.showSaveDialog(options); if (fileDialog.canceled) return ""; return fileDialog.filePath; }); @@ -215,8 +216,16 @@ ipcMain.handle(events.NEW_SYSTEM, async () => { ipcMain.handle( events.IMPORT_CSV, async (event) => { - const itemInfo = await CommandsManager.getFocusItemInfo(); - CommandsManager.importCSV(itemInfo, true); + let v=new VariableBase(minsky.canvas.itemFocus); + CommandsManager.importCSV(minsky.variableValues.elem(await v.valueId()), true); + return; + } +); + +ipcMain.handle( + events.IMPORT_CSV_TO_DB, + async (event, {dropTable}) => { + CommandsManager.importCSV(minsky.databaseIngestor, false, dropTable); return; } ); diff --git a/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts index 8d139cc4c..81d83e6af 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts @@ -3,6 +3,7 @@ import { importCSVvariableName, InstallCase, minsky, + VariableBase, } from '@minsky/shared'; import { dialog, @@ -142,8 +143,9 @@ export class ApplicationMenuManager { enabled: true, async click() { try { - const _dialog = await dialog.showOpenDialog({ + const _dialog = await WindowManager.showOpenDialog({ properties: ['openFile'], + defaultPath: ':models', filters: [ { name: 'Minsky/Ravel', extensions: ['rvl','mky'] }, { name: '*.xml', extensions: ['xml'] }, @@ -204,16 +206,36 @@ export class ApplicationMenuManager { }, { label: 'Import Data', - async click() { - minsky.canvas.addVariable(importCSVvariableName, 'parameter'); - CommandsManager.importCSV(await CommandsManager.getFocusItemInfo(), true); - } + submenu: [ + { + label: 'to parameter', + async click() { + minsky.canvas.addVariable(importCSVvariableName, 'parameter'); + let v=new VariableBase(minsky.canvas.itemFocus); + CommandsManager.importCSV(minsky.variableValues.elem(await v.valueId()), true); + } + }, + { + label: 'to database', + enabled: await minsky.databaseIngestor.db.ravelPro(), + async click() { + WindowManager.createPopupWindowWithRouting({ + width: 250, + height: 150, + title: '', + url: `#/headless/new-database`, + modal: true, + }); + } + }, + ] }, { label: 'Insert File as Group', async click() { try { - const insertGroupDialog = await dialog.showOpenDialog({ + const insertGroupDialog = await WindowManager.showOpenDialog({ + defaultPaths: ':models', properties: ['openFile'], }); @@ -552,9 +574,9 @@ export class ApplicationMenuManager { } private static async exportPlot(extension: string, command: (file:string)=>void) { - const exportPlotDialog = await dialog.showSaveDialog({ + const exportPlotDialog = await WindowManager.showSaveDialog({ title: `Export plot as ${extension}`, - defaultPath: 'plot', + defaultPath: ':models/plot', properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [{ extensions: [extension], name: extension.toUpperCase() }], }); diff --git a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts index acca5a08e..622de2e1e 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts @@ -1,6 +1,7 @@ import { CanvasItem, ClassType, + CSVDialog, events, Functions, HandleDimensionPayload, @@ -117,9 +118,9 @@ export class CommandsManager { extension: string, name: string ): Promise { - const exportImage = await dialog.showSaveDialog({ + const exportImage = await WindowManager.showSaveDialog({ title: 'Export item as...', - defaultPath: `export.${extension}`, + defaultPath: `:models/export.${extension}`, properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [ {name, extensions: [extension]}, @@ -170,9 +171,9 @@ export class CommandsManager { } static async exportItemAsCSV(item: any, tabular=false): Promise { - const exportItemDialog = await dialog.showSaveDialog({ + const exportItemDialog = await WindowManager.showSaveDialog({ title: 'Export item as csv', - defaultPath: 'item.csv', + defaultPath: ':models/item.csv', properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [ { extensions: ['csv'], name: 'CSV' }, @@ -324,8 +325,8 @@ export class CommandsManager { } static async saveSelectionAsFile(): Promise { - const saveDialog = await dialog.showSaveDialog({ - defaultPath: 'selection.mky', + const saveDialog = await WindowManager.showSaveDialog({ + defaultPath: ':models/selection.mky', }); const { canceled, filePath } = saveDialog; @@ -367,7 +368,7 @@ export class CommandsManager { } static async getFilePathUsingSaveDialog(): Promise { - const saveDialog = await dialog.showSaveDialog({}); + const saveDialog = await WindowManager.showSaveDialog({defaultPath:':models'}); const { canceled, filePath} = saveDialog; @@ -381,9 +382,9 @@ export class CommandsManager { static async getFilePathFromExportCanvasDialog( type: string, label: string ): Promise { - const exportCanvasDialog = await dialog.showSaveDialog({ + const exportCanvasDialog = await WindowManager.showSaveDialog({ title: 'Export canvas', - defaultPath: `canvas.${type}`, + defaultPath: `:models/canvas.${type}`, properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [ {name: label, extensions: [type]}, @@ -487,7 +488,7 @@ export class CommandsManager { static async saveGroupAsFile(): Promise { const defaultExtension = await minsky.model.defaultExtension(); - const saveDialog = await dialog.showSaveDialog({ + const saveDialog = await WindowManager.showSaveDialog({ filters: [ { name: defaultExtension, @@ -495,7 +496,7 @@ export class CommandsManager { }, { name: 'All', extensions: ['*'] }, ], - defaultPath: `group${defaultExtension}`, + defaultPath: `:models/group${defaultExtension}`, properties: ['showOverwriteConfirmation'], }); @@ -512,7 +513,7 @@ export class CommandsManager { ext: string, command: (x: string)=>void = null ): Promise { - const saveDialog = await dialog.showSaveDialog({ + const saveDialog = await WindowManager.showSaveDialog({ filters: [ { name: '.' + ext, @@ -520,7 +521,7 @@ export class CommandsManager { }, { name: 'All', extensions: ['*'] }, ], - defaultPath: `godley.${ext}`, + defaultPath: `:models/godley.${ext}`, properties: ['showOverwriteConfirmation'], }); @@ -893,9 +894,9 @@ export class CommandsManager { return; } - const logSimulation = await dialog.showSaveDialog({ + const logSimulation = await WindowManager.showSaveDialog({ title: 'Save As', - defaultPath: 'log_simulation.csv', + defaultPath: ':models/log_simulation.csv', properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [{ extensions: ['csv'], name: 'CSV' }], }); @@ -912,11 +913,19 @@ export class CommandsManager { this.setLogSimulationCheckmark(true); } - static async importCSV(itemInfo: CanvasItem, isInvokedUsingToolbar = false) { + /** + * Opens CSV import dialog + * @param csvDialog - CSV dialog configuration object + * @param isInvokedUsingToolbar - Whether invoked from toolbar (affects cleanup) + * @param dropTable - Whether to drop existing table (for database imports) + */ + static async importCSV(csvDialog: CSVDialog, isInvokedUsingToolbar = false, dropTable=false) { + const itemInfo: CanvasItem={classType: ClassType.Variable, id: csvDialog.$prefix(), displayContents: false}; if (!WindowManager.focusIfWindowIsPresent(itemInfo.id)) { + const window = await this.initializePopupWindow({ itemInfo, - url: `#/headless/import-csv?systemWindowId=0&itemId=${itemInfo.id}&isInvokedUsingToolbar=${isInvokedUsingToolbar}`, + url: `#/headless/import-csv?systemWindowId=0&csvDialog=${csvDialog.$prefix()}&isInvokedUsingToolbar=${isInvokedUsingToolbar}&dropTable=${dropTable}`, height: 600, width: 1300, minWidth: 650, @@ -942,7 +951,7 @@ export class CommandsManager { window.loadURL( WindowManager.getWindowUrl( - `#/headless/import-csv?systemWindowId=${systemWindowId}&itemId=${itemInfo.id}&isInvokedUsingToolbar=${isInvokedUsingToolbar}&examplesPath=${join(dirname(app.getAppPath()),'examples','data')}` + `#/headless/import-csv?systemWindowId=${systemWindowId}&csvDialog=${csvDialog.$prefix()}&isInvokedUsingToolbar=${isInvokedUsingToolbar}&dropTable=${dropTable}&examplesPath=${join(dirname(app.getAppPath()),'examples','data')}` ) ); } @@ -985,7 +994,7 @@ export class CommandsManager { ], defaultPath: this.currentMinskyModelFilePath || - `model${defaultExtension}`, + `:models/model${defaultExtension}`, properties: ['showOverwriteConfirmation'], } } @@ -999,7 +1008,7 @@ export class CommandsManager { } static async saveAs() { - const saveDialog = await dialog.showSaveDialog(await CommandsManager.defaultSaveOptions()); + const saveDialog = await WindowManager.showSaveDialog(await CommandsManager.defaultSaveOptions()); const { canceled, filePath: filePath } = saveDialog; diff --git a/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts index ba7db78d8..7a25fb957 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts @@ -900,6 +900,30 @@ export class ContextMenuManager { checked: editorMode, click: () => {ravel.toggleEditorMode();} }), + new MenuItem({ + label: 'Connect to database', + enabled: await ravel.db.ravelPro(), + click: () => { + WindowManager.createPopupWindowWithRouting({ + title: 'Connect to database', + url: '#/headless/connect-database', + height: 120, + width: 250, + }) + }, + }), + new MenuItem({ + label: 'Set numerical axes', + enabled: await ravel.db.ravelPro(), + click: () => { + WindowManager.createPopupWindowWithRouting({ + title: 'Set numerical axes', + url: '#/headless/ravel-select-horizontal-dim', + height: 400, + width: 400, + }) + }, + }), new MenuItem({ label: 'Export as CSV', submenu: this.exportAsCSVSubmenu(ravel), @@ -1164,8 +1188,8 @@ export class ContextMenuManager { menuItems.push( new MenuItem({ label: 'Import CSV', - click: () => { - CommandsManager.importCSV(itemInfo); + click: async () => { + CommandsManager.importCSV(minsky.variableValues.elem(await v.valueId())); }, }) ); @@ -1364,57 +1388,57 @@ export class ContextMenuManager { private static async initContextMenuForCSVImport(event: IpcMainEvent, variableValue: string, row: number, col: number) { - const refresh=()=>event.sender.send(events.CSV_IMPORT_REFRESH); + const refresh=()=>event.sender.send(events.REFRESH_CSV_IMPORT); const value=new VariableValue(variableValue); var menu=Menu.buildFromTemplate([ new MenuItem({ label: 'Set as header row', click: ()=>{ - value.csvDialog.spec.headerRow(row); + value.spec.headerRow(row); refresh(); }, }), new MenuItem({ label: 'Auto-classify columns as axis/data', click: async ()=>{ - value.csvDialog.classifyColumns(); + value.classifyColumns(); refresh(); }, }), new MenuItem({ label: 'Populate column labels', click: async ()=>{ - value.csvDialog.populateHeaders(); + value.populateHeaders(); refresh(); }, }), new MenuItem({ label: 'Populate current column label', click: ()=>{ - value.csvDialog.populateHeader(col); + value.populateHeader(col); refresh(); }, }), new MenuItem({ label: 'Set start of data row, and column', click: ()=>{ - value.csvDialog.spec.setDataArea(row,col); + value.spec.setDataArea(row,col); refresh(); }, }), new MenuItem({ label: 'Set start of data row', click: async ()=>{ - let c=await value.csvDialog.spec.nColAxes(); - value.csvDialog.spec.setDataArea(row,c); + let c=await value.spec.nColAxes(); + value.spec.setDataArea(row,c); refresh(); }, }), new MenuItem({ label: 'Set start of data column', click: async ()=>{ - let r=await value.csvDialog.spec.nRowAxes(); - value.csvDialog.spec.setDataArea(r,col); + let r=await value.spec.nRowAxes(); + value.spec.setDataArea(r,col); refresh(); }, }), diff --git a/gui-js/apps/minsky-electron/src/app/managers/RecordingManager.ts b/gui-js/apps/minsky-electron/src/app/managers/RecordingManager.ts index 9caeb1b77..327560eff 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/RecordingManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/RecordingManager.ts @@ -23,7 +23,8 @@ export class RecordingManager { static async handleRecordingReplay() { this.stopRecording(); - const replayRecordingDialog = await dialog.showOpenDialog({ + const replayRecordingDialog = await WindowManager.showOpenDialog({ + defaultPath: ':models', filters: [ { extensions: ['json'], name: 'JSON' }, { extensions: ['*'], name: 'All Files' }, @@ -47,7 +48,7 @@ export class RecordingManager { const index = dialog.showMessageBoxSync(options); if (options.buttons[index] === positiveResponseText) { - const saveDialog = await dialog.showSaveDialog({}); + const saveDialog = await WindowManager.showSaveDialog({defaultPath: ':models'}); const { canceled, filePath } = saveDialog; @@ -128,13 +129,13 @@ export class RecordingManager { return; } - const saveRecordingDialog = await dialog.showSaveDialog({ + const saveRecordingDialog = await WindowManager.showSaveDialog({ properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [ { extensions: ['json'], name: 'JSON' }, { extensions: ['*'], name: 'All Files' }, ], - defaultPath: 'recording.json', + defaultPath: ':models/recording.json', }); const { canceled, filePath } = saveRecordingDialog; diff --git a/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts b/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts index aa24844df..2447ab5f8 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts @@ -1,5 +1,6 @@ import { defaultBackgroundColor } from '@minsky/shared'; import Store from 'electron-store'; +import {homedir} from 'node:os'; interface MinskyPreferences { godleyTableShowValues: boolean; @@ -15,6 +16,8 @@ interface MinskyStore { recentFiles: Array; backgroundColor: string; preferences: MinskyPreferences; + defaultModelDirectory: string; + defaultDataDirectory: string; ravelPlugin: string; // used for post installation installation of Ravel } @@ -24,6 +27,8 @@ class StoreManager { defaults: { recentFiles: [], backgroundColor: defaultBackgroundColor, + defaultModelDirectory: homedir(), + defaultDataDirectory: homedir(), preferences: { godleyTableShowValues: false, godleyTableOutputStyle: 'sign', diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index 6af5b0bb5..771ceddd5 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -11,10 +11,11 @@ import { Utility, } from '@minsky/shared'; import { StoreManager } from './StoreManager'; -import { BrowserWindow, dialog, Menu, screen } from 'electron'; +import { BrowserWindow, dialog, Menu, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, + SaveDialogReturnValue, screen } from 'electron'; import log from 'electron-log'; import os from 'os'; -import { join } from 'path'; +import { join, dirname } from 'path'; import { format } from 'url'; //const logWindows = debug('minsky:electron_windows'); @@ -131,6 +132,68 @@ export class WindowManager { return initialURL; } + /// If options contains defaultPath that has starts with ':model/' + /// or ':data/', then the last directory visited with that type is + /// substituted. + /// returns the directory key for the StoreManager. + static processDefaultDirectory(options: OpenDialogOptions|SaveDialogOptions) { + let splitDefaultPath=/([^\/]*)\/?(.*)/.exec(options.defaultPath); + let defaultType=splitDefaultPath[1]; + let defaultDirectoryKey=""; + + switch (defaultType) { + case ':models': + defaultDirectoryKey='defaultModelDirectory'; + break; + case ':data': + defaultDirectoryKey='defaultDataDirectory'; + break; + } + + if (defaultDirectoryKey) { + let defaultDirectory=StoreManager.store.get(defaultDirectoryKey) as string; + if (defaultDirectory) + options['defaultPath']=defaultDirectory+'/'+splitDefaultPath[2]; + } + return defaultDirectoryKey; + } + + /// wrappers around the standard electron dialogs that saves the directory opened as a defaultPath + /// if options.defaultPath is set to either models or data. + static async showOpenDialog(...args: any[]) + { + let options=args[args.length-1] as OpenDialogOptions; + let defaultDirectoryKey=this.processDefaultDirectory(options); + + let res: Electron.OpenDialogReturnValue; + if (args.length>1) + res=await dialog.showOpenDialog(args[0],options); + else + res=await dialog.showOpenDialog(options); + if (!res.canceled && defaultDirectoryKey) { + StoreManager.store.set(defaultDirectoryKey,dirname(res.filePaths[0])); + } + return res; + } + + /// wrappers around the standard electron dialogs that saves the directory opened as a defaultPath + /// if options.defaultPath is set to either models or data. + static async showSaveDialog(...args: any[]) + { + let options=args[args.length-1] as SaveDialogOptions; + let defaultDirectoryKey=this.processDefaultDirectory(options); + + let res: Electron.SaveDialogReturnValue; + if (args.length>1) + res=await dialog.showSaveDialog(args[0], options); + else + res=await dialog.showSaveDialog(options); + if (!res.canceled && defaultDirectoryKey) { + StoreManager.store.set(defaultDirectoryKey,dirname(res.filePath)); + } + return res; + } + /// if window already exists attached to \a url, then raise it /// @return window if it exists, null otherwise static raiseWindow(url: string): BrowserWindow { diff --git a/gui-js/apps/minsky-web/src/app/app-routing.module.ts b/gui-js/apps/minsky-web/src/app/app-routing.module.ts index b50d3cffc..9262601e4 100644 --- a/gui-js/apps/minsky-web/src/app/app-routing.module.ts +++ b/gui-js/apps/minsky-web/src/app/app-routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { + ConnectDatabaseComponent, CliInputComponent, EditDescriptionComponent, EditGodleyCurrencyComponent, @@ -13,11 +14,13 @@ import { FindAllInstancesComponent, GodleyWidgetViewComponent, ImportCsvComponent, + NewDatabaseComponent, NewPubTabComponent, PageNotFoundComponent, SummaryComponent, PlotWidgetOptionsComponent, PlotWidgetViewComponent, + RavelSelectHorizontalDimComponent, RavelViewComponent, RenameAllInstancesComponent, VariablePaneComponent, @@ -51,88 +54,100 @@ const routes: Routes = [ loadChildren: () => import('@minsky/menu').then((m) => m.MenuModule), }, { - path: 'headless/rename-all-instances', - component: RenameAllInstancesComponent, + path: 'headless/connect-database', + component: ConnectDatabaseComponent, }, { - path: 'headless/edit-operation', - component: EditOperationComponent, + path: 'headless/edit-description', + component: EditDescriptionComponent, }, { - path: 'headless/edit-userfunction', - component: EditUserFunctionComponent, + path: 'headless/edit-godley-currency', + component: EditGodleyCurrencyComponent, }, { - path: 'headless/edit-intop', - component: EditIntegralComponent, + path: 'headless/edit-godley-title', + component: EditGodleyTitleComponent, }, { path: 'headless/edit-group', component: EditGroupComponent, }, { - path: 'headless/edit-godley-title', - component: EditGodleyTitleComponent, + path: 'headless/edit-handle-description', + component: EditHandleDescriptionComponent, }, { - path: 'headless/edit-godley-currency', - component: EditGodleyCurrencyComponent, + path: 'headless/edit-handle-dimension', + component: EditHandleDimensionComponent, }, { - path: 'headless/edit-description', - component: EditDescriptionComponent, + path: 'headless/edit-intop', + component: EditIntegralComponent, }, { - path: 'headless/new-pub-tab', - component: NewPubTabComponent, + path: 'headless/edit-operation', + component: EditOperationComponent, }, { - path: 'headless/edit-handle-description', - component: EditHandleDescriptionComponent, + path: 'headless/edit-userfunction', + component: EditUserFunctionComponent, }, { - path: 'headless/edit-handle-dimension', - component: EditHandleDimensionComponent, + path: 'headless/find-all-instances', + component: FindAllInstancesComponent, }, { - path: 'headless/pick-slices', - component: PickSlicesComponent, + path: 'headless/godley-widget-view', + component: GodleyWidgetViewComponent, + }, + { + path: 'headless/import-csv', + component: ImportCsvComponent, }, { path: 'headless/lock-handles', component: LockHandlesComponent, }, { - path: 'headless/find-all-instances', - component: FindAllInstancesComponent, + path: 'headless/new-database', + component: NewDatabaseComponent, }, { - path: 'headless/variable-pane', - component: VariablePaneComponent, + path: 'headless/new-pub-tab', + component: NewPubTabComponent, }, { - path: 'headless/plot-widget-view', - component: PlotWidgetViewComponent, + path: 'headless/pick-slices', + component: PickSlicesComponent, }, { - path: 'headless/godley-widget-view', - component: GodleyWidgetViewComponent, + path: 'headless/plot-widget-view', + component: PlotWidgetViewComponent, }, { path: 'headless/plot-widget-options', component: PlotWidgetOptionsComponent, }, + { + path: 'headless/ravel-select-horizontal-dim', + component: RavelSelectHorizontalDimComponent, + }, { path: 'headless/ravel-widget-view', component: RavelViewComponent, }, + { + path: 'headless/rename-all-instances', + component: RenameAllInstancesComponent, + }, { path: 'headless/terminal', component: CliInputComponent, }, { - path: 'headless/import-csv', - component: ImportCsvComponent, + path: 'headless/variable-pane', + component: VariablePaneComponent, }, { path: '**', diff --git a/gui-js/libs/core/src/lib/services/communication/communication.service.ts b/gui-js/libs/core/src/lib/services/communication/communication.service.ts index c43a64c55..bdd72a551 100644 --- a/gui-js/libs/core/src/lib/services/communication/communication.service.ts +++ b/gui-js/libs/core/src/lib/services/communication/communication.service.ts @@ -428,12 +428,7 @@ export class CommunicationService { async importData() { this.electronService.minsky.canvas.addVariable(importCSVvariableName, 'parameter'); - - const payload: MinskyProcessPayload = { - mouseX: await this.electronService.minsky.canvas.itemFocus.x(), - mouseY: await this.electronService.minsky.canvas.itemFocus.y(), - }; - this.electronService.invoke(events.IMPORT_CSV, payload); + this.electronService.invoke(events.IMPORT_CSV); } resetScrollTimeout = () => { diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index e996d88de..90cdb22d9 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -10,6 +10,7 @@ class minsky__EventInterface__KeyPressArgs {} class minsky__GodleyIcon__MoveCellArgs {} class minsky__RenderNativeWindow__RenderFrameArgs {} class minsky__VariableType__TypeT {} +class CAPIRenderer {} class civita__ITensor__Args {} class classdesc__json_pack_t {} class classdesc__pack_t {} @@ -92,7 +93,7 @@ export class Item extends CppClass { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async onResizeHandle(a1: number,a2: number): Promise {return this.$callMethod('onResizeHandle',a1,a2);} async onResizeHandles(...args: boolean[]): Promise {return this.$callMethod('onResizeHandles',...args);} async portX(a1: number): Promise {return this.$callMethod('portX',a1);} @@ -265,7 +266,7 @@ export class VariableBase extends Item { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async onResizeHandle(a1: number,a2: number): Promise {return this.$callMethod('onResizeHandle',a1,a2);} async onResizeHandles(...args: boolean[]): Promise {return this.$callMethod('onResizeHandles',...args);} async portX(a1: number): Promise {return this.$callMethod('portX',a1);} @@ -344,61 +345,23 @@ export class BoundingBox extends CppClass { } export class CSVDialog extends CppClass { - backgroundColour: ecolab__cairo__Colour; - item: Item; spec: DataSpec; - wire: Wire; constructor(prefix: string){ super(prefix); - this.backgroundColour=new ecolab__cairo__Colour(this.$prefix()+'.backgroundColour'); - this.item=new Item(this.$prefix()+'.item'); this.spec=new DataSpec(this.$prefix()+'.spec'); - this.wire=new Wire(this.$prefix()+'.wire'); } async classifyColumns(): Promise {return this.$callMethod('classifyColumns');} - async colWidth(...args: number[]): Promise {return this.$callMethod('colWidth',...args);} - async columnOver(a1: number): Promise {return this.$callMethod('columnOver',a1);} - async controlMouseDown(a1: number,a2: number): Promise {return this.$callMethod('controlMouseDown',a1,a2);} async correctedUniqueValues(): Promise {return this.$callMethod('correctedUniqueValues');} - async destroyFrame(): Promise {return this.$callMethod('destroyFrame');} - async draw(): Promise {return this.$callMethod('draw');} - async flashNameRow(...args: boolean[]): Promise {return this.$callMethod('flashNameRow',...args);} - async frameArgs(): Promise {return this.$callMethod('frameArgs');} - async getItemAt(a1: number,a2: number): Promise {return this.$callMethod('getItemAt',a1,a2);} - async getWireAt(a1: number,a2: number): Promise {return this.$callMethod('getWireAt',a1,a2);} async guessSpecAndLoadFile(): Promise {return this.$callMethod('guessSpecAndLoadFile');} - async hasScrollBars(): Promise {return this.$callMethod('hasScrollBars');} - async init(): Promise {return this.$callMethod('init');} - async keyPress(a1: minsky__EventInterface__KeyPressArgs): Promise {return this.$callMethod('keyPress',a1);} + async importFromCSV(a1: string[]): Promise {return this.$callMethod('importFromCSV',a1);} async loadFile(): Promise {return this.$callMethod('loadFile');} async loadFileFromName(a1: string): Promise {return this.$callMethod('loadFileFromName',a1);} - async mouseDown(a1: number,a2: number): Promise {return this.$callMethod('mouseDown',a1,a2);} - async mouseMove(a1: number,a2: number): Promise {return this.$callMethod('mouseMove',a1,a2);} - async mouseUp(a1: number,a2: number): Promise {return this.$callMethod('mouseUp',a1,a2);} - async moveTo(a1: number,a2: number): Promise {return this.$callMethod('moveTo',a1,a2);} async numInitialLines(...args: number[]): Promise {return this.$callMethod('numInitialLines',...args);} async parseLines(a1: number): Promise {return this.$callMethod('parseLines',a1);} async populateHeader(a1: number): Promise {return this.$callMethod('populateHeader',a1);} async populateHeaders(): Promise {return this.$callMethod('populateHeaders');} - async position(): Promise {return this.$callMethod('position');} - async registerImage(): Promise {return this.$callMethod('registerImage');} - async renderFrame(a1: minsky__RenderNativeWindow__RenderFrameArgs): Promise {return this.$callMethod('renderFrame',a1);} - async renderToEMF(a1: string): Promise {return this.$callMethod('renderToEMF',a1);} - async renderToPDF(a1: string): Promise {return this.$callMethod('renderToPDF',a1);} - async renderToPNG(a1: string): Promise {return this.$callMethod('renderToPNG',a1);} - async renderToPS(a1: string): Promise {return this.$callMethod('renderToPS',a1);} - async renderToSVG(a1: string): Promise {return this.$callMethod('renderToSVG',a1);} - async reportDrawTime(a1: number): Promise {return this.$callMethod('reportDrawTime',a1);} async reportFromFile(a1: string,a2: string): Promise {return this.$callMethod('reportFromFile',a1,a2);} - async requestRedraw(): Promise {return this.$callMethod('requestRedraw');} - async resolutionScaleFactor(...args: any[]): Promise {return this.$callMethod('resolutionScaleFactor',...args);} - async rowOver(a1: number): Promise {return this.$callMethod('rowOver',a1);} - async scaleFactor(): Promise {return this.$callMethod('scaleFactor');} - async tableWidth(): Promise {return this.$callMethod('tableWidth');} async url(...args: string[]): Promise {return this.$callMethod('url',...args);} - async xoffs(...args: number[]): Promise {return this.$callMethod('xoffs',...args);} - async zoom(a1: number,a2: number,a3: number): Promise {return this.$callMethod('zoom',a1,a2,a3);} - async zoomFactor(): Promise {return this.$callMethod('zoomFactor');} } export class Canvas extends RenderNativeWindow { @@ -601,6 +564,29 @@ export class DataSpecSchema extends CppClass { async separator(...args: number[]): Promise {return this.$callMethod('separator',...args);} } +export class DatabaseIngestor extends CppClass { + db: ravelCAPI__Database; + spec: DataSpec; + constructor(prefix: string){ + super(prefix); + this.db=new ravelCAPI__Database(this.$prefix()+'.db'); + this.spec=new DataSpec(this.$prefix()+'.spec'); + } + async classifyColumns(): Promise {return this.$callMethod('classifyColumns');} + async correctedUniqueValues(): Promise {return this.$callMethod('correctedUniqueValues');} + async createTable(a1: string): Promise {return this.$callMethod('createTable',a1);} + async guessSpecAndLoadFile(): Promise {return this.$callMethod('guessSpecAndLoadFile');} + async importFromCSV(a1: string[]): Promise {return this.$callMethod('importFromCSV',a1);} + async loadFile(): Promise {return this.$callMethod('loadFile');} + async loadFileFromName(a1: string): Promise {return this.$callMethod('loadFileFromName',a1);} + async numInitialLines(...args: number[]): Promise {return this.$callMethod('numInitialLines',...args);} + async parseLines(a1: number): Promise {return this.$callMethod('parseLines',a1);} + async populateHeader(a1: number): Promise {return this.$callMethod('populateHeader',a1);} + async populateHeaders(): Promise {return this.$callMethod('populateHeaders');} + async reportFromFile(a1: string,a2: string): Promise {return this.$callMethod('reportFromFile',a1,a2);} + async url(...args: string[]): Promise {return this.$callMethod('url',...args);} +} + export class EngNotation extends CppClass { constructor(prefix: string){ super(prefix); @@ -740,7 +726,7 @@ export class GodleyIcon extends Item { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async removeControlledItems(a1: GroupItems): Promise {return this.$callMethod('removeControlledItems',a1);} async resize(a1: LassoBox): Promise {return this.$callMethod('resize',a1);} async rowSum(a1: number): Promise {return this.$callMethod('rowSum',a1);} @@ -1108,7 +1094,7 @@ export class Group extends Item { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async onResizeHandle(a1: number,a2: number): Promise {return this.$callMethod('onResizeHandle',a1,a2);} async onResizeHandles(...args: boolean[]): Promise {return this.$callMethod('onResizeHandles',...args);} async portX(a1: number): Promise {return this.$callMethod('portX',a1);} @@ -1266,6 +1252,7 @@ export class Lock extends Item { export class Minsky extends CppClass { canvas: Canvas; conversions: civita__Conversions; + databaseIngestor: DatabaseIngestor; dimensions: Map; equationDisplay: EquationDisplay; evalGodley: EvalGodley; @@ -1287,6 +1274,7 @@ export class Minsky extends CppClass { super(prefix); this.canvas=new Canvas(this.$prefix()+'.canvas'); this.conversions=new civita__Conversions(this.$prefix()+'.conversions'); + this.databaseIngestor=new DatabaseIngestor(this.$prefix()+'.databaseIngestor'); this.dimensions=new Map(this.$prefix()+'.dimensions',civita__Dimension); this.equationDisplay=new EquationDisplay(this.$prefix()+'.equationDisplay'); this.evalGodley=new EvalGodley(this.$prefix()+'.evalGodley'); @@ -1757,6 +1745,7 @@ export class PubTab extends RenderNativeWindow { export class Ravel extends Item { axisDimensions: Map; + db: ravelCAPI__Database; lockGroup: RavelLockGroup; popup: RavelPopup; svgRenderer: SVGRenderer; @@ -1766,6 +1755,7 @@ export class Ravel extends Item { else super(prefix.$prefix()) this.axisDimensions=new Map(this.$prefix()+'.axisDimensions',civita__Dimension); + this.db=new ravelCAPI__Database(this.$prefix()+'.db'); this.lockGroup=new RavelLockGroup(this.$prefix()+'.lockGroup'); this.popup=new RavelPopup(this.$prefix()+'.popup'); this.svgRenderer=new SVGRenderer(this.$prefix()+'.svgRenderer'); @@ -1776,6 +1766,7 @@ export class Ravel extends Item { async applyState(a1: ravel__RavelState): Promise {return this.$callMethod('applyState',a1);} async broadcastStateToLockGroup(): Promise {return this.$callMethod('broadcastStateToLockGroup');} async collapseAllHandles(a1: boolean): Promise {return this.$callMethod('collapseAllHandles',a1);} + async createChain(a1: civita__ITensor): Promise {return this.$callMethod('createChain',a1);} async description(): Promise {return this.$callMethod('description');} async dimension(a1: number): Promise {return this.$callMethod('dimension',a1);} async dimensionType(...args: any[]): Promise {return this.$callMethod('dimensionType',...args);} @@ -1793,6 +1784,7 @@ export class Ravel extends Item { async handleSortableByValue(): Promise {return this.$callMethod('handleSortableByValue');} async hypercube(): Promise {return this.$callMethod('hypercube');} async inItem(a1: number,a2: number): Promise {return this.$callMethod('inItem',a1,a2);} + async initRavelFromDb(): Promise {return this.$callMethod('initRavelFromDb');} async joinLockGroup(a1: number): Promise {return this.$callMethod('joinLockGroup',a1);} async leaveLockGroup(): Promise {return this.$callMethod('leaveLockGroup');} async lockGroupColours(): Promise {return this.$callMethod('lockGroupColours');} @@ -1805,7 +1797,7 @@ export class Ravel extends Item { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async pickSliceLabels(a1: number,a2: string[]): Promise {return this.$callMethod('pickSliceLabels',a1,a2);} async pickedSliceLabels(...args: any[]): Promise {return this.$callMethod('pickedSliceLabels',...args);} async populateHypercube(a1: civita__Hypercube): Promise {return this.$callMethod('populateHypercube',a1);} @@ -2025,7 +2017,7 @@ export class Selection extends CppClass { async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} - async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} async onResizeHandle(a1: number,a2: number): Promise {return this.$callMethod('onResizeHandle',a1,a2);} async onResizeHandles(...args: boolean[]): Promise {return this.$callMethod('onResizeHandles',...args);} async portX(a1: number): Promise {return this.$callMethod('portX',a1);} @@ -2235,14 +2227,14 @@ export class VariablePaneCell extends CppClass { } export class VariableValue extends CppClass { - csvDialog: CSVDialog; rhs: civita__ITensor; + spec: DataSpec; tensorInit: civita__TensorVal; units: Units; constructor(prefix: string){ super(prefix); - this.csvDialog=new CSVDialog(this.$prefix()+'.csvDialog'); this.rhs=new civita__ITensor(this.$prefix()+'.rhs'); + this.spec=new DataSpec(this.$prefix()+'.spec'); this.tensorInit=new civita__TensorVal(this.$prefix()+'.tensorInit'); this.units=new Units(this.$prefix()+'.units'); } @@ -2252,15 +2244,20 @@ export class VariableValue extends CppClass { async atHCIndex(a1: number): Promise {return this.$callMethod('atHCIndex',a1);} async begin(): Promise {return this.$callMethod('begin');} async cancel(a1: boolean): Promise {return this.$callMethod('cancel',a1);} + async checkCancel(): Promise {return this.$callMethod('checkCancel');} + async classifyColumns(): Promise {return this.$callMethod('classifyColumns');} + async correctedUniqueValues(): Promise {return this.$callMethod('correctedUniqueValues');} async data(): Promise {return this.$callMethod('data');} async detailedText(...args: string[]): Promise {return this.$callMethod('detailedText',...args);} async enableSlider(...args: boolean[]): Promise {return this.$callMethod('enableSlider',...args);} async end(): Promise {return this.$callMethod('end');} async exportAsCSV(a1: string,a2: string,a3: boolean): Promise {return this.$callMethod('exportAsCSV',a1,a2,a3);} async godleyOverridden(...args: boolean[]): Promise {return this.$callMethod('godleyOverridden',...args);} + async guessSpecAndLoadFile(): Promise {return this.$callMethod('guessSpecAndLoadFile');} async hypercube(...args: any[]): Promise {return this.$callMethod('hypercube',...args);} async idx(): Promise {return this.$callMethod('idx');} async idxInRange(): Promise {return this.$callMethod('idxInRange');} + async importFromCSV(a1: string[]): Promise {return this.$callMethod('importFromCSV',a1);} async imposeDimensions(a1: Container>): Promise {return this.$callMethod('imposeDimensions',a1);} async incrSlider(a1: number): Promise {return this.$callMethod('incrSlider',a1);} async index(...args: any[]): Promise {return this.$callMethod('index',...args);} @@ -2268,9 +2265,16 @@ export class VariableValue extends CppClass { async isFlowVar(): Promise {return this.$callMethod('isFlowVar');} async isZero(): Promise {return this.$callMethod('isZero');} async lhs(): Promise {return this.$callMethod('lhs');} + async loadFile(): Promise {return this.$callMethod('loadFile');} + async loadFileFromName(a1: string): Promise {return this.$callMethod('loadFileFromName',a1);} async maxSliderSteps(): Promise {return this.$callMethod('maxSliderSteps');} async name(...args: string[]): Promise {return this.$callMethod('name',...args);} + async numInitialLines(...args: number[]): Promise {return this.$callMethod('numInitialLines',...args);} + async parseLines(a1: number): Promise {return this.$callMethod('parseLines',a1);} + async populateHeader(a1: number): Promise {return this.$callMethod('populateHeader',a1);} + async populateHeaders(): Promise {return this.$callMethod('populateHeaders');} async rank(): Promise {return this.$callMethod('rank');} + async reportFromFile(a1: string,a2: string): Promise {return this.$callMethod('reportFromFile',a1,a2);} async reset_idx(): Promise {return this.$callMethod('reset_idx');} async setArgument(a1: civita__ITensor,a2: civita__ITensor__Args): Promise {return this.$callMethod('setArgument',a1,a2);} async setArguments(...args: any[]): Promise {return this.$callMethod('setArguments',...args);} @@ -2289,6 +2293,7 @@ export class VariableValue extends CppClass { async type(): Promise {return this.$callMethod('type');} async typeName(a1: number): Promise {return this.$callMethod('typeName',a1);} async unitsCached(...args: boolean[]): Promise {return this.$callMethod('unitsCached',...args);} + async url(...args: string[]): Promise {return this.$callMethod('url',...args);} async value(): Promise {return this.$callMethod('value');} async valueAt(a1: number): Promise {return this.$callMethod('valueAt',a1);} async valueId(): Promise {return this.$callMethod('valueId');} @@ -2382,6 +2387,7 @@ export class civita__ITensor extends CppClass { async at(a1: number): Promise {return this.$callMethod('at',a1);} async atHCIndex(a1: number): Promise {return this.$callMethod('atHCIndex',a1);} async cancel(a1: boolean): Promise {return this.$callMethod('cancel',a1);} + async checkCancel(): Promise {return this.$callMethod('checkCancel');} async data(): Promise {return this.$callMethod('data');} async hypercube(...args: any[]): Promise {return this.$callMethod('hypercube',...args);} async imposeDimensions(a1: Container>): Promise {return this.$callMethod('imposeDimensions',a1);} @@ -2405,16 +2411,26 @@ export class civita__Index extends CppClass { async size(): Promise {return this.$callMethod('size');} } +export class civita__NamedDimension extends CppClass { + dimension: civita__Dimension; + constructor(prefix: string){ + super(prefix); + this.dimension=new civita__Dimension(this.$prefix()+'.dimension'); + } + async name(...args: string[]): Promise {return this.$callMethod('name',...args);} +} + export class civita__TensorVal extends CppClass { constructor(prefix: string){ super(prefix); } async allocVal(): Promise {return this.$callMethod('allocVal');} - async assign(a1: Map): Promise {return this.$callMethod('assign',a1);} + async assign(...args: any[]): Promise {return this.$callMethod('assign',...args);} async at(a1: number): Promise {return this.$callMethod('at',a1);} async atHCIndex(a1: number): Promise {return this.$callMethod('atHCIndex',a1);} async begin(): Promise {return this.$callMethod('begin');} async cancel(a1: boolean): Promise {return this.$callMethod('cancel',a1);} + async checkCancel(): Promise {return this.$callMethod('checkCancel');} async data(): Promise {return this.$callMethod('data');} async end(): Promise {return this.$callMethod('end');} async hypercube(...args: any[]): Promise {return this.$callMethod('hypercube',...args);} @@ -2477,6 +2493,103 @@ export class minsky__Canvas__ZoomCrop extends CppClass { async zoom(...args: number[]): Promise {return this.$callMethod('zoom',...args);} } +export class ravelCAPI__Database extends CppClass { + constructor(prefix: string){ + super(prefix); + } + async close(): Promise {return this.$callMethod('close');} + async connect(a1: string,a2: string,a3: string): Promise {return this.$callMethod('connect',a1,a2,a3);} + async connection(): Promise {return this.$callMethod('connection');} + async createTable(a1: string,a2: ravel__DataSpec): Promise {return this.$callMethod('createTable',a1,a2);} + async deduplicate(a1: string,a2: ravel__DataSpec): Promise {return this.$callMethod('deduplicate',a1,a2);} + async fullHypercube(a1: ravelCAPI__Ravel): Promise {return this.$callMethod('fullHypercube',a1);} + async hyperSlice(a1: ravelCAPI__Ravel): Promise {return this.$callMethod('hyperSlice',a1);} + async loadDatabase(a1: string[],a2: ravel__DataSpec): Promise {return this.$callMethod('loadDatabase',a1,a2);} + async loadDatabaseCallback(a1: minsky__dummy): Promise {return this.$callMethod('loadDatabaseCallback',a1);} + async numericalColumnNames(): Promise {return this.$callMethod('numericalColumnNames');} + async ravelPro(): Promise {return this.$callMethod('ravelPro');} + async setAxisNames(a1: string[],a2: string): Promise {return this.$callMethod('setAxisNames',a1,a2);} + async tableNames(): Promise {return this.$callMethod('tableNames');} +} + +export class ravelCAPI__Ravel extends CppClass { + constructor(prefix: string){ + super(prefix); + } + async addHandle(a1: string,a2: string[]): Promise {return this.$callMethod('addHandle',a1,a2);} + async adjustSlicer(a1: number): Promise {return this.$callMethod('adjustSlicer',a1);} + async allSliceLabels(a1: number,a2: string): Promise {return this.$callMethod('allSliceLabels',a1,a2);} + async applyCustomPermutation(a1: number,a2: number[]): Promise {return this.$callMethod('applyCustomPermutation',a1,a2);} + async available(): Promise {return this.$callMethod('available');} + async cancel(a1: boolean): Promise {return this.$callMethod('cancel',a1);} + async clear(): Promise {return this.$callMethod('clear');} + async currentPermutation(a1: number): Promise {return this.$callMethod('currentPermutation',a1);} + async daysUntilExpired(): Promise {return this.$callMethod('daysUntilExpired');} + async description(...args: any[]): Promise {return this.$callMethod('description',...args);} + async displayFilterCaliper(a1: number,a2: boolean): Promise {return this.$callMethod('displayFilterCaliper',a1,a2);} + async explain(a1: number,a2: number): Promise {return this.$callMethod('explain',a1,a2);} + async fromXML(a1: string): Promise {return this.$callMethod('fromXML',a1);} + async getCaliperPositions(a1: number): Promise {return this.$callMethod('getCaliperPositions',a1);} + async getHandleState(a1: number): Promise {return this.$callMethod('getHandleState',a1);} + async getRavelState(): Promise {return this.$callMethod('getRavelState');} + async handleDescription(a1: number): Promise {return this.$callMethod('handleDescription',a1);} + async handleSetReduction(a1: number,a2: string): Promise {return this.$callMethod('handleSetReduction',a1,a2);} + async hyperSlice(a1: civita__ITensor): Promise {return this.$callMethod('hyperSlice',a1);} + async lastError(): Promise {return this.$callMethod('lastError');} + async nextReduction(a1: string): Promise {return this.$callMethod('nextReduction',a1);} + async numAllSliceLabels(a1: number): Promise {return this.$callMethod('numAllSliceLabels',a1);} + async numHandles(): Promise {return this.$callMethod('numHandles');} + async numSliceLabels(a1: number): Promise {return this.$callMethod('numSliceLabels',a1);} + async onMouseDown(a1: number,a2: number): Promise {return this.$callMethod('onMouseDown',a1,a2);} + async onMouseLeave(): Promise {return this.$callMethod('onMouseLeave');} + async onMouseMotion(a1: number,a2: number): Promise {return this.$callMethod('onMouseMotion',a1,a2);} + async onMouseOver(a1: number,a2: number): Promise {return this.$callMethod('onMouseOver',a1,a2);} + async onMouseUp(a1: number,a2: number): Promise {return this.$callMethod('onMouseUp',a1,a2);} + async orderLabels(a1: number,a2: string): Promise {return this.$callMethod('orderLabels',a1,a2);} + async outputHandleIds(): Promise {return this.$callMethod('outputHandleIds');} + async populateFromHypercube(a1: civita__Hypercube): Promise {return this.$callMethod('populateFromHypercube',a1);} + async radius(): Promise {return this.$callMethod('radius');} + async rank(): Promise {return this.$callMethod('rank');} + async redistributeHandles(): Promise {return this.$callMethod('redistributeHandles');} + async render(a1: CAPIRenderer): Promise {return this.$callMethod('render',a1);} + async rescale(a1: number): Promise {return this.$callMethod('rescale',a1);} + async resetExplain(): Promise {return this.$callMethod('resetExplain');} + async selectedHandle(): Promise {return this.$callMethod('selectedHandle');} + async setCaliperPositions(a1: number,a2: number,a3: number): Promise {return this.$callMethod('setCaliperPositions',a1,a2,a3);} + async setCalipers(a1: number,a2: string,a3: string): Promise {return this.$callMethod('setCalipers',a1,a2,a3);} + async setExplain(a1: string,a2: number,a3: number): Promise {return this.$callMethod('setExplain',a1,a2,a3);} + async setHandleDescription(a1: number,a2: string): Promise {return this.$callMethod('setHandleDescription',a1,a2);} + async setHandleState(a1: number,a2: ravel__HandleState): Promise {return this.$callMethod('setHandleState',a1,a2);} + async setOutputHandleIds(a1: number[]): Promise {return this.$callMethod('setOutputHandleIds',a1);} + async setRavelState(a1: ravel__RavelState): Promise {return this.$callMethod('setRavelState',a1);} + async setSlicer(a1: number,a2: string): Promise {return this.$callMethod('setSlicer',a1,a2);} + async sliceLabels(a1: number): Promise {return this.$callMethod('sliceLabels',a1);} + async sortByValue(a1: civita__ITensor,a2: string): Promise {return this.$callMethod('sortByValue',a1,a2);} + async toXML(): Promise {return this.$callMethod('toXML');} + async version(): Promise {return this.$callMethod('version');} +} + +export class ravel__DataSpec extends CppClass { + dataCols: Container; + dimensionCols: Container; + dimensions: Sequence; + constructor(prefix: string){ + super(prefix); + this.dataCols=new Container(this.$prefix()+'.dataCols'); + this.dimensionCols=new Container(this.$prefix()+'.dimensionCols'); + this.dimensions=new Sequence(this.$prefix()+'.dimensions',civita__NamedDimension); + } + async counter(...args: boolean[]): Promise {return this.$callMethod('counter',...args);} + async dataRowOffset(...args: number[]): Promise {return this.$callMethod('dataRowOffset',...args);} + async decSeparator(...args: number[]): Promise {return this.$callMethod('decSeparator',...args);} + async dontFail(...args: boolean[]): Promise {return this.$callMethod('dontFail',...args);} + async escape(...args: number[]): Promise {return this.$callMethod('escape',...args);} + async headerRow(...args: number[]): Promise {return this.$callMethod('headerRow',...args);} + async mergeDelimiters(...args: boolean[]): Promise {return this.$callMethod('mergeDelimiters',...args);} + async quote(...args: number[]): Promise {return this.$callMethod('quote',...args);} + async separator(...args: number[]): Promise {return this.$callMethod('separator',...args);} +} + export class ravel__HandleState extends CppClass { customOrder: Sequence; customOrderComplement: Sequence; diff --git a/gui-js/libs/shared/src/lib/constants/constants.ts b/gui-js/libs/shared/src/lib/constants/constants.ts index 8da3c8772..f3248810e 100644 --- a/gui-js/libs/shared/src/lib/constants/constants.ts +++ b/gui-js/libs/shared/src/lib/constants/constants.ts @@ -24,7 +24,6 @@ export const events = { CLOSE_WINDOW: 'close-window', CONTEXT_MENU: 'context-menu', CREATE_MENU_POPUP: 'create-menu-popup', - CSV_IMPORT_REFRESH: 'csv-import-refresh', CURRENT_TAB_MOVE_TO: 'current-tab-move-to', CURRENT_TAB_POSITION: 'current-tab-position', CURSOR_BUSY: 'cursor-busy', @@ -39,6 +38,7 @@ export const events = { GODLEY_VIEW_IMPORT_STOCK: 'godley-view-import-stock', HELP_FOR: 'help-for', IMPORT_CSV: 'import-csv', + IMPORT_CSV_TO_DB: 'import-csv-to-db', INIT_MENU_FOR_GODLEY_VIEW: 'init-menu-for-godley-view', KEY_PRESS: 'key-press', LOG: 'log', @@ -51,6 +51,7 @@ export const events = { RECORD: 'record', RECORDING_REPLAY: 'recording-replay', RECORDING_STATUS_CHANGED: 'recording-status-changed', + REFRESH_CSV_IMPORT: 'refresh-csv-import', REPLAY_RECORDING: 'replay-recording', RESET_ZOOM: 'reset-zoom', RESET_SCROLL: 'reset-scroll', diff --git a/gui-js/libs/shared/src/lib/constants/version.ts b/gui-js/libs/shared/src/lib/constants/version.ts index 17fa9aa60..64abb2987 100644 --- a/gui-js/libs/shared/src/lib/constants/version.ts +++ b/gui-js/libs/shared/src/lib/constants/version.ts @@ -1 +1 @@ -export const version="3.19.0-beta.1"; +export const version="3.19.0-beta.2"; diff --git a/gui-js/libs/ui-components/src/index.ts b/gui-js/libs/ui-components/src/index.ts index 2a63964c9..20009ee5a 100644 --- a/gui-js/libs/ui-components/src/index.ts +++ b/gui-js/libs/ui-components/src/index.ts @@ -1,32 +1,34 @@ +export * from './lib/connect-database/connect-database.component'; export * from './lib/cli-input/cli-input.component'; export * from './lib/create-variable/create-variable.component'; +export * from './lib/directives/latex.directive'; export * from './lib/edit-description/edit-description.component'; -export * from './lib/edit-handle-description/edit-handle-description.component'; -export * from './lib/edit-handle-dimension/edit-handle-dimension.component'; -export * from './lib/pick-slices/pick-slices.component'; -export * from './lib/lock-handles/lock-handles.component'; export * from './lib/edit-godley-currency/edit-godley-currency.component'; export * from './lib/edit-godley-title/edit-godley-title.component'; export * from './lib/edit-group/edit-group.component'; +export * from './lib/edit-handle-description/edit-handle-description.component'; +export * from './lib/edit-handle-dimension/edit-handle-dimension.component'; export * from './lib/edit-integral/edit-integral.component'; export * from './lib/edit-operation/edit-operation.component'; export * from './lib/edit-user-function/edit-user-function.component'; export * from './lib/equations/equations.component'; export * from './lib/find-all-instances/find-all-instances.component'; export * from './lib/godley-widget-view/godley-widget-view.component'; -export * from './lib/variable-pane/variable-pane.component'; export * from './lib/header/header.component'; export * from './lib/import-csv/import-csv.component'; export * from './lib/input-modal/input-modal.component'; +export * from './lib/lock-handles/lock-handles.component'; +export * from './lib/new-database/new-database.component'; export * from './lib/new-pub-tab/new-pub-tab.component'; export * from './lib/page-not-found/page-not-found.component'; -export * from './lib/summary/summary.component'; export * from './lib/pen-styles/pen-styles.component'; +export * from './lib/pick-slices/pick-slices.component'; export * from './lib/plot-widget-options/plot-widget-options.component'; export * from './lib/plot-widget-view/plot-widget-view.component'; +export * from './lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component'; export * from './lib/ravel-widget-view/ravel-widget-view.component'; export * from './lib/rename-all-instances/rename-all-instances.component'; - +export * from './lib/summary/summary.component'; +export * from './lib/variable-pane/variable-pane.component'; export * from './lib/wiring/wiring.component'; -export * from './lib/directives/latex.directive'; diff --git a/gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts new file mode 100644 index 000000000..4f5dbea84 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts @@ -0,0 +1,82 @@ +import { ChangeDetectorRef, Component, SimpleChanges } from '@angular/core'; +import { FormsModule, } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { ElectronService } from '@minsky/core'; +import { Ravel} from '@minsky/shared'; +import { MatButtonModule } from '@angular/material/button'; +import { OpenDialogOptions } from 'electron'; +import { CommonModule } from '@angular/common'; // Often useful for ngIf, ngFor + +@Component({ + selector: 'connect-database', + templateUrl: './connect-database.html', + styleUrls: ['./connect-database.scss'], + standalone: true, + imports: [ + FormsModule, + CommonModule, + MatButtonModule, + ], +}) +export class ConnectDatabaseComponent { + dbType: string="sqlite3"; + connection: string; + table: string=""; + tables: string[]=[]; + ravel: Ravel; + constructor( + private route: ActivatedRoute, + private electronService: ElectronService, + private cdRef: ChangeDetectorRef + ) { + this.ravel=new Ravel(this.electronService.minsky.canvas.item); + } + + setDbType(event: Event) { + const target = event.target as HTMLSelectElement; + this.dbType=target.value; + } + + // get list of tables + async getTables() { + this.ravel.db.connect(this.dbType,this.connection,""); + this.tables=await this.ravel.db.tableNames(); + } + + setTable(event: Event) { + const target = event.target as HTMLSelectElement; + this.table=target.value; + } + + setConnection(event: Event) { + const target = event.target as HTMLSelectElement; + this.connection=target.value; + } + + async selectFile() { + let options: OpenDialogOptions = { + defaultPath: ':models', + filters: [ + { extensions: ['sqlite'], name: 'CSV' }, + { extensions: ['*'], name: 'All Files' }, + ], + properties: ['openFile'], + }; + //if (defaultPath) options['defaultPath'] = defaultPath; + let filePath = await this.electronService.openFileDialog(options); + if (typeof filePath==='string') + this.connection=`db=${filePath}`; + else + this.connection=`db=${filePath[0]}`; + await this.getTables(); + } + + connect() { + this.ravel.db.connect(this.dbType,this.connection,this.table); + this.ravel.initRavelFromDb(); + this.electronService.minsky.canvas.requestRedraw(); + this.closeWindow(); + } + + closeWindow() {this.electronService.closeWindow();} +} diff --git a/gui-js/libs/ui-components/src/lib/connect-database/connect-database.html b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.html new file mode 100644 index 000000000..86704b2f7 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.html @@ -0,0 +1,24 @@ +
+
+ + +
+ + +
+ + +
+
diff --git a/gui-js/libs/ui-components/src/lib/connect-database/connect-database.scss b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.scss new file mode 100644 index 000000000..ba9966679 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/connect-database/connect-database.scss @@ -0,0 +1,13 @@ +@import '../../../../shared/src/lib/theme/common-style.scss'; + +.row { + display: flex; + flex-direction: row; + align-items: center; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.html b/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.html index fad567f3b..9e42ffeef 100644 --- a/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.html +++ b/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.html @@ -386,7 +386,13 @@ - + + + + + diff --git a/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts b/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts index 7cc8f127d..997e79cdb 100644 --- a/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts +++ b/gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts @@ -3,6 +3,7 @@ import { AbstractControl, FormControl, FormGroup, FormsModule, ReactiveFormsModu import { ActivatedRoute } from '@angular/router'; import { ElectronService } from '@minsky/core'; import { + CSVDialog, dateTimeFormats, events, importCSVvariableName, @@ -17,6 +18,7 @@ import { NgIf, NgFor, NgStyle } from '@angular/common'; import { MatOptionModule } from '@angular/material/core'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; +import JSON5 from 'json5'; enum ColType { axis = "axis", @@ -92,12 +94,11 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni fileLoaded = false; - itemId: string; systemWindowId: number; isInvokedUsingToolbar: boolean; + newTable: boolean; examplesPath: string; - valueId: string; - variableValuesSubCommand: VariableValue; + csvDialog: CSVDialog; timeFormatStrings = dateTimeFormats; parsedLines: string[][] = []; csvCols: any[]; @@ -134,6 +135,9 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni public get counter(): AbstractControl { return this.form.get('counter'); } + public get dropTable(): AbstractControl { + return this.form.get('dropTable'); + } public get separator(): AbstractControl { return this.form.get('separator'); } @@ -187,9 +191,10 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni ) { super(); this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => { - this.itemId = params.itemId; + this.csvDialog = new CSVDialog(params.csvDialog); this.systemWindowId = params.systemWindowId; - this.isInvokedUsingToolbar = params.isInvokedUsingToolbar; + this.isInvokedUsingToolbar = params.isInvokedUsingToolbar==="true"; + this.newTable = params.dropTable==="true"; this.examplesPath = params.examplesPath; }); @@ -199,6 +204,7 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni detailedDescription: new FormControl(''), dontFail: new FormControl(false), counter: new FormControl(false), + dropTable: new FormControl(false), decSeparator: new FormControl('.'), duplicateKeyAction: new FormControl('throwException'), escape: new FormControl(''), @@ -216,7 +222,7 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni }), }); - this.electronService.on(events.CSV_IMPORT_REFRESH, async e => { + this.electronService.on(events.REFRESH_CSV_IMPORT, async e => { await this.getCSVDialogSpec(); this.updateColumnTypes(); this.updateForm(); @@ -239,10 +245,6 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni ngAfterViewInit() { (async () => { - this.valueId = await this.getValueId(); - this.variableValuesSubCommand = this.electronService.minsky.variableValues.elem(this.valueId); - - await this.getCSVDialogSpec(); this.updateForm(); this.load(2); @@ -272,6 +274,7 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni updateForm() { this.url.setValue(this.dialogState.url); + if (!this.files && this.dialogState.url) this.files=[this.dialogState.url]; this.dontFail.setValue(this.dialogState.spec.dontFail); this.counter.setValue(this.dialogState.spec.counter); @@ -289,18 +292,16 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni }); } - async getValueId() { - return new VariableBase(this.electronService.minsky.namedItems.elem(this.itemId)).valueId(); - } - async selectFile(defaultPath: string = '') { let options: OpenDialogOptions = { + defaultPath: ':data', filters: [ { extensions: ['csv'], name: 'CSV' }, { extensions: ['*'], name: 'All Files' }, ], properties: ['openFile', 'multiSelections'], }; + // support examples directory if (defaultPath) options['defaultPath'] = defaultPath; const filePaths = await this.electronService.openFileDialog(options); @@ -324,22 +325,20 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni this.setParameterNameFromUrl(); if (this.url.value.includes('://')) { - const savePath = await this.electronService.downloadCSV({ windowUid: this.itemId, url: this.url.value }); + const savePath = await this.electronService.downloadCSV({ windowUid: this.csvDialog.$prefix(), url: this.url.value }); this.url.setValue(savePath); this.files = [savePath]; } - const fileUrlOnServer = await this.variableValuesSubCommand.csvDialog.url(); + const fileUrlOnServer = await this.csvDialog.url(); - if (this.url.value !== fileUrlOnServer) { - await this.variableValuesSubCommand.csvDialog.url(this.url.value); - await this.variableValuesSubCommand.csvDialog.guessSpecAndLoadFile(); + if (!this.url.value || this.url.value !== fileUrlOnServer) { + await this.csvDialog.url(this.url.value); + await this.csvDialog.guessSpecAndLoadFile(); await this.getCSVDialogSpec(); this.updateForm(); - } else { - await this.variableValuesSubCommand.csvDialog.loadFile(); } - + await this.csvDialog.loadFile(); await this.parseLines(); for (const tab of this.tabs) { @@ -359,9 +358,9 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni } async getCSVDialogSpec() { - this.variableValuesSubCommand.csvDialog.spec.toSchema(); - this.dialogState = await this.variableValuesSubCommand.csvDialog.$properties() as Record; - this.uniqueValues = await this.variableValuesSubCommand.csvDialog.correctedUniqueValues(); + this.csvDialog.spec.toSchema(); + this.dialogState = await this.csvDialog.$properties() as Record; + this.uniqueValues = await this.csvDialog.correctedUniqueValues(); } updateColumnTypes() { @@ -384,7 +383,7 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni } async parseLines() { - this.parsedLines = await this.variableValuesSubCommand.csvDialog.parseLines(this.dialogState.spec.maxColumn) as string[][]; + this.parsedLines = await this.csvDialog.parseLines(this.dialogState.spec.maxColumn) as string[][]; await this.getCSVDialogSpec(); let header = this.dialogState.spec.headerRow; @@ -413,7 +412,7 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni onSeparatorChange() { this.updateSpecFromForm(); - this.variableValuesSubCommand.csvDialog.spec.$properties(this.dialogState.spec); + this.csvDialog.spec.$properties(this.dialogState.spec); this.parseLines(); } @@ -553,9 +552,13 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni if (this.dialogState.spec.dataCols.length === 0) this.dialogState.spec.counter = true; - let v = new VariableBase(this.electronService.minsky.canvas.item); + this.csvDialog.spec.$properties(this.dialogState.spec); + + if (!this.files || !this.files[0]) this.files=[this.url.value]; + if (this.files && (this.dropTable.value || this.newTable)) + this.electronService.minsky.databaseIngestor.createTable(this.files[0]); // returns an error message on error - const res = await v.importFromCSV(this.files, this.dialogState.spec) as unknown as string; + const res = await this.csvDialog.importFromCSV(this.files) as unknown as string; if (typeof res === 'string') { const positiveResponseText = 'Yes'; @@ -575,20 +578,16 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni return; } - const currentItemId = await v.id(); - const currentItemName = await v.name(); - - if ( - this.isInvokedUsingToolbar && - currentItemId === this.itemId && - currentItemName === importCSVvariableName && - this.parameterName.value - ) { - await this.electronService.minsky.canvas.renameItem(this.parameterName.value); - v.tooltip(this.shortDescription.value); - v.detailedText(this.detailedDescription.value); + if (this.isInvokedUsingToolbar && this.parameterName.value) { + // rename variable if newly added variable is still focussed + let v=new VariableBase(this.electronService.minsky.canvas.itemFocus); + let vv=new VariableValue(this.csvDialog.$prefix()); + if (await v?.valueId()===await vv?.valueId()) { + v.tooltip(this.shortDescription.value); + v.detailedText(this.detailedDescription.value); + v.name(this.parameterName.value); + } } - this.closeWindow(); } @@ -598,26 +597,26 @@ export class ImportCsvComponent extends Zoomable implements OnInit, AfterViewIni .split('.csv')[0]; const filePath = await this.electronService.saveFileDialog({ - defaultPath: `${filePathWithoutExt}-error-report.csv`, + defaultPath: `:data/${filePathWithoutExt}-error-report.csv`, title: 'Save report', properties: ['showOverwriteConfirmation', 'createDirectory'], filters: [{ extensions: ['csv'], name: 'CSV' }], }); if (!filePath) return; - await this.variableValuesSubCommand.csvDialog.reportFromFile(this.url.value, filePath); + await this.csvDialog.reportFromFile(this.url.value, filePath); return; } async contextMenu(row: number, col: number) { // update C++ spec with current state this.updateSpecFromForm(); - await this.variableValuesSubCommand.csvDialog.spec.$properties(this.dialogState.spec); + await this.csvDialog.spec.$properties(this.dialogState.spec); this.electronService.send(events.CONTEXT_MENU, { x: row, y: col, type: 'csv-import', - command: this.variableValuesSubCommand.$prefix(), + command: this.csvDialog.$prefix(), }); } diff --git a/gui-js/libs/ui-components/src/lib/new-database/new-database.component.ts b/gui-js/libs/ui-components/src/lib/new-database/new-database.component.ts new file mode 100644 index 000000000..b64007b32 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/new-database/new-database.component.ts @@ -0,0 +1,98 @@ +import { ChangeDetectorRef, Component, SimpleChanges } from '@angular/core'; +import { FormsModule, } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { ElectronService } from '@minsky/core'; +import { events} from '@minsky/shared'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { OpenDialogOptions, SaveDialogOptions } from 'electron'; +import { CommonModule } from '@angular/common'; // Often useful for ngIf, ngFor + +@Component({ + selector: 'new-database', + templateUrl: './new-database.html', + styleUrls: ['./new-database.scss'], + standalone: true, + imports: [ + FormsModule, + CommonModule, + MatAutocompleteModule, + MatButtonModule, + MatOptionModule, + ], +}) +export class NewDatabaseComponent { + dbType: string="sqlite3"; + connection: string; + table: string=""; + tables: string[]=[]; + constructor( + private route: ActivatedRoute, + private electronService: ElectronService, + private cdRef: ChangeDetectorRef + ) { + } + + setDbType(event: Event) { + const target = event.target as HTMLSelectElement; + this.dbType=target.value; + } + + setConnection(event: Event) { + const target = event.target as HTMLSelectElement; + this.connection=target.value; + } + + // get list of tables + async getTables() { + this.electronService.minsky.databaseIngestor.db.connect(this.dbType,this.connection,""); + this.tables=await this.electronService.minsky.databaseIngestor.db.tableNames(); + } + + setTable(event: Event) { + const target = event.target as HTMLSelectElement; + this.table=target.value; + } + + setTableInput(event) { + this.table=event?.option?.value; + } + + async selectFile() { + let options: OpenDialogOptions = { + title: 'Select existing database', + defaultPath: ':models', + filters: [ + { extensions: ['sqlite'], name: 'SQLite' }, + { extensions: ['*'], name: 'All Files' }, + ], + }; + //if (defaultPath) options['defaultPath'] = defaultPath; + let filePath = await this.electronService.openFileDialog(options); + if (filePath) + this.connection=`db=${filePath}`; + else { + // if the user cancelled, then try to create a new database file + options.title='Create new database'; + let filePath = await this.electronService.saveFileDialog(options); + if (filePath) + this.connection=`db=${filePath}`; + } + if (this.connection) + await this.getTables(); + } + + connect() { + if (!this.connection || !this.table) { + this.electronService.showMessageBoxSync({message: "Connection string or table not present"}); + return; + } + this.electronService.minsky.databaseIngestor.db.connect(this.dbType,this.connection,this.table); + let dropTable=!this.tables.includes(this.table); + this.electronService.invoke(events.IMPORT_CSV_TO_DB, {dropTable}); + this.closeWindow(); + } + + closeWindow() {this.electronService.closeWindow();} +} diff --git a/gui-js/libs/ui-components/src/lib/new-database/new-database.html b/gui-js/libs/ui-components/src/lib/new-database/new-database.html new file mode 100644 index 000000000..5a71b2fa1 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/new-database/new-database.html @@ -0,0 +1,40 @@ +
+
+ + +
+ + + + + {{table}} + +
+ + +
+
diff --git a/gui-js/libs/ui-components/src/lib/new-database/new-database.scss b/gui-js/libs/ui-components/src/lib/new-database/new-database.scss new file mode 100644 index 000000000..ba9966679 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/new-database/new-database.scss @@ -0,0 +1,13 @@ +@import '../../../../shared/src/lib/theme/common-style.scss'; + +.row { + display: flex; + flex-direction: row; + align-items: center; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts new file mode 100644 index 000000000..59e614118 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts @@ -0,0 +1,59 @@ +import { ChangeDetectorRef, Component, OnInit, SimpleChanges } from '@angular/core'; +import { FormsModule, } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { ElectronService } from '@minsky/core'; +import { events, Ravel} from '@minsky/shared'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { OpenDialogOptions, SaveDialogOptions } from 'electron'; +import { CommonModule } from '@angular/common'; // Often useful for ngIf, ngFor +import JSON5 from 'json5'; + +@Component({ + selector: 'ravel-select-horizontal-dim', + templateUrl: './ravel-select-horizontal-dim.html', + styleUrls: ['./ravel-select-horizontal-dim.scss'], + standalone: true, + imports: [ + FormsModule, + CommonModule, + MatAutocompleteModule, + MatButtonModule, + MatOptionModule, + ], +}) +export class RavelSelectHorizontalDimComponent implements OnInit { + dataCols=[]; + horizontalDimCols=new Set; + horizontalDimName="?"; + ravel: Ravel; + + constructor( + private route: ActivatedRoute, + private electronService: ElectronService, + private cdRef: ChangeDetectorRef + ) { + this.ravel=new Ravel(this.electronService.minsky.canvas.item); + } + + async ngOnInit() { + this.dataCols=await this.ravel.db.numericalColumnNames(); + } + + clickDim(event: Event) { + const target = event.target as HTMLInputElement; + if (target.checked) + this.horizontalDimCols.add(target.name); + else + this.horizontalDimCols.delete(target.name); + } + + setHorizontalNames() { + this.ravel.db.setAxisNames([...this.horizontalDimCols],this.horizontalDimName); + this.ravel.initRavelFromDb(); + this.closeWindow(); + } + + closeWindow() {this.electronService.closeWindow();} +} diff --git a/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.html b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.html new file mode 100644 index 000000000..92059b039 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.html @@ -0,0 +1,19 @@ +
+ + +
+ +
+ + +
+
+
+
+ + +
+
diff --git a/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.scss b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.scss new file mode 100644 index 000000000..908ca4bf0 --- /dev/null +++ b/gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.scss @@ -0,0 +1,23 @@ +@import '../../../../shared/src/lib/theme/common-style.scss'; + +:host { + display: block; /* Important for host to take up space */ + height: 100%; /* Allows the component to fill its parent */ +} + +.selectors { + height: 100vh; //calc(100vh + 100px); + overflow-y: auto; +} + +.dim-selector { + display: flex; + flex-direction: row; + justify-content: end; + width: 100%; +} + +.form-buttons { + margin-top: 0px; + margin-bottom: 10px; +} diff --git a/gui-js/package-lock.json b/gui-js/package-lock.json index 390bd2f4f..1a36c7227 100644 --- a/gui-js/package-lock.json +++ b/gui-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "ravel", - "version": "3.19.0-beta.1", + "version": "3.19.0-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ravel", - "version": "3.19.0-beta.1", + "version": "3.19.0-beta.2", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { @@ -77,13 +77,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2001.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2001.0.tgz", - "integrity": "sha512-IDBG+YP0nPaA/tIjtJ1ZPh0VEfbxSn0yCvbS7dTfqyrnmanPUFpU5qsT9vJTU6yzkuzBEhNFRzkUCQaUAziLRA==", + "version": "0.2001.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2001.2.tgz", + "integrity": "sha512-n6F9VMJXbesgzV4aQEhqoT83irJw+RBbo/V6F8uHilDF3bC4jHBgFhcLkajNAg6i3gLcQb6BpResO7vqQ5MsaQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.0", + "@angular-devkit/core": "20.1.2", "rxjs": "7.8.2" }, "engines": { @@ -93,17 +93,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.1.0.tgz", - "integrity": "sha512-u0v5X5djZnW7K9HW+tsroyYVNnoX9Q2fCw9+kTBo7kOppM1p+bQ/krLWE2joWhgC++TZV1q0y/T/uEbAP0wyMg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.1.2.tgz", + "integrity": "sha512-WSkpgMiEryJdCsmbOjx6NUff1RNrZVUneKtYR2cp0AwbkBV3+RaLkQJGtJwd60hn+4OtB6HhDJT3EWigja4yTA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2001.0", - "@angular-devkit/build-webpack": "0.2001.0", - "@angular-devkit/core": "20.1.0", - "@angular/build": "20.1.0", + "@angular-devkit/architect": "0.2001.2", + "@angular-devkit/build-webpack": "0.2001.2", + "@angular-devkit/core": "20.1.2", + "@angular/build": "20.1.2", "@babel/core": "7.27.7", "@babel/generator": "7.27.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -114,7 +114,7 @@ "@babel/preset-env": "7.27.2", "@babel/runtime": "7.27.6", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.1.0", + "@ngtools/webpack": "20.1.2", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", @@ -169,7 +169,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.1.0", + "@angular/ssr": "^20.1.2", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", @@ -226,13 +226,13 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2001.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2001.0.tgz", - "integrity": "sha512-41dGClWoMAL+SoEazyw7AghvVHhbxF6LRSMjlgEiFmSy0aGVyEsYTeH+TlBwClS0KUKXtGx16C5cKch21CuAXA==", + "version": "0.2001.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2001.2.tgz", + "integrity": "sha512-JrirWgiauncSeydGkFC0DSYJcyukVeYP8wNxM9IPHf9Yv3E1v83VZRAX4R77lDVzVNK2IMWLhmpWXi49cZWDRQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2001.0", + "@angular-devkit/architect": "0.2001.2", "rxjs": "7.8.2" }, "engines": { @@ -246,9 +246,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.0.tgz", - "integrity": "sha512-i2t22bklvKsqdwmUtjXltRyxmJ+lJW8isrdc7XeN0N6VW/lDHSJqFlucT1+pO9+FxXJQyz3Hc1dpRd6G65mGyw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.2.tgz", + "integrity": "sha512-GBZoc5VxgY0xnXVwC715ubcWpVKc2m1H63Nv/msw5mmnfkjgOyG2lo4vA5VzLYVvptc8hwUhX9rsLN/C340rDg==", "dev": true, "license": "MIT", "dependencies": { @@ -274,9 +274,9 @@ } }, "node_modules/@angular/animations": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.1.0.tgz", - "integrity": "sha512-5ILngsvu5VPQYaIm7lRyegZaDaAEtLUIPSS8h1dzWPaCxBIJ4uwzx9RDMiF32zhbxi+q0mAO2w2FdDlzWTT3og==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.1.3.tgz", + "integrity": "sha512-3mkWhcHw2CbfvvjfJYMWjXbTtNHAtZDiVuaqQX4r9i0rPbQ7DqoM1zSgC6XWainWqxnfCHZIZFoI6PKEBVKSrg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -285,19 +285,19 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0" + "@angular/common": "20.1.3", + "@angular/core": "20.1.3" } }, "node_modules/@angular/build": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.1.0.tgz", - "integrity": "sha512-Sl4rkq5PQIrbVNk8cXx2JQhQ156H4bXLvfAYpgXPHAfSfbIIzaV25LJIfTdWSEjMzBGdIX5E0Vpi0SGwcNS7Uw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.1.2.tgz", + "integrity": "sha512-QCzXl/+nnlU7e6hTqWK5dkeUbZWAy/n5trbkIzBLiVQj6j1iTDoF3ABkS76jn5LUKB0Fx1AJVCSAqdxHqMHjDQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2001.0", + "@angular-devkit/architect": "0.2001.2", "@babel/core": "7.27.7", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -339,7 +339,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.1.0", + "@angular/ssr": "^20.1.2", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -389,14 +389,14 @@ } }, "node_modules/@angular/cdk": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.1.0.tgz", - "integrity": "sha512-JhgbSOv7xZqWNZjuCh8A3A7pGv0mhtmGjHo36157LrxRO6R7x2yJJjxC5nQeroKZWhgN+X/jG/EJlzEvl9PxTw==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.1.3.tgz", + "integrity": "sha512-TO/OBOPWIDJe+0g4S+ye6hewnWOhgWGa4iygvAlmQ77nyqhioHT60puyaDZRATxKh9k6KVmg9cPAk1lYbOFvaA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "parse5": "^7.1.2", + "parse5": "^8.0.0", "tslib": "^2.3.0" }, "peerDependencies": { @@ -406,9 +406,9 @@ } }, "node_modules/@angular/common": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.1.0.tgz", - "integrity": "sha512-RsHClHJux+4lXrHdGHVw22wekRbSjYtx6Xwjox2S+IRPP51CbX0KskAALZ9ZmtCttkYSFVtvr0S+SQrU2cu5WA==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.1.3.tgz", + "integrity": "sha512-h2eQfbx6kYw69xpEHtwZ3XbtWinGa6f8sXj7k9di1/xVAxqtbf+9OcBhYYY++oR1QqDeRghNYNblNNt0H9zKzQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -417,14 +417,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.1.0", + "@angular/core": "20.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.1.0.tgz", - "integrity": "sha512-sM8H3dJotIDDmI1u8qGuAn16XVfR7A4+/5s5cKLI/osnnIjafi5HHqAf76R5IlGoIv0ZHVQIYaJ/Qdvfyvdhfg==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.1.3.tgz", + "integrity": "sha512-NGMFLymImIdvjLSoH+pasgtJxKynDHX9COBU6T5LP7qi5kf6eR829Zrf7650R3K+uERqwz5PTLg8Kwa4aY7I9w==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -434,9 +434,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.1.0.tgz", - "integrity": "sha512-ajbCmvYYFxeXRdKSfdHjp62MZ2lCMUS0UzswBDAbT9sPd/ThppbvLXLsMBj8SlwaXSSBeTAa1oSHEO1MeuVvGQ==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.1.3.tgz", + "integrity": "sha512-NT7+vtwABtvVj2NLL7KvRzSsa5hgro23AvkAvg6A5sdfWzYDRXovI0YILlTIx1oEA8rupTPu/39gStW5k8XZqg==", "license": "MIT", "dependencies": { "@babel/core": "7.28.0", @@ -456,7 +456,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.1.0", + "@angular/compiler": "20.1.3", "typescript": ">=5.8 <5.9" }, "peerDependenciesMeta": { @@ -527,9 +527,9 @@ } }, "node_modules/@angular/core": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.1.0.tgz", - "integrity": "sha512-/dJooZi+OAACkjWgGMPrOOGikdtlTJXwdeXPJTgZSUD5L8oQMbhZFG0XW/1Hldvsti87wPjZPz67ivB7zR86VA==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.1.3.tgz", + "integrity": "sha512-haQypZGbKKsClDbR0I4eK+PmKGaZ8b/9QDwNYzInaEqHrTX/rkFXu0L0ejTTznElutQuMM6OPh6aVfnJ9nRr2g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -538,7 +538,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.1.0", + "@angular/compiler": "20.1.3", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -552,9 +552,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.1.0.tgz", - "integrity": "sha512-NgQxowyyG2yiSOXxtQS1xK1vAQT+4GRoMFuzmS3uBshIifgCgFckSxJHQXhlQOInuv2NsZ1Q0HuCvao+yZfIow==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.1.3.tgz", + "integrity": "sha512-q2Lbz65mqk/Xmp3qvFSZyUJRKeah3jtfSRxJlHC63utG5WdGl7gN7xRy2dydarRKToWyXqMsjoSlh1YIrUIAng==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -563,23 +563,23 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0", + "@angular/common": "20.1.3", + "@angular/core": "20.1.3", + "@angular/platform-browser": "20.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.1.0.tgz", - "integrity": "sha512-LfGz/V/kZwRIhzIZBiurM4Wc5CQiiJkiOChUfoEOvQLN2hckPFZbbvtg6JwxxA6nhzsDhuGHbj7Xj5dNsLfZLw==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.1.3.tgz", + "integrity": "sha512-W6/XJ2mih70b+PJUEAbI3mC415/SNY06nMBKcjWjRSth0jHe5/ujqIj0WkygkpDz34HEa11vV/0BgSpdS2FT5g==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/cdk": "20.1.0", + "@angular/cdk": "20.1.3", "@angular/common": "^20.0.0 || ^21.0.0", "@angular/core": "^20.0.0 || ^21.0.0", "@angular/forms": "^20.0.0 || ^21.0.0", @@ -588,9 +588,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.1.0.tgz", - "integrity": "sha512-l3+Ijq5SFxT0v10DbOyMc7NzGdbK76yot2i8pXyArlPSPmpWvbbjXbiBqzrv3TSTrksHBhG3mMvyhTmHQ1cQFA==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.1.3.tgz", + "integrity": "sha512-58iwj2LXdvwr4DG5tAiA2vj9bm/fhBWaR5JWvn3fJEAdW8fnT2gpjpfdBJTMcqg7Qfpx0ZhFsRxH2EUGEV6mvw==", "license": "MIT", "peer": true, "dependencies": { @@ -600,9 +600,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.1.0", - "@angular/common": "20.1.0", - "@angular/core": "20.1.0" + "@angular/animations": "20.1.3", + "@angular/common": "20.1.3", + "@angular/core": "20.1.3" }, "peerDependenciesMeta": { "@angular/animations": { @@ -611,9 +611,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.1.0.tgz", - "integrity": "sha512-s+Rm2akzYTE2UFdXZPvf02TxDCDskGdUxAxa/jmJbVuOpniuY0RlbnxIKDUD0qj3bYMUkbr7f2KJwHVldqJP6w==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.1.3.tgz", + "integrity": "sha512-y8m+HNHTYfgyQ/Mtku6+NOvlrD54oaj5cTnr382MVc692r+FuBkI9jMI1oZCqNTdv9cFK6Opj5Ie6A7ZxAfGVA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -622,16 +622,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/compiler": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0" + "@angular/common": "20.1.3", + "@angular/compiler": "20.1.3", + "@angular/core": "20.1.3", + "@angular/platform-browser": "20.1.3" } }, "node_modules/@angular/router": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.1.0.tgz", - "integrity": "sha512-fuUX1+AhcVSDgSSx85o6VOtXKM3oXAza+44jQ+nJGf316P0xpLKA586DKRNPjS4sRsWM7otKuOOTXXc4AMUHpQ==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.1.3.tgz", + "integrity": "sha512-ELJyzFJ2JeJkuVpv3kte4AwGBd/zuB5H/wv4+9gcmf6exxO5xH2/PbbLDGs+rWwHkCUcoRHFVyUPqk9yuRq/XA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -640,9 +640,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.1.0", - "@angular/core": "20.1.0", - "@angular/platform-browser": "20.1.0", + "@angular/common": "20.1.3", + "@angular/core": "20.1.3", + "@angular/platform-browser": "20.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -3287,28 +3287,28 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", - "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "license": "MIT", "dependencies": { - "@emnapi/wasi-threads": "1.0.3", + "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", - "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz", - "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -3786,14 +3786,14 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", @@ -3814,9 +3814,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", "dev": true, "license": "MIT", "engines": { @@ -3824,9 +3824,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "dev": true, "license": "MIT", "engines": { @@ -4107,6 +4107,17 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -4168,6 +4179,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", @@ -5072,9 +5094,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.1.0.tgz", - "integrity": "sha512-v+Mdg+NIvkWJYWcuHCQeRC4/Wov8RxNEF8eiCPFmQGmXJllIWUybY/o9lysG1TY4j/2H56VinIBYbeK/VIBYvg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.1.2.tgz", + "integrity": "sha512-1kN6o/JGevLY9d89qyRtr5bKkRMBUwH2/6wIquZgkcK2jPYs0Cmm0N7kV0eDL7yBFLI4RtyV6IHligPmdCHpeA==", "dev": true, "license": "MIT", "engines": { @@ -7076,27 +7098,41 @@ ] }, "node_modules/@nx/workspace": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.2.3.tgz", - "integrity": "sha512-bC3J6pgXvL9JWyYmP7AOGCIZhtI6vmY1YLan1T+FFkSr7yyKvIwnnL9E68whQD5jcbJl1Mvu9l0lVlsVdQYF/g==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.3.5.tgz", + "integrity": "sha512-jvA/wVOzMANHIvyz+9ACkK/twUaYHWgzDbES+xnQDdNMnjzkbgHPWdeTnjkyu2dspTm4c5nVMHH6ErMv6IjWeQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@nx/devkit": "21.2.3", + "@nx/devkit": "21.3.5", "@zkochan/js-yaml": "0.0.7", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "21.2.3", + "nx": "21.3.5", "picomatch": "4.0.2", "tslib": "^2.3.0", "yargs-parser": "21.1.1" } }, + "node_modules/@nx/workspace/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@nx/workspace/node_modules/@nx/devkit": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.2.3.tgz", - "integrity": "sha512-H5Hk0qeZwqhxQmqcWaLpMc+otU4TroUzDYoV6kFpZdvcwGnXQKHCuGzZoI18kh9wPXvKFmb1BWmr9as3lHUw3Q==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.3.5.tgz", + "integrity": "sha512-YTmjD+kqDUapfcV37IgVddLZL4oOoFVXO0dFKsYSkx/FNmhccTbeXxgsdcyRTJY6gCwsFJ+4X0aIv8NxebWYaw==", "dev": true, "license": "MIT", "peer": true, @@ -7111,13 +7147,13 @@ "yargs-parser": "21.1.1" }, "peerDependencies": { - "nx": "21.2.3" + "nx": "21.3.5" } }, "node_modules/@nx/workspace/node_modules/@nx/nx-darwin-arm64": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.2.3.tgz", - "integrity": "sha512-5WgOjoX4vqG286A8abYoLCScA2ZF5af/2ZBjaM5EHypgbJLGQuMcP2ahzX66FYohT4wdAej1D0ILkEax71fAKw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.3.5.tgz", + "integrity": "sha512-QKWtKjIdYS2foDo/4ojvVr5NjrtY8IcHPyFFATAk7v5BWe2tEGh6pPDj8GRqvSqZPWSBZNDcJ6efovJyka6yzw==", "cpu": [ "arm64" ], @@ -7130,9 +7166,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-darwin-x64": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.2.3.tgz", - "integrity": "sha512-aSaK8Ic9nHTwSuNZZtaKCPIXgD6+Ss9UwkNMIXPLYiYLF+EdSDORHnHutmajZZ8HakoWCQPWvxfWv30zre6iqw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.3.5.tgz", + "integrity": "sha512-X06eb6lzuln9ZHh7L8430s4Cc7pi5mihU3IlJsN0rbgdCp2PMlOsJ/8P/zw/DBwq6qmTuVwZ8Xk01VOVtWZT0w==", "cpu": [ "x64" ], @@ -7145,9 +7181,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-freebsd-x64": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.2.3.tgz", - "integrity": "sha512-hFSbtaYM1gL+XQq88CkmwqeeabmFsLjpsBF+HFIv1UMAjb02ObrYHVVICmmin5c1NkBsEJcQzh3mf8PBSOHW8A==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.3.5.tgz", + "integrity": "sha512-+ZWvAn/1UD4Wwduv5nhcXWIUcev1JDEEsNBesGvFWS4c19dBk8vaUpUv3YQspwRUgAGdfYw1CWqDDOYvGFrZ3w==", "cpu": [ "x64" ], @@ -7160,9 +7196,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.2.3.tgz", - "integrity": "sha512-yRzt8dLwTpRP7655We9/ol+Ol+n52R9wsRRnxJFdWHyLrHguZF0dqiZ5rAFFzyvywaDP6CRoPuS7wqFT7K14bw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.3.5.tgz", + "integrity": "sha512-JeSw0/WdVo4AxCKWRrH686rxu9jHzKnl/IY1m+/jiIPq2yUPUUxqSj9+Xesvp9K3plAmZFlSulbfd8BX15/cEw==", "cpu": [ "arm" ], @@ -7175,9 +7211,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-linux-arm64-gnu": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.2.3.tgz", - "integrity": "sha512-5u8mmUogvrNn1xlJk8Y6AJg/g1h2bKxYSyWfxR2mazKj5wI/VgbHuxHAgMXB7WDW2tK5bEcrUTvO8V0DjZQhNA==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.3.5.tgz", + "integrity": "sha512-TlMhwwj75cP67m/qYuSAmvdMMW6oASELo3uxRJ9PbpyTRiCmQZoqjZqALL/48rAEk1Ig69o83RI4pIMRkGMQUw==", "cpu": [ "arm64" ], @@ -7190,9 +7226,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-linux-arm64-musl": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.2.3.tgz", - "integrity": "sha512-4huuq2iuCBOWmJQw60gk5g3yjeHxFzwdDZJPM0680fZ7Pa/haPwamkR6kE2U6aFtFMhi1QVGPEoj4v4vE4ZS5g==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.3.5.tgz", + "integrity": "sha512-GZBMLTJFP9H9/o26jSfqxwlBoZ4c0FNBl8rJ3tOC9jCNHn7wcMJKaVbp0dUKDgUtyzATa5poJsdClG84zMnHdA==", "cpu": [ "arm64" ], @@ -7205,9 +7241,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-linux-x64-gnu": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.2.3.tgz", - "integrity": "sha512-qWpJXpF8vjOrZTkgSC8kQAnIh0pIFbsisePicYWj5U9szJYyTUvVbjMAvdUPH4Z3bnrUtt+nzf9mpFCJRLjsOQ==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.3.5.tgz", + "integrity": "sha512-rNZ2X+h3AbF+vM3zKcpv122Eu5fyYS0079iNiYAHNknwLPJUlvDEQU3nu6ts8Hw1zSjxzibHbWZghSfZRAIHZA==", "cpu": [ "x64" ], @@ -7220,9 +7256,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-linux-x64-musl": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.2.3.tgz", - "integrity": "sha512-JZHlovF9uzvN3blImysYJmG90/8ookr3jOmEFxmP4RfMUl6EdN9yBLBdx0zIG2ulh7+WQrR3eQ1qrmsWFb6oiw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.3.5.tgz", + "integrity": "sha512-LtsDUhrH0sVl7gBSsJ+cy/cKH71PorysOhJqTrE7Z5UVpnWu+1djiOsbEDRAheyUf80QfFA8xC239Pi+QG3T/w==", "cpu": [ "x64" ], @@ -7235,9 +7271,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-win32-arm64-msvc": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.2.3.tgz", - "integrity": "sha512-8Q1ljgFle6F2ZGSe6dLBItSdvYXjO0n2ovZI0zIih9+5OGLdN8wf6iONQJT7he2YST1dowIDPNWdeKiuOzPo6w==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.3.5.tgz", + "integrity": "sha512-lmhzLTVXzQNa0em6v0gBCfswpD5vdgtcjUxr5flR6ylWYo0hVYD4w/EqoymqXq0rU94lx28lksmKX0vhNH5aiw==", "cpu": [ "arm64" ], @@ -7250,9 +7286,9 @@ "peer": true }, "node_modules/@nx/workspace/node_modules/@nx/nx-win32-x64-msvc": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.2.3.tgz", - "integrity": "sha512-qJpHIZU/D48+EZ2bH02/LIFIkANYryGbcbNQUqC+pYA8ZPCU0wMqZVn4UcNMoI9K4YtXe/SvSBdjiObDuRb8yw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.3.5.tgz", + "integrity": "sha512-8ljAlmV96WUK7NbXCL96HDxP7Qm6MDKgz/8mD4XtMJIWvZ098VDv4+1Ce2lK366grQRmNQfYVUuhwPppnJfqug==", "cpu": [ "x64" ], @@ -7264,6 +7300,28 @@ ], "peer": true }, + "node_modules/@nx/workspace/node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@nx/workspace/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@nx/workspace/node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -7372,6 +7430,23 @@ "node": ">=8" } }, + "node_modules/@nx/workspace/node_modules/jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@nx/workspace/node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -7399,9 +7474,9 @@ } }, "node_modules/@nx/workspace/node_modules/nx": { - "version": "21.2.3", - "resolved": "https://registry.npmjs.org/nx/-/nx-21.2.3.tgz", - "integrity": "sha512-2wL/2fSmIbRWn6zXaQ/g3kj5DfEaTw/aJkPr6ozJh8BUq5iYKE+tS9oh0PjsVVwN6Pybe80Lu+mn9RgWyeV3xw==", + "version": "21.3.5", + "resolved": "https://registry.npmjs.org/nx/-/nx-21.3.5.tgz", + "integrity": "sha512-iRAO7D7SkjhIM6y5xH8GO+ojTJB2QSIzG2xNBgbRwuTV6AxLBkq6sU5hFA0wNzD/LncUeEoJmRtHfbGXfQQORQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7423,7 +7498,7 @@ "flat": "^5.0.2", "front-matter": "^4.0.2", "ignore": "^5.0.4", - "jest-diff": "^29.4.1", + "jest-diff": "^30.0.2", "jsonc-parser": "3.2.0", "lines-and-columns": "2.0.3", "minimatch": "9.0.3", @@ -7448,16 +7523,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "21.2.3", - "@nx/nx-darwin-x64": "21.2.3", - "@nx/nx-freebsd-x64": "21.2.3", - "@nx/nx-linux-arm-gnueabihf": "21.2.3", - "@nx/nx-linux-arm64-gnu": "21.2.3", - "@nx/nx-linux-arm64-musl": "21.2.3", - "@nx/nx-linux-x64-gnu": "21.2.3", - "@nx/nx-linux-x64-musl": "21.2.3", - "@nx/nx-win32-arm64-msvc": "21.2.3", - "@nx/nx-win32-x64-msvc": "21.2.3" + "@nx/nx-darwin-arm64": "21.3.5", + "@nx/nx-darwin-x64": "21.3.5", + "@nx/nx-freebsd-x64": "21.3.5", + "@nx/nx-linux-arm-gnueabihf": "21.3.5", + "@nx/nx-linux-arm64-gnu": "21.3.5", + "@nx/nx-linux-arm64-musl": "21.3.5", + "@nx/nx-linux-x64-gnu": "21.3.5", + "@nx/nx-linux-x64-musl": "21.3.5", + "@nx/nx-win32-arm64-msvc": "21.3.5", + "@nx/nx-win32-x64-msvc": "21.3.5" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -7515,6 +7590,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@nx/workspace/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@nx/workspace/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -8595,6 +8686,32 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@types/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -8627,9 +8744,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", - "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dev": true, "license": "MIT", "dependencies": { @@ -12712,9 +12829,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.182", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz", - "integrity": "sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==", + "version": "1.5.190", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz", + "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==", "license": "ISC" }, "node_modules/electron-winstaller": { @@ -12756,9 +12873,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.7.tgz", - "integrity": "sha512-1GM9z6BJOv86qkPvzh2i6VW5+VVrXxCLknfmTkWEqz+6DqosiY28XUWCTmBcJ0ACzKqx/iwdIREfo1fwExIlkA==", + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", "dev": true, "license": "MIT", "dependencies": { @@ -14119,9 +14236,9 @@ "license": "ISC" }, "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", "dev": true, "license": "Unlicense" }, @@ -16343,9 +16460,9 @@ } }, "node_modules/jest-preset-angular": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.6.0.tgz", - "integrity": "sha512-LGSKLCsUhtrs2dw6f7ega/HOS8/Ni/1gV+oXmxPHmJDLHFpM6cI78Monmz8Z1P87a/A4OwnKilxgPRr+6Pzmgg==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.6.1.tgz", + "integrity": "sha512-7q5x42wKrsF2ykOwGVzcXpr9p1X4FQJMU/DnH1tpvCmeOm5XqENdwD/xDZug+nP6G8SJPdioauwdsK/PMY/MpQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16825,6 +16942,19 @@ "node": ">= 6.0.0" } }, + "node_modules/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/jsdom/node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -16854,6 +16984,19 @@ "node": ">= 6" } }, + "node_modules/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -18421,9 +18564,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", - "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", "dev": true, "license": "MIT", "optional": true, @@ -20266,11 +20409,12 @@ } }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "entities": "^6.0.0" }, @@ -20306,6 +20450,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5-sax-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", @@ -20319,12 +20476,39 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-sax-parser/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -24384,13 +24568,13 @@ } }, "node_modules/wait-on": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.3.tgz", - "integrity": "sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.4.tgz", + "integrity": "sha512-8f9LugAGo4PSc0aLbpKVCVtzayd36sSCp4WLpVngkYq6PK87H79zt77/tlCU6eKCLqR46iFvcl0PU5f+DmtkwA==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.8.2", + "axios": "^1.11.0", "joi": "^17.13.3", "lodash": "^4.17.21", "minimist": "^1.2.8", diff --git a/gui-js/package.json b/gui-js/package.json index b017769db..50206de02 100644 --- a/gui-js/package.json +++ b/gui-js/package.json @@ -1,6 +1,6 @@ { "name": "ravel", - "version":"3.19.0-beta.1", + "version":"3.19.0-beta.2", "author": "High Performance Coders", "description": "Graphical dynamical systems simulator oriented towards economics", "repository": { diff --git a/loadDb.py b/loadDb.py new file mode 100644 index 000000000..1a06d6b19 --- /dev/null +++ b/loadDb.py @@ -0,0 +1,53 @@ +import sys +import json +sys.path.insert(0,'.') +from pyminsky import minsky, DataSpec +minsky.databaseIngestor.db.connect("sqlite3","db=citibike.sqlite","citibike") +# sys.argv[0] is this script name +filenames=sys.argv[1:] + +# set up spec for Citibike +spec=minsky.databaseIngestor.spec + +spec.dataRowOffset(1) +spec.dataCols([0]) +spec.dimensionCols([1,4,8,11,12,13,14]) +spec.dimensionNames([ + "tripduration", + "starttime", + "stoptime", + "start station id", + "start station name", + "start station latitude", + "start station longitude", + "end station id", + "end station name", + "end station latitude", + "end station longitude", + "bikeid", + "usertype", + "birth year", + "gender" +]) + +spec.dimensions([ + {"type":"value"}, + {"type":"time"}, + {"type":"time"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"string"}, + {"type":"time"}, + {"type":"string"} +]) +spec.dontFail(True) + +minsky.databaseIngestor.db.createTable(filenames[0]) +minsky.databaseIngestor.importFromCSV(filenames) diff --git a/minskyVersion.h b/minskyVersion.h index a21415c44..f482a5295 100644 --- a/minskyVersion.h +++ b/minskyVersion.h @@ -1 +1 @@ -#define MINSKY_VERSION "3.19.0-beta.1" +#define MINSKY_VERSION "3.19.0-beta.2" diff --git a/model/CSVDialog.cc b/model/CSVDialog.cc index 27f2394cd..5a686d02b 100644 --- a/model/CSVDialog.cc +++ b/model/CSVDialog.cc @@ -148,156 +148,6 @@ vector> parseLines(const Parser& parser, const vector& li return r; } -namespace -{ - struct CroppedPango: public Pango - { - cairo_t* cairo; - double w, x=0, y=0; - CroppedPango(cairo_t* cairo, double width): Pango(cairo), cairo(cairo), w(width) {} - void setxy(double xx, double yy) {x=xx; y=yy;} - void show() { - const CairoSave cs(cairo); - cairo_rectangle(cairo,x,y,w,height()); - cairo_clip(cairo); - cairo_move_to(cairo,x,y); - Pango::show(); - } - }; -} - -bool CSVDialog::redraw(int, int, int, int) -{ - cairo_t* cairo=surface->cairo(); - rowHeight=15; - vector> parsedLines=parseLines(); - - // LHS row labels - { - Pango pango(cairo); - pango.setText("Dimension"); - cairo_move_to(cairo,xoffs-pango.width()-5,0); - pango.show(); - pango.setText("Type"); - cairo_move_to(cairo,xoffs-pango.width()-5,rowHeight); - pango.show(); - pango.setText("Format"); - cairo_move_to(cairo,xoffs-pango.width()-5,2*rowHeight); - pango.show(); - if (flashNameRow) - pango.setMarkup("Name"); - else - pango.setText("Name"); - cairo_move_to(cairo,xoffs-pango.width()-5,3*rowHeight); - pango.show(); - pango.setText("Header"); - cairo_move_to(cairo,xoffs-pango.width()-5,(4+spec.headerRow)*rowHeight); - pango.show(); - - } - - CroppedPango pango(cairo, colWidth); - pango.setFontSize(0.8*rowHeight); - - set done; - double x=xoffs, y=0; - size_t col=0; - for (; done.size()(spec.dimensions[col].type)); - pango.setxy(x,y); - pango.show(); - } - y+=rowHeight; - if (spec.dimensionCols.contains(col) && colcairo(),0,0.7,0); - else - cairo_set_source_rgb(surface->cairo(),0,0,1); - else if (rowcairo(),1,0,0); - else if (colcairo(),0,0,1); - pango.show(); - } - else - done.insert(row); - y+=rowHeight; - } - { - const CairoSave cs(cairo); - cairo_set_source_rgb(cairo,.5,.5,.5); - cairo_move_to(cairo,x-2.5,0); - cairo_rel_line_to(cairo,0,(parsedLines.size()+4)*rowHeight); - cairo_stroke(cairo); - } - x+=colWidth+5; - y=0; - } - m_tableWidth=(col-1)*(colWidth+5); - for (size_t row=0; row> CSVDialog::parseLines(size_t maxColumn) { vector> parsedLines; diff --git a/model/CSVDialog.h b/model/CSVDialog.h index 1e3775c87..ef56d6f39 100644 --- a/model/CSVDialog.h +++ b/model/CSVDialog.h @@ -32,24 +32,16 @@ namespace minsky { - class CSVDialog: public RenderNativeWindow + class CSVDialog { std::vector initialLines; ///< initial lines of file - double rowHeight=0; - double m_tableWidth; CLASSDESC_ACCESS(DataSpec); - bool redraw(int, int, int width, int height) override; public: static const unsigned numInitialLines=100; - double xoffs=80; - double colWidth=50; - bool flashNameRow=false; DataSpec spec; /// filename, or web url std::string url; - /// width of table (in pixels) - double tableWidth() const {return m_tableWidth;} /// loads an initial sequence of lines from \a url. If fname /// contains "://", is is treated as a URL, and downloaded from @@ -60,11 +52,6 @@ namespace minsky /// common implementation of loading the initial sequence of lines void loadFileFromName(const std::string& fname); void reportFromFile(const std::string& input, const std::string& output) const; - void requestRedraw() {if (surface.get()) surface->requestRedraw();} - /// return column mouse is over - std::size_t columnOver(double x) const; - /// return row mouse is over - std::size_t rowOver(double y) const; std::vector > parseLines(size_t maxColumn=std::numeric_limits::max()); /// populate all column names from the headers row void populateHeaders(); @@ -76,6 +63,8 @@ namespace minsky /// could slightly underestimate the value, and is never less than /// 1, even for empty columns std::vector correctedUniqueValues(); + /// import names CSV files using spec above + virtual void importFromCSV(const std::vector& filenames)=0; }; bool isNumerical(const std::string& s); diff --git a/model/canvas.cc b/model/canvas.cc index 84323ebe9..b9af9d02d 100644 --- a/model/canvas.cc +++ b/model/canvas.cc @@ -146,9 +146,9 @@ namespace minsky if (itemFocus && clickType==ClickType::inItem) { - itemFocus->onMouseUp(x,y); + bool requestReset=itemFocus->onMouseUp(x,y); itemFocus.reset(); // prevent spurious mousemove events being processed - minsky().requestReset(); + if (requestReset) minsky().requestReset(); } if (fromPort.get()) { diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index 9f24e21c9..ad4def455 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -609,8 +609,11 @@ namespace minsky void GodleyIcon::onMouseDown(float x, float y) {if (m_editorMode) editor.mouseDown(toEditorX(x),toEditorY(y));} - void GodleyIcon::onMouseUp(float x, float y) - {if (m_editorMode) editor.mouseUp(toEditorX(x),toEditorY(y));} + bool GodleyIcon::onMouseUp(float x, float y) + { + if (m_editorMode) editor.mouseUp(toEditorX(x),toEditorY(y)); + return m_editorMode; + } bool GodleyIcon::onMouseMotion(float x, float y) { diff --git a/model/godleyIcon.h b/model/godleyIcon.h index 4779b36a1..a16c588e4 100644 --- a/model/godleyIcon.h +++ b/model/godleyIcon.h @@ -151,7 +151,7 @@ namespace minsky void insertControlled(Selection& selection) override; void onMouseDown(float, float) override; - void onMouseUp(float, float) override; + bool onMouseUp(float, float) override; bool onMouseMotion(float, float) override; bool onMouseOver(float, float) override; void onMouseLeave() override; diff --git a/model/item.h b/model/item.h index 6e97fb667..7d8961cfe 100644 --- a/model/item.h +++ b/model/item.h @@ -258,8 +258,8 @@ namespace minsky virtual bool onItem(float x, float y) const; /// respond to mouse down events virtual void onMouseDown(float x, float y) {} - /// respond to mouse up events - virtual void onMouseUp(float x, float y) {} + /// respond to mouse up events. Return true if model needs to be reset. + virtual bool onMouseUp(float x, float y) {return false;} /// respond to mouse motion events with button pressed /// @return true if it needs to be rerendered virtual bool onMouseMotion(float x, float y) {return false;} diff --git a/model/minsky.cc b/model/minsky.cc index cb392a4f3..1d45ca8e1 100644 --- a/model/minsky.cc +++ b/model/minsky.cc @@ -34,6 +34,12 @@ #include "minskyVersion.h" +#include "CSVTools.xcd" +#include "databaseIngestor.cd" +#include "databaseIngestor.rcd" +#include "databaseIngestor.xcd" +#include "dynamicRavelCAPI.rcd" +#include "dynamicRavelCAPI.xcd" #include "fontDisplay.rcd" #include "minsky.rcd" #include "minsky.xcd" @@ -203,6 +209,7 @@ namespace minsky VariablePtr Minsky::definingVar(const string& valueId) const { + if (!model) return {}; return dynamic_pointer_cast (model->findAny(&Group::items, [&](const ItemPtr& x) { auto v=x->variableCast(); @@ -1828,3 +1835,4 @@ CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::Minsky); CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(classdesc::Signature); CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(classdesc::PolyRESTProcessBase); CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::CallableFunction); +CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(ravelCAPI::Database); diff --git a/model/minsky.h b/model/minsky.h index ab4af6ce8..27c64937d 100644 --- a/model/minsky.h +++ b/model/minsky.h @@ -26,7 +26,9 @@ #include "cairoItems.h" #include "canvas.h" #include "clipboard.h" +#include "databaseIngestor.h" #include "dimension.h" +#include "dynamicRavelCAPI.h" #include "evalOp.h" #include "equationDisplay.h" #include "equations.h" @@ -142,7 +144,8 @@ namespace minsky FontDisplay fontSampler; PhillipsDiagram phillipsDiagram; std::vector publicationTabs; - + DatabaseIngestor databaseIngestor; + void addNewPublicationTab(const std::string& name) {publicationTabs.emplace_back(name);} void addCanvasItemToPublicationTab(size_t i) { if (canvas.item && inumWires(); // only reset if input port connected. Don't if sourced from database } bool Ravel::onMouseMotion(float xx, float yy) { @@ -275,6 +276,30 @@ namespace minsky minsky().requestReset(); } + void Ravel::initRavelFromDb() + { + if (db && m_ports[1]->wires().empty()) + { + minsky().flags&=~Minsky::reset_needed; //disable resetting until user gets a chance to manipulate ravel + db.fullHypercube(wrappedRavel); + } + } + + vector Ravel::createChain(const TensorPtr& arg) + { + if (arg) + { + populateHypercube(arg->hypercube()); + return ravel::createRavelChain(getState(), arg); + } + pack_t buf; buf< allSliceLabelsImpl(int axis, ravel::HandleSort::Order) const; ravelCAPI::Ravel wrappedRavel; + /// serialised copy of previous state for caching purposes + classdesc::pack_t lastState; + civita::TensorPtr cachedDbResult; ///< cache of database query result ravel::Op::ReductionOp m_nextReduction=ravel::Op::sum; public: static SVGRenderer svgRenderer; ///< SVG icon to display when not in editor mode RavelPopup popup; ///< popup Ravel control window bool flipped=false; + ravelCAPI::Database db; ///< backing database + Ravel(); // copy operations needed for clone, but not really used for now // define them as empty operations to prevent double frees if accidentally used @@ -108,7 +113,7 @@ namespace minsky void resize(const LassoBox&) override; bool inItem(float x, float y) const override; void onMouseDown(float x, float y) override; - void onMouseUp(float x, float y) override; + bool onMouseUp(float x, float y) override; bool onMouseMotion(float x, float y) override; bool onMouseOver(float x, float y) override; void onMouseLeave() override {wrappedRavel.onMouseLeave();} @@ -135,6 +140,14 @@ namespace minsky /// collapse all handles (applying nextReduction op where appropriate) /// @param collapse if true, uncollapse if false void collapseAllHandles(bool collapse=true); + + /// if connected to a database, initialise the ravel state from it + void initRavelFromDb(); + + /// make a tensor expression corresponding to the state of this + /// Ravel, applied to \a arg. If \a arg is nullptr, then the + /// returned expression is extracted from the database. + std::vector createChain(const TensorPtr& arg); /// enable/disable calipers on currently selected handle bool displayFilterCaliper() const; diff --git a/model/variable.cc b/model/variable.cc index 3f098b847..7f2b888e3 100644 --- a/model/variable.cc +++ b/model/variable.cc @@ -464,13 +464,8 @@ void VariableBase::exportAsCSV(const std::string& filename, bool tabular) const void VariableBase::importFromCSV(const vector& filenames, const DataSpecSchema& spec) const { if (auto v=vValue()) { - v->csvDialog.spec=spec; - if (!filenames.empty()) - v->csvDialog.url=filenames[0]; - loadValueFromCSVFile(*v, filenames, v->csvDialog.spec); - minsky().populateMissingDimensionsFromVariable(*v); - if (!v->hypercube().dimsAreDistinct()) - throw_error("Axes of imported data should all have distinct names"); + v->spec=spec; + v->importFromCSV(filenames); } } @@ -478,15 +473,8 @@ void VariableBase::importFromCSV(const vector& filenames, const DataSpec void VariableBase::reloadCSV() { if (auto v=vValue()) - if (!v->csvDialog.url.empty()) - loadValueFromCSVFile(*v, {v->csvDialog.url}, v->csvDialog.spec); -} - - -void VariableBase::destroyFrame() -{ - if (auto vv=vValue()) - vv->csvDialog.destroyFrame(); + if (!v->url.empty()) + loadValueFromCSVFile(*v, {v->url}, v->spec); } void VariableBase::insertControlled(Selection& selection) diff --git a/model/variable.h b/model/variable.h index 2ac54e68d..033b85c89 100644 --- a/model/variable.h +++ b/model/variable.h @@ -219,15 +219,13 @@ namespace minsky /// export this variable as a CSV file /// @param tabular - if true, the longest dimension is split across columns as a horizontal dimension void exportAsCSV(const std::string& filename, bool tabular) const; + /// \deprecated To be removed in version 4. /// import CSV files, using \a spec void importFromCSV(const std::vector& filenames, const DataSpecSchema& spec) const; /// reload CSV file if previously imported void reloadCSV(); - /// clean up popup window structures on window close - void destroyFrame() override; - bool miniPlotEnabled() const {return bool(miniPlot);} bool miniPlotEnabled(bool); void resetMiniPlot(); diff --git a/schema/dataSpecSchema.h b/schema/dataSpecSchema.h index 07cad917d..6b4219687 100644 --- a/schema/dataSpecSchema.h +++ b/schema/dataSpecSchema.h @@ -19,20 +19,20 @@ #ifndef DATASPECSCHEMA_H #define DATASPECSCHEMA_H +#include "CSVTools.h" +#include "ravelState.h" #include #include #include namespace minsky { - struct DataSpecSchema + struct DataSpecSchema: public ravel::CSVSpec { // these fields are only used for persistence. Need to be handled specially within schema code std::size_t dataRowOffset, dataColOffset; std::size_t numCols=0; ///< number of columns in CSV. Must be > dataColOffset - // NB escape character might be backslash ('\\'), but not usually used in CSV files, so set to nul. - char separator=',', quote='"', escape='\0', decSeparator='.'; bool mergeDelimiters=false; bool counter=false; ///< count data items, not read their values bool dontFail=false; ///< do not throw an error on corrupt data diff --git a/schema/schema3.cc b/schema/schema3.cc index 4ced0ee73..d2c204c1b 100644 --- a/schema/schema3.cc +++ b/schema/schema3.cc @@ -18,6 +18,8 @@ */ #include "dataOp.h" #include "schema3.h" +#include "CSVTools.xcd" +#include "dynamicRavelCAPI.xcd" #include "sheet.h" #include "userFunction.h" #include "minsky_epilogue.h" @@ -192,6 +194,9 @@ namespace schema3 items.back().editorMode=r->editorMode(); } if (r->flipped) items.back().rotation=180; + auto dbConnection=r->db.connection(); + if (!dbConnection.dbType.empty()) + items.back().dbConnection=dbConnection; } if (auto* l=dynamic_cast(i)) if (l->locked()) @@ -499,6 +504,11 @@ namespace schema3 if (auto* x1=dynamic_cast(&x)) { + if (y.dbConnection) + { + x1->db.connect(y.dbConnection->dbType,y.dbConnection->connection,y.dbConnection->table); + x1->initRavelFromDb(); + } if (y.ravelState) { x1->applyState(y.ravelState->toRavelRavelState()); @@ -817,9 +827,9 @@ namespace schema3 if (auto val=v->vValue()) { if (i.second.csvDataSpec) - val->csvDialog.spec=*i.second.csvDataSpec; + val->spec=*i.second.csvDataSpec; if (i.second.url) - val->csvDialog.url=*i.second.url; + val->url=*i.second.url; if (i.second.tensorData) { auto buf=minsky::decode(*i.second.tensorData); diff --git a/schema/schema3.h b/schema/schema3.h index e0ed588f2..35b461797 100644 --- a/schema/schema3.h +++ b/schema/schema3.h @@ -113,8 +113,9 @@ namespace schema3 Optional csvDataSpec; //CSV import data Optional> dataOpData; Optional expression; // userfunction - Optional filename; + Optional filename; Optional ravelState; + Optional dbConnection; Optional lockGroup; Optional> lockGroupHandles; Optional dimensions; @@ -147,9 +148,9 @@ namespace schema3 if (auto vv=v.vValue()) { units=vv->units.str(); - if (!vv->csvDialog.url.empty()) - csvDataSpec=vv->csvDialog.spec.toSchema(); - url=vv->csvDialog.url; + if (!vv->url.empty()) + csvDataSpec=vv->spec.toSchema(); + url=vv->url; } } Item(int id, const minsky::OperationBase& o, const std::vector& ports): diff --git a/test/00/RESTService.sh b/test/00/RESTService.sh index 9063f6007..5bfae404c 100755 --- a/test/00/RESTService.sh +++ b/test/00/RESTService.sh @@ -9,7 +9,7 @@ EOF if [ $? -ne 0 ]; then fail; fi cat >reference <{"csvDialog":{"backgroundColour":{"a":1,"b":0.80000000000000004,"g":0.80000000000000004,"r":0.80000000000000004},"colWidth":50,"flashNameRow":false,"item":{},"spec":{"counter":false,"dataColOffset":0,"dataCols":[],"dataRowOffset":0,"decSeparator":".","dimensionCols":[],"dimensionNames":[],"dimensions":[],"dontFail":false,"duplicateKeyAction":"throwException","escape":"\u0000","headerRow":0,"horizontalDimName":"?","horizontalDimension":{"type":"string","units":""},"maxColumn":1000,"mergeDelimiters":false,"missingValue":NaN,"numCols":0,"quote":"\"","separator":","},"url":"","wire":{},"xoffs":80},"detailedText":"","enableSlider":true,"godleyOverridden":false,"name":"constant:one","rhs":{},"sliderMax":-1.7976931348623157e+308,"sliderMin":1.7976931348623157e+308,"sliderStep":0,"sliderStepRel":false,"tensorInit":{},"tooltip":"","units":[],"unitsCached":false} +/minsky/variableValues/@elem/"constant:one"=>{"detailedText":"","enableSlider":true,"godleyOverridden":false,"name":"constant:one","rhs":{},"sliderMax":-1.7976931348623157e+308,"sliderMin":1.7976931348623157e+308,"sliderStep":0,"sliderStepRel":false,"spec":{"counter":false,"dataColOffset":0,"dataCols":[],"dataRowOffset":0,"decSeparator":".","dimensionCols":[],"dimensionNames":[],"dimensions":[],"dontFail":false,"duplicateKeyAction":"throwException","escape":"\u0000","headerRow":0,"horizontalDimName":"?","horizontalDimension":{"type":"string","units":""},"maxColumn":1000,"mergeDelimiters":false,"missingValue":NaN,"numCols":0,"quote":"\"","separator":","},"tensorInit":{},"tooltip":"","units":[],"unitsCached":false,"url":""} EOF diff -q -w output reference diff --git a/test/00/importCSV.sh b/test/00/importCSV.sh index 503646c0f..a73b8230a 100755 --- a/test/00/importCSV.sh +++ b/test/00/importCSV.sh @@ -7,7 +7,7 @@ cat >input.py <&2 - cd $here - chmod -R u+w $tmp - rm -rf $tmp - exit 1 -} - -pass() -{ - echo "PASSED" 1>&2 - cd $here - chmod -R u+w $tmp - rm -rf $tmp - exit 0 -} - -trap "fail" 1 2 3 15 +. test/common-test.sh # check description/tooltip functionality cat >input.py <&) override {} + }; + + TEST_FIXTURE(TestCSVDialog,classifyColumns) { string input="10,2022/10/2,hello,\n" "'5,150,000','2023/1/3','foo bar',\n" @@ -555,10 +560,7 @@ SUITE(CSVParser) spec.guessFromStream(f); } VariableValue newV(VariableType::flow); - { - ifstream f("tmp.csv"); - loadValueFromCSVFile(newV,f,spec); - } + loadValueFromCSVFile(newV,{"tmp.csv"},spec); CHECK(newV.hypercube().xvectors==v.hypercube().xvectors); CHECK_EQUAL(v.size(), newV.size()); diff --git a/test/testCSVParserGemini.cc b/test/testCSVParserGemini.cc index 450a73bdb..ce7d4802d 100644 --- a/test/testCSVParserGemini.cc +++ b/test/testCSVParserGemini.cc @@ -42,10 +42,15 @@ class CSVParserTest : public ::testing::TestWithParam { DataSpec spec; }; +struct TestCSVDialog: CSVDialog +{ + void importFromCSV(const std::vector&) override {} +}; + // Fixture for CSVDialog tests class CSVDialogTest : public ::testing::Test { protected: - CSVDialog dialog; + TestCSVDialog dialog; DataSpec& spec = dialog.spec; // Access DataSpec through CSVDialog string url; diff --git a/test/testSaver.cc b/test/testSaver.cc index d7445dc7e..c47b817e6 100644 --- a/test/testSaver.cc +++ b/test/testSaver.cc @@ -19,6 +19,8 @@ #include "saver.h" #include "schema3.h" +#include "CSVTools.xcd" +#include "dynamicRavelCAPI.xcd" #include "minsky_epilogue.h" #undef True #include