public void ExportToFolderWithProgress(HtmlDom dom, string imagesFolderPath, string outputFolder,
                                               Action <string> resultCallback)
        {
            var mainShell = Application.OpenForms.Cast <Form>().FirstOrDefault(f => f is Shell);

            BrowserProgressDialog.DoWorkWithProgressDialog(_webSocketServer, "spreadsheet-export", () =>
                                                           new ReactDialog("progressDialogBundle",
                                                                           // props to send to the react component
                                                                           new
            {
                title                = "Exporting Spreadsheet",
                titleIcon            = "",                              // enhance: add icon if wanted
                titleColor           = "white",
                titleBackgroundColor = Palette.kBloomBlueHex,
                webSocketContext     = "spreadsheet-export",
                showReportButton     = "if-error"
            }, "Export Spreadsheet")
                                                           // winforms dialog properties
            {
                Width = 620, Height = 550
            }, (progress, worker) =>
            {
                var spreadsheet = ExportToFolder(dom, imagesFolderPath, outputFolder, out string outputFilePath,
                                                 progress);
                resultCallback(outputFilePath);
                return(progress.HaveProblemsBeenReported);
            }, null, mainShell);
        public void ImportWithProgress(string inputFilepath)
        {
            Debug.Assert(_pathToBookFolder != null,
                         "Somehow we made it into ImportWithProgress() without a path to the book folder");
            var mainShell = Application.OpenForms.Cast <Form>().FirstOrDefault(f => f is Shell);

            BrowserProgressDialog.DoWorkWithProgressDialog(_webSocketServer, "spreadsheet-import", () =>
                                                           new ReactDialog("progressDialogBundle",
                                                                           // props to send to the react component
                                                                           new
            {
                title                = "Importing Spreadsheet",
                titleIcon            = "",                              // enhance: add icon if wanted
                titleColor           = "white",
                titleBackgroundColor = Palette.kBloomBlueHex,
                webSocketContext     = "spreadsheet-import",
                showReportButton     = "if-error"
            }, "Import Spreadsheet")
                                                           // winforms dialog properties
            {
                Width = 620, Height = 550
            }, (progress, worker) =>
            {
                var hasAudio           = _destinationDom.GetRecordedAudioSentences(_pathToBookFolder).Any();
                var cannotImportEnding = " For this reason, we need to abandon the import. Instead, you can import into a blank book.";
                if (hasAudio)
                {
                    progress.MessageWithoutLocalizing($"Warning: Spreadsheet import cannot currently preserve Talking Book audio that is already in this book." + cannotImportEnding, ProgressKind.Error);
                    return(true);                    // leave progress window up so user can see error.
                }
                var hasActivities = _destinationDom.HasActivityPages();
                if (hasActivities)
                {
                    progress.MessageWithoutLocalizing($"Warning: Spreadsheet import cannot currently preserve quizzes, widgets, or other activities that are already in this book." + cannotImportEnding, ProgressKind.Error);
                    return(true);                    // leave progress window up so user can see error.
                }
                var sheet = InternalSpreadsheet.ReadFromFile(inputFilepath, progress);
                if (sheet == null)
                {
                    return(true);
                }
                if (!Validate(sheet, progress))
                {
                    return(true);                    // errors already reported to progress
                }
                progress.MessageWithoutLocalizing($"Making a backup of the original book...");
                var backupPath = BookStorage.SaveCopyBeforeImportOverwrite(_pathToBookFolder);
                progress.MessageWithoutLocalizing($"Backup completed (at {backupPath})");
                Import(sheet, progress);
                return(true);                // always leave the dialog up until the user chooses 'close'
            }, null, mainShell);
        }
        // Precondition: bulkSaveSettings must be non-null
        public void PublishAllBooks(BulkBloomPubPublishSettings bulkSaveSettings)
        {
            BrowserProgressDialog.DoWorkWithProgressDialog(_webSocketServer, "Bulk Save BloomPubs",
                                                           (progress, worker) =>
            {
                var dest = new TemporaryFolder("BloomPubs");
                progress.MessageWithoutLocalizing($"Creating files in {dest.FolderPath}...");

                var filenameWithoutExtension = _collectionModel.CollectionSettings.DefaultBookshelf.SanitizeFilename(' ', true);
                ;
                if (bulkSaveSettings.makeBookshelfFile)
                {
                    // see https://docs.google.com/document/d/1UUvwxJ32W2X5CRgq-TS-1HmPj7gCKH9Y9bxZKbmpdAI

                    progress.MessageWithoutLocalizing($"Creating bloomshelf file...");
                    System.Diagnostics.Debug.Assert(!bulkSaveSettings.bookshelfColor.Contains("\n") && !bulkSaveSettings.bookshelfColor.Contains("\r"), "(BL-10190 Repro) Invalid bookshelfColor setting (contains newline). Please investigate!");
                    var colorString = getBloomReaderColorString(bulkSaveSettings.bookshelfColor);
                    System.Diagnostics.Debug.Assert(!colorString.Contains("\n") && !colorString.Contains("\r"), "(BL-10190 Repro) Invalid computed colorString value (contains newline). Please investigate!");

                    // OK I know this looks lame but trust me, using jsconvert to make that trivial label array is way too verbose.
                    var template =
                        "{ 'label': [{ 'en': 'bookshelf-name'}], 'id': 'id-of-the-bookshelf', 'color': 'hex-color-value'}";
                    var json = template.Replace('\'', '"')
                               .Replace("bookshelf-name", bulkSaveSettings.bookshelfLabel.Replace('"', '\''))
                               .Replace("id-of-the-bookshelf", _collectionModel.CollectionSettings.DefaultBookshelf)
                               .Replace("hex-color-value", colorString);
                    var filename       = $"{filenameWithoutExtension}.bloomshelf";
                    var bloomShelfPath = Path.Combine(dest.FolderPath, filename);
                    RobustFile.WriteAllText(bloomShelfPath, json, Encoding.UTF8);
                }

                foreach (var bookInfo in _collectionModel.TheOneEditableCollection.GetBookInfos())
                {
                    if (worker.CancellationPending)
                    {
                        progress.MessageWithoutLocalizing("Cancelled.");
                        return(true);
                    }
                    progress.MessageWithoutLocalizing($"Making BloomPUB for {bookInfo.QuickTitleUserDisplay}...",
                                                      ProgressKind.Heading);

                    var settings             = AndroidPublishSettings.GetPublishSettingsForBook(_bookServer, bookInfo);
                    settings.DistributionTag = bulkSaveSettings.distributionTag;
                    if (bulkSaveSettings.makeBookshelfFile)
                    {
                        settings.BookshelfTag = _collectionModel.CollectionSettings.DefaultBookshelf;
                    }
                    BloomPubMaker.CreateBloomPub(bookInfo, settings, dest.FolderPath, _bookServer, progress);
                }

                if (bulkSaveSettings.makeBloomBundle)
                {
                    var bloomBundlePath = Path.Combine(dest.FolderPath, $"{filenameWithoutExtension}.bloombundle");

                    var bloomBundleFile = new BloomTarArchive(bloomBundlePath);
                    bloomBundleFile.AddDirectoryContents(dest.FolderPath, new string[] { ".bloombundle" });
                    bloomBundleFile.Save();
                }

                progress.MessageWithoutLocalizing("Done.", ProgressKind.Heading);
                Process.SafeStart(dest.FolderPath);
                // true means wait for the user, don't close automatically
                return(true);
            });
        }