Web: Improve file dialog.
parent
4981570844
commit
1603402df2
|
@ -345,9 +345,15 @@ if(ENABLE_GUI)
|
|||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.js
|
||||
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js
|
||||
COMMENT "Copying UI script"
|
||||
COMMENT "Copying UI script solvespaceui.js"
|
||||
VERBATIM)
|
||||
add_custom_command(
|
||||
TARGET solvespace POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/filemanagerui.js
|
||||
${EXECUTABLE_OUTPUT_PATH}/filemanagerui.js
|
||||
COMMENT "Copying UI script filemanagerui.sj"
|
||||
VERBATIM)
|
||||
|
||||
else()
|
||||
target_sources(solvespace PRIVATE
|
||||
platform/guigtk.cpp)
|
||||
|
|
|
@ -1229,29 +1229,28 @@ public:
|
|||
auto it = std::remove(dialogsOnScreen.begin(), dialogsOnScreen.end(),
|
||||
shared_from_this());
|
||||
dialogsOnScreen.erase(it);
|
||||
};
|
||||
responseFuncs.push_back(responseFunc);
|
||||
htmlButton.call<void>("addEventListener", val("trigger"), Wrap(&responseFuncs.back()));
|
||||
|
||||
static std::function<void()> updateShowFlagFunc = [this] {
|
||||
|
||||
this->is_shown = false;
|
||||
};
|
||||
htmlButton.call<void>("addEventListener", val("trigger"), Wrap(&updateShowFlagFunc));
|
||||
if (responseFuncs.size() == 0) {
|
||||
//FIXME(emscripten): I don't know why but the item in the head of responseFuncs cannot call.
|
||||
// So add dummy item
|
||||
responseFuncs.push_back([]{ });
|
||||
}
|
||||
responseFuncs.push_back(responseFunc);
|
||||
std::function<void()>* callback = &responseFuncs.back();
|
||||
htmlButton.call<void>("addEventListener", val("trigger"), Wrap(callback));
|
||||
|
||||
htmlButtons.call<void>("appendChild", htmlButton);
|
||||
}
|
||||
|
||||
Response RunModal() {
|
||||
// ssassert(false, "RunModal not supported on Emscripten");
|
||||
// dbp("MessageDialog::RunModal() called.");
|
||||
this->ShowModal();
|
||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||
while (true) {
|
||||
if (this->is_shown) {
|
||||
// dbp("MessageDialog::RunModal(): is_shown == true");
|
||||
emscripten_sleep(2000);
|
||||
emscripten_sleep(50);
|
||||
} else {
|
||||
// dbp("MessageDialog::RunModal(): break due to is_shown == false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1260,7 +1259,6 @@ public:
|
|||
return this->latestResponse;
|
||||
} else {
|
||||
// FIXME(emscripten):
|
||||
// dbp("MessageDialog::RunModal(): Cannot get Response.");
|
||||
return this->latestResponse;
|
||||
}
|
||||
}
|
||||
|
@ -1281,161 +1279,97 @@ MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
|||
// File dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class FileOpenDialogImplHtml : public FileDialog {
|
||||
// In emscripten psuedo filesystem, all userdata will be stored in this directory.
|
||||
static std::string basePathInFilesystem = "/data/";
|
||||
|
||||
|
||||
/* FileDialog that can open, save and browse. Also refer `src/platform/html/filemanagerui.js`.
|
||||
*/
|
||||
class FileDialogImplHtml : public FileDialog {
|
||||
public:
|
||||
|
||||
enum class Modes {
|
||||
OPEN = 0,
|
||||
SAVE,
|
||||
BROWSER
|
||||
};
|
||||
|
||||
Modes mode;
|
||||
|
||||
std::string title;
|
||||
std::string filename;
|
||||
std::string filters;
|
||||
|
||||
emscripten::val fileUploadHelper;
|
||||
val jsFileManagerUI;
|
||||
|
||||
FileOpenDialogImplHtml() {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::FileOpenDialogImplHtml()");
|
||||
// FIXME(emscripten): workaround. following code raises "constructor is not a constructor" exception.
|
||||
// val fuh = val::global("FileUploadHelper");
|
||||
// this->fileUploadHelper = fuh.new_();
|
||||
this->fileUploadHelper = val::global().call<val>("createFileUploadHelperInstance");
|
||||
dbp("FileOpenDialogImplHtml::FileOpenDialogImplHtml() OK.");
|
||||
FileDialogImplHtml(Modes mode) {
|
||||
dbp("FileDialogImplHtml::FileDialogImplHtml()");
|
||||
val fileManagerUIClass = val::global("window")["FileManagerUI"];
|
||||
val dialogModeValue;
|
||||
this->mode = mode;
|
||||
if (mode == Modes::OPEN) {
|
||||
dialogModeValue = val(0);
|
||||
} else if (mode == Modes::SAVE) {
|
||||
dialogModeValue = val(1);
|
||||
} else {
|
||||
dialogModeValue = val(2);
|
||||
}
|
||||
this->jsFileManagerUI = fileManagerUIClass.new_(dialogModeValue);
|
||||
dbp("FileDialogImplHtml::FileDialogImplHtml() Done.");
|
||||
}
|
||||
|
||||
~FileOpenDialogImplHtml() override {
|
||||
dbp("FileOpenDialogImplHtml::~FileOpenDialogImplHtml()");
|
||||
this->fileUploadHelper.call<void>("dispose");
|
||||
~FileDialogImplHtml() override {
|
||||
dbp("FileDialogImplHtml::~FileDialogImplHtml()");
|
||||
this->jsFileManagerUI.call<void>("dispose");
|
||||
}
|
||||
|
||||
void SetTitle(std::string title) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::SetTitle(): title=%s", title.c_str());
|
||||
dbp("FileDialogImplHtml::SetTitle(): title=\"%s\"", title.c_str());
|
||||
this->title = title;
|
||||
//FIXME(emscripten):
|
||||
this->fileUploadHelper.set("title", val(this->title));
|
||||
this->jsFileManagerUI.call<void>("setTitle", val(title));
|
||||
}
|
||||
|
||||
void SetCurrentName(std::string name) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::SetCurrentName(): name=%s", name.c_str());
|
||||
SetFilename(GetFilename().Parent().Join(name));
|
||||
dbp("FileDialogImplHtml::SetCurrentName(): name=\"%s\", parent=\"%s\"", name.c_str(), this->GetFilename().Parent().raw.c_str());
|
||||
|
||||
Path filepath = Path::From(name);
|
||||
if (filepath.IsAbsolute()) {
|
||||
// dbp("FileDialogImplHtml::SetCurrentName(): path is absolute.");
|
||||
SetFilename(filepath);
|
||||
} else {
|
||||
// dbp("FileDialogImplHtml::SetCurrentName(): path is relative.");
|
||||
SetFilename(GetFilename().Parent().Join(name));
|
||||
}
|
||||
}
|
||||
|
||||
Platform::Path GetFilename() override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::GetFilename()");
|
||||
return Platform::Path::From(this->filename.c_str());
|
||||
}
|
||||
|
||||
void SetFilename(Platform::Path path) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::SetFilename(): path=%s", path.raw.c_str());
|
||||
this->filename = path.raw;
|
||||
//FIXME(emscripten):
|
||||
this->fileUploadHelper.set("filename", val(this->filename));
|
||||
dbp("FileDialogImplHtml::GetFilename(): path=\"%s\"", path.raw.c_str());
|
||||
this->filename = std::string(path.raw);
|
||||
std::string filename_ = Path::From(this->filename).FileName();
|
||||
this->jsFileManagerUI.call<void>("setDefaultFilename", val(filename_));
|
||||
}
|
||||
|
||||
void SuggestFilename(Platform::Path path) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::SuggestFilename(): path=%s", path.raw.c_str());
|
||||
dbp("FileDialogImplHtml::SuggestFilename(): path=\"%s\"", path.raw.c_str());
|
||||
SetFilename(Platform::Path::From(path.FileStem()));
|
||||
}
|
||||
|
||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::AddFilter()");
|
||||
this->filters = "";
|
||||
for (auto extension : extensions) {
|
||||
this->filters = "." + extension;
|
||||
if (this->filters.length() > 0) {
|
||||
this->filters += ",";
|
||||
}
|
||||
dbp("filter=%s", this->filters.c_str());
|
||||
}
|
||||
|
||||
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::FreezeChoise()");
|
||||
}
|
||||
|
||||
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::ThawChoices()");
|
||||
}
|
||||
|
||||
bool RunModal() override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::RunModal()");
|
||||
this->filename = "/untitled.slvs";
|
||||
this->fileUploadHelper.call<void>("showDialog");
|
||||
|
||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||
dbp("FileOpenDialogImplHtml: start loop");
|
||||
// Wait until fileUploadHelper.is_shown == false
|
||||
while (true) {
|
||||
bool is_shown = this->fileUploadHelper["is_shown"].as<bool>();
|
||||
if (!is_shown) {
|
||||
// dbp("FileOpenDialogImplHtml: break due to is_shown == false");
|
||||
break;
|
||||
} else {
|
||||
// dbp("FileOpenDialogImplHtml: sleep 100msec... (%d)", is_shown);
|
||||
emscripten_sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
val selectedFilenameVal = this->fileUploadHelper["currentFilename"];
|
||||
|
||||
if (selectedFilenameVal == val::null()) {
|
||||
// dbp("selectedFilenameVal is null");
|
||||
return false;
|
||||
} else {
|
||||
std::string selectedFilename = selectedFilenameVal.as<std::string>();
|
||||
this->filename = selectedFilename;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FileSaveDummyDialogImplHtml : public FileDialog {
|
||||
public:
|
||||
std::string title;
|
||||
std::string filename;
|
||||
std::string filters;
|
||||
|
||||
FileSaveDummyDialogImplHtml() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
~FileSaveDummyDialogImplHtml() override {
|
||||
|
||||
}
|
||||
|
||||
void SetTitle(std::string title) override {
|
||||
this->title = title;
|
||||
}
|
||||
|
||||
void SetCurrentName(std::string name) override {
|
||||
SetFilename(GetFilename().Parent().Join(name));
|
||||
}
|
||||
|
||||
Platform::Path GetFilename() override {
|
||||
return Platform::Path::From(this->filename.c_str());
|
||||
}
|
||||
|
||||
void SetFilename(Platform::Path path) override {
|
||||
this->filename = path.raw;
|
||||
}
|
||||
|
||||
void SuggestFilename(Platform::Path path) override {
|
||||
SetFilename(Platform::Path::From(path.FileStem()));
|
||||
}
|
||||
|
||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||
this->filters = "";
|
||||
for (size_t i = 0; i < extensions.size(); i++) {
|
||||
if (i != 0) {
|
||||
this->filters += ",";
|
||||
}
|
||||
this->filters = "." + extensions[i];
|
||||
this->filters += "." + extensions[i];
|
||||
}
|
||||
dbp("filter=%s", this->filters.c_str());
|
||||
dbp("FileDialogImplHtml::AddFilter(): filter=%s", this->filters.c_str());
|
||||
this->jsFileManagerUI.call<void>("setFilter", val(this->filters));
|
||||
}
|
||||
|
||||
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||
|
@ -1443,27 +1377,49 @@ public:
|
|||
}
|
||||
|
||||
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||
|
||||
//FIXME(emscripten): implement
|
||||
}
|
||||
|
||||
bool RunModal() override {
|
||||
if (this->filename.length() < 1) {
|
||||
this->filename = "untitled.slvs";
|
||||
dbp("FileDialogImplHtml::RunModal()");
|
||||
|
||||
this->jsFileManagerUI.call<void>("setBasePath", val(basePathInFilesystem));
|
||||
this->jsFileManagerUI.call<void>("show");
|
||||
while (true) {
|
||||
bool isShown = this->jsFileManagerUI.call<bool>("isShown");
|
||||
if (!isShown) {
|
||||
break;
|
||||
} else {
|
||||
emscripten_sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
dbp("FileSaveDialogImplHtml::RunModal() : dialog closed.");
|
||||
|
||||
std::string selectedFilename = this->jsFileManagerUI.call<std::string>("getSelectedFilename");
|
||||
if (selectedFilename.length() > 0) {
|
||||
// Dummy call to set parent directory
|
||||
this->SetFilename(Path::From(basePathInFilesystem + "/dummy"));
|
||||
this->SetCurrentName(selectedFilename);
|
||||
}
|
||||
|
||||
|
||||
if (selectedFilename.length() > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||
// FIXME(emscripten): implement
|
||||
// dbp("CreateOpenFileDialog()");
|
||||
return std::shared_ptr<FileOpenDialogImplHtml>(new FileOpenDialogImplHtml());
|
||||
dbp("CreateOpenFileDialog()");
|
||||
return std::shared_ptr<FileDialogImplHtml>(new FileDialogImplHtml(FileDialogImplHtml::Modes::OPEN));
|
||||
}
|
||||
|
||||
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||
// FIXME(emscripten): implement
|
||||
// dbp("CreateSaveFileDialog()");
|
||||
return std::shared_ptr<FileSaveDummyDialogImplHtml>(new FileSaveDummyDialogImplHtml());
|
||||
dbp("CreateSaveFileDialog()");
|
||||
return std::shared_ptr<FileDialogImplHtml>(new FileDialogImplHtml(FileDialogImplHtml::Modes::SAVE));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1481,7 +1437,7 @@ void OpenInBrowser(const std::string &url) {
|
|||
|
||||
void OnSaveFinishedCallback(const Platform::Path& filename, bool is_saveAs, bool is_autosave) {
|
||||
dbp("OnSaveFinished(): %s, is_saveAs=%d, is_autosave=%d\n", filename.FileName().c_str(), is_saveAs, is_autosave);
|
||||
std::string filename_str = filename.FileName();
|
||||
std::string filename_str = filename.raw;
|
||||
EM_ASM(saveFileDone(UTF8ToString($0), $1, $2), filename_str.c_str(), is_saveAs, is_autosave);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
--><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ" crossorigin="anonymous"><!--
|
||||
--><link rel="stylesheet" href="solvespaceui.css"><!--
|
||||
--><script src="solvespaceui.js"></script><!--
|
||||
--><script src="filemanagerui.js"></script><!--
|
||||
--></head><!--
|
||||
--><body><!--
|
||||
--><div id="splash">
|
||||
|
|
|
@ -0,0 +1,525 @@
|
|||
"use strict";
|
||||
|
||||
const FileManagerUI_OPEN = 0;
|
||||
const FileManagerUI_SAVE = FileManagerUI_OPEN + 1;
|
||||
const FileManagerUI_BROWSE = FileManagerUI_SAVE + 1;
|
||||
|
||||
//FIXME(emscripten): File size thresholds. How large file can we accept safely ?
|
||||
|
||||
/** Maximum filesize for a uploaded file.
|
||||
* @type {number} */
|
||||
const FileManagerUI_UPLOAD_FILE_SIZE_LIMIT = 50 * 1000 * 1000;
|
||||
|
||||
const tryMakeDirectory = (path) => {
|
||||
try {
|
||||
FS.mkdir(path);
|
||||
} catch {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FileManagerUI {
|
||||
/**
|
||||
* @param {number} mode - dialog mode FileManagerUI_[ OPEN, SAVE, BROWSE ]
|
||||
*/
|
||||
constructor(mode) {
|
||||
/** @type {boolean} */
|
||||
this.__isOpenDialog = false;
|
||||
/** @type {boolean} */
|
||||
this.__isSaveDialog = false;
|
||||
/** @type {boolean} */
|
||||
this.__isBrowseDialog = false;
|
||||
|
||||
if (mode == FileManagerUI_OPEN) {
|
||||
this.__isOpenDialog = true;
|
||||
} else if (mode == FileManagerUI_SAVE) {
|
||||
this.__isSaveDialog = true;
|
||||
} else {
|
||||
this.__isBrowseDialog = true;
|
||||
}
|
||||
|
||||
/** @type {boolean} true if the dialog is shown. */
|
||||
this.__isShown = false;
|
||||
|
||||
/** @type {string[]} */
|
||||
this.__extension_filters = [".slvs"];
|
||||
|
||||
/** @type {string} */
|
||||
this.__basePathInFilesystem = "";
|
||||
|
||||
/** @type {string} filename user selected. empty if nothing selected */
|
||||
this.__selectedFilename = "";
|
||||
|
||||
this.__closedWithCancel = false;
|
||||
|
||||
this.__defaultFilename = "untitled";
|
||||
}
|
||||
|
||||
/** deconstructor
|
||||
*/
|
||||
dispose() {
|
||||
if (this.__dialogRootElement) {
|
||||
this.__dialogHeaderElement = null;
|
||||
this.__descriptionElement = null;
|
||||
this.__filelistElement = null;
|
||||
this.__fileInputElement = null;
|
||||
this.__saveFilenameInputElement = null;
|
||||
this.__buttonContainerElement = null;
|
||||
this.__dialogRootElement.parentElement.removeChild(this.__dialogRootElement);
|
||||
this.__dialogRootElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} label
|
||||
* @param {string} response
|
||||
* @param {bool} isDefault
|
||||
*/
|
||||
__addButton(label, response, isDefault, onclick) {
|
||||
const buttonElem = document.createElement("div");
|
||||
addClass(buttonElem, "button");
|
||||
setLabelWithMnemonic(buttonElem, label);
|
||||
if (isDefault) {
|
||||
addClass(buttonElem, "default");
|
||||
addClass(buttonElem, "selected");
|
||||
}
|
||||
buttonElem.addEventListener("click", () => {
|
||||
if (onclick) {
|
||||
if (onclick()) {
|
||||
this.__close();
|
||||
}
|
||||
} else {
|
||||
this.__close();
|
||||
}
|
||||
});
|
||||
|
||||
this.__buttonContainerElement.appendChild(buttonElem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} div element that built
|
||||
*/
|
||||
buildDialog() {
|
||||
const root = document.createElement('div');
|
||||
addClass(root, "modal");
|
||||
root.style.display = "none";
|
||||
root.style.zIndex = 1000;
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
addClass(dialog, "dialog");
|
||||
addClass(dialog, "wide");
|
||||
root.appendChild(dialog);
|
||||
|
||||
const messageHeader = document.createElement('strong');
|
||||
this.__dialogHeaderElement = messageHeader;
|
||||
addClass(messageHeader, "dialog_header");
|
||||
dialog.appendChild(messageHeader);
|
||||
|
||||
const description = document.createElement('p');
|
||||
this.__descriptionElement = description;
|
||||
dialog.appendChild(description);
|
||||
|
||||
const filelistheader = document.createElement('h3');
|
||||
filelistheader.textContent = 'Files:';
|
||||
dialog.appendChild(filelistheader);
|
||||
|
||||
const filelist = document.createElement('ul');
|
||||
this.__filelistElement = filelist;
|
||||
addClass(filelist, 'filelist');
|
||||
dialog.appendChild(filelist);
|
||||
|
||||
const dummyfilelistitem = document.createElement('li');
|
||||
dummyfilelistitem.textContent = "(No file in psuedo filesystem)";
|
||||
filelist.appendChild(dummyfilelistitem);
|
||||
|
||||
if (this.__isOpenDialog) {
|
||||
const fileuploadcontainer = document.createElement('div');
|
||||
dialog.appendChild(fileuploadcontainer);
|
||||
|
||||
const fileuploadheader = document.createElement('h3');
|
||||
fileuploadheader.textContent = "Upload file:";
|
||||
fileuploadcontainer.appendChild(fileuploadheader);
|
||||
|
||||
const dragdropdescription = document.createElement('p');
|
||||
dragdropdescription.textContent = "(Drag & drop file to the following box)";
|
||||
dragdropdescription.style.fontSize = "0.8em";
|
||||
dragdropdescription.style.margin = "0.1em";
|
||||
fileuploadcontainer.appendChild(dragdropdescription);
|
||||
|
||||
const filedroparea = document.createElement('div');
|
||||
addClass(filedroparea, 'filedrop');
|
||||
filedroparea.addEventListener('dragstart', (ev) => this.__onFileDragDrop(ev));
|
||||
filedroparea.addEventListener('dragover', (ev) => this.__onFileDragDrop(ev));
|
||||
filedroparea.addEventListener('dragleave', (ev) => this.__onFileDragDrop(ev));
|
||||
filedroparea.addEventListener('drop', (ev) => this.__onFileDragDrop(ev));
|
||||
fileuploadcontainer.appendChild(filedroparea);
|
||||
|
||||
const fileinput = document.createElement('input');
|
||||
this.__fileInputElement = fileinput;
|
||||
fileinput.setAttribute('type', 'file');
|
||||
fileinput.style.width = "100%";
|
||||
fileinput.addEventListener('change', (ev) => this.__onFileInputChanged(ev));
|
||||
filedroparea.appendChild(fileinput);
|
||||
|
||||
} else if (this.__isSaveDialog) {
|
||||
const filenameinputcontainer = document.createElement('div');
|
||||
dialog.appendChild(filenameinputcontainer);
|
||||
|
||||
const filenameinputheader = document.createElement('h3');
|
||||
filenameinputheader.textContent = "Filename:";
|
||||
filenameinputcontainer.appendChild(filenameinputheader);
|
||||
|
||||
const filenameinput = document.createElement('input');
|
||||
filenameinput.setAttribute('type', 'input');
|
||||
filenameinput.style.width = "90%";
|
||||
filenameinput.style.margin = "auto 1em auto 1em";
|
||||
this.__saveFilenameInputElement = filenameinput;
|
||||
filenameinputcontainer.appendChild(filenameinput);
|
||||
}
|
||||
|
||||
// Paragraph element for spacer
|
||||
dialog.appendChild(document.createElement('p'));
|
||||
|
||||
const buttoncontainer = document.createElement('div');
|
||||
this.__buttonContainerElement = buttoncontainer;
|
||||
addClass(buttoncontainer, "buttons");
|
||||
dialog.appendChild(buttoncontainer);
|
||||
|
||||
this.__addButton('OK', 0, false, () => {
|
||||
if (this.__isOpenDialog) {
|
||||
let selectedFilename = null;
|
||||
const fileitems = document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]');
|
||||
Array.from(fileitems).forEach((radiobox) => {
|
||||
if (radiobox.checked) {
|
||||
selectedFilename = radiobox.parentElement.getAttribute('data-filename');
|
||||
}
|
||||
});
|
||||
if (selectedFilename) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
this.__addButton('Cancel', 1, true, () => {
|
||||
this.__closedWithCancel = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
setTitle(text) {
|
||||
this.__dialogHeaderText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
setDescription(text) {
|
||||
this.__descriptionText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path file prefix. (ex) 'tmp/' to '/tmp/filename.txt'
|
||||
*/
|
||||
setBasePath(path) {
|
||||
this.__basePathInFilesystem = path;
|
||||
tryMakeDirectory(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
*/
|
||||
setDefaultFilename(filename) {
|
||||
this.__defaultFilename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} filter comma-separated extensions like ".slvs,.stl;."
|
||||
*/
|
||||
setFilter(filter) {
|
||||
const exts = filter.split(',');
|
||||
this.__extension_filters = exts;
|
||||
}
|
||||
|
||||
__buildFileEntry(filename) {
|
||||
const lielem = document.createElement('li');
|
||||
const label = document.createElement('label');
|
||||
label.setAttribute('data-filename', filename);
|
||||
lielem.appendChild(label);
|
||||
const radiobox = document.createElement('input');
|
||||
radiobox.setAttribute('type', 'radio');
|
||||
if (!this.__isOpenDialog) {
|
||||
radiobox.style.display = "none";
|
||||
}
|
||||
radiobox.setAttribute('name', 'filemanager_filelist');
|
||||
label.appendChild(radiobox);
|
||||
const filenametext = document.createTextNode(filename);
|
||||
label.appendChild(filenametext);
|
||||
|
||||
return lielem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]} filename array
|
||||
*/
|
||||
__getFileEntries() {
|
||||
const basePath = this.__basePathInFilesystem;
|
||||
/** @type {any[]} */
|
||||
const nodes = FS.readdir(basePath);
|
||||
/** @type {string[]} */
|
||||
const files = nodes.filter((nodename) => {
|
||||
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
||||
});
|
||||
/*.map((filename) => {
|
||||
return basePath + filename;
|
||||
});*/
|
||||
console.log(`__getFileEntries():`, files);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]?} files file list already constructed
|
||||
* @returns {string[]} filename array
|
||||
*/
|
||||
__getFileEntries_recurse(basePath) {
|
||||
//FIXME:remove try catch block
|
||||
try {
|
||||
//const basePath = this.__basePathInFilesystem;
|
||||
FS.currentPath = basePath;
|
||||
/** @type {any[]} */
|
||||
const nodes = FS.readdir(basePath);
|
||||
|
||||
const filesInThisDirectory = nodes.filter((nodename) => {
|
||||
return FS.isFile(FS.lstat(basePath + "/" + nodename).mode);
|
||||
}).map((filename) => {
|
||||
return basePath + "/" + filename;
|
||||
});
|
||||
let files = filesInThisDirectory;
|
||||
|
||||
const directories = nodes.filter((nodename) => {
|
||||
return FS.isDir(FS.lstat(basePath + "/" + nodename).mode);
|
||||
});
|
||||
|
||||
for (let i = 0; i < directories.length; i++) {
|
||||
const directoryname = directories[i];
|
||||
if (directoryname == '.' || directoryname == '..') {
|
||||
continue;
|
||||
}
|
||||
const orig_cwd = FS.currentPath;
|
||||
const directoryfullpath = basePath + "/" + directoryname;
|
||||
FS.currentPath = directoryfullpath;
|
||||
files = files.concat(this.__getFileEntries_recurse(directoryfullpath));
|
||||
FS.currentPath = orig_cwd;
|
||||
}
|
||||
|
||||
console.log(`__getFileEntries_recurse(): in "${basePath}"`, files);
|
||||
return files;
|
||||
|
||||
} catch (excep) {
|
||||
console.log(excep);
|
||||
throw excep;
|
||||
}
|
||||
}
|
||||
|
||||
__updateFileList() {
|
||||
console.log(`__updateFileList()`);
|
||||
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||
this.__filelistElement.removeChild(elem);
|
||||
});
|
||||
// const files = this.__getFileEntries();
|
||||
FS.currentPath = this.__basePathInFilesystem;
|
||||
const files = this.__getFileEntries_recurse(this.__basePathInFilesystem);
|
||||
if (files.length < 1) {
|
||||
const dummyfilelistitem = document.createElement('li');
|
||||
dummyfilelistitem.textContent = "(No file in psuedo filesystem)";
|
||||
this.__filelistElement.appendChild(dummyfilelistitem);
|
||||
|
||||
} else {
|
||||
files.forEach((entry) => {
|
||||
this.__filelistElement.appendChild(this.__buildFileEntry(entry));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
__getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const filereader = new FileReader();
|
||||
filereader.onerror = (ev) => {
|
||||
reject(ev);
|
||||
};
|
||||
filereader.onload = (ev) => {
|
||||
resolve(ev.target.result);
|
||||
};
|
||||
filereader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {File} file
|
||||
*/
|
||||
async __tryAddFile(file) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!file) {
|
||||
reject(new Error(`Invalid arg: file is ${file}`));
|
||||
|
||||
} else if (file.size > FileManagerUI_UPLOAD_FILE_SIZE_LIMIT) {
|
||||
//FIXME(emscripten): Use our MessageDialog instead of browser's alert().
|
||||
alert(`Specified file is larger than limit of ${FileManagerUI_UPLOAD_FILE_SIZE_LIMIT} bytes. Canceced.`);
|
||||
reject(new Error(`File is too large: "${file.name} is ${file.size} bytes`));
|
||||
|
||||
} else {
|
||||
// Just add to Filesystem
|
||||
const path = `${this.__basePathInFilesystem}${file.name}`;
|
||||
const blobArrayBuffer = await this.__getFileAsArrayBuffer(file);
|
||||
const u8array = new Uint8Array(blobArrayBuffer);
|
||||
const fs = FS.open(path, "w");
|
||||
FS.write(fs, u8array, 0, u8array.length, 0);
|
||||
FS.close(fs);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__addSelectedFile() {
|
||||
if (this.__fileInputElement.files.length < 1) {
|
||||
console.warn(`No file selected.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.__fileInputElement.files[0];
|
||||
this.__tryAddFile(file)
|
||||
.then(() => {
|
||||
this.__updateFileList();
|
||||
})
|
||||
.catch((err) => {
|
||||
this.__fileInputElement.value = null;
|
||||
console.error(err);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DragEvent} ev
|
||||
*/
|
||||
__onFileDragDrop(ev) {
|
||||
ev.preventDefault();
|
||||
if (ev.type == "dragenter" || ev.type == "dragover" || ev.type == "dragleave") {
|
||||
return;
|
||||
}
|
||||
if (ev.dataTransfer.files.length < 1) {
|
||||
return;
|
||||
}
|
||||
this.__fileInputElement.files = ev.dataTransfer.files;
|
||||
|
||||
this.__addSelectedFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InputEvent} _ev
|
||||
*/
|
||||
__onFileInputChanged(_ev) {
|
||||
this.__addSelectedFile();
|
||||
}
|
||||
|
||||
|
||||
/** Show the FileManager UI dialog */
|
||||
__show() {
|
||||
this.__closedWithCancel = false;
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.__dialogRootElement = this.buildDialog();
|
||||
document.querySelector('body').appendChild(this.__dialogRootElement);
|
||||
|
||||
this.__dialogHeaderElement.textContent = this.__dialogHeaderText || "File manager";
|
||||
this.__descriptionElement.textContent = this.__descriptionText || "Select a file.";
|
||||
if (this.__extension_filters) {
|
||||
this.__descriptionElement.textContent += "Requested filter is " + this.__extension_filters.join(", ");
|
||||
}
|
||||
|
||||
if (this.__isOpenDialog && this.__extension_filters) {
|
||||
this.__fileInputElement.accept = this.__extension_filters.concat(',');
|
||||
}
|
||||
|
||||
if (this.__isSaveDialog) {
|
||||
this.__saveFilenameInputElement.value = this.__defaultFilename;
|
||||
}
|
||||
|
||||
this.__dialogRootElement.style.display = "block";
|
||||
this.__isShown = true;
|
||||
}
|
||||
|
||||
/** Close the dialog */
|
||||
__close() {
|
||||
this.__selectedFilename = "";
|
||||
if (this.__isOpenDialog) {
|
||||
Array.from(document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]'))
|
||||
.forEach((elem) => {
|
||||
if (elem.checked) {
|
||||
this.__selectedFilename = elem.parentElement.getAttribute("data-filename");
|
||||
}
|
||||
});
|
||||
} else if (this.__isSaveDialog) {
|
||||
if (!this.__closedWithCancel) {
|
||||
this.__selectedFilename = this.__saveFilenameInputElement.value;
|
||||
}
|
||||
}
|
||||
|
||||
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||
this.__filelistElement.removeChild(elem);
|
||||
});
|
||||
|
||||
this.dispose();
|
||||
|
||||
this.__isShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isShown() {
|
||||
return this.__isShown;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise} filename string on resolved.
|
||||
*/
|
||||
showModalAsync() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.__show();
|
||||
this.__updateFileList();
|
||||
const intervalTimer = setInterval(() => {
|
||||
if (!this.isShown()) {
|
||||
clearInterval(intervalTimer);
|
||||
resolve(this.__selectedFilename);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedFilename() {
|
||||
return this.__selectedFilename;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.__show();
|
||||
this.__updateFileList();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
window.FileManagerUI = FileManagerUI;
|
|
@ -246,11 +246,34 @@ main {
|
|||
min-width: 200px;
|
||||
max-width: 400px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 70%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.dialog.wide {
|
||||
width: 80%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
.dialog > .buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.dialog .filedrop {
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 1em;
|
||||
border: 2px solid black;
|
||||
background-color: hsl(0, 0%, 50%);
|
||||
}
|
||||
.dialog .filelist {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.dialog .filelist li {
|
||||
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Mnemonics */
|
||||
.label > u {
|
||||
|
|
|
@ -657,8 +657,9 @@ class FileDownloadHelper {
|
|||
|
||||
this.descriptionParagraph.innerHTML = "";
|
||||
const linkElem = document.createElement("a");
|
||||
let downloadfilename = "solvespace_browser-";
|
||||
downloadfilename += `${GetCurrentDateTimeString()}.slvs`;
|
||||
//let downloadfilename = "solvespace_browser-";
|
||||
//downloadfilename += `${GetCurrentDateTimeString()}.slvs`;
|
||||
let downloadfilename = filename;
|
||||
linkElem.setAttribute("download", downloadfilename);
|
||||
linkElem.setAttribute("href", blobURL);
|
||||
// WORKAROUND: FIXME(emscripten)
|
||||
|
|
|
@ -702,6 +702,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
if(dialog->RunModal()) {
|
||||
dialog->FreezeChoices(settings, "ExportImage");
|
||||
SS.ExportAsPngTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -727,6 +730,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
}
|
||||
|
||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -739,6 +745,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||
|
||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -751,6 +760,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportSection");
|
||||
|
||||
SS.ExportSectionTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -763,6 +775,10 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportMesh");
|
||||
|
||||
SS.ExportMeshTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -776,6 +792,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
|
||||
StepFileWriter sfw = {};
|
||||
sfw.ExportSurfacesTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue