Exemplo n.º 1
0
        private void HandleJoinTeamCollection(ApiRequest request)
        {
            try
            {
                FolderTeamCollection.JoinCollectionTeam();
                ReactDialog.CloseCurrentModal();

                Analytics.Track("TeamCollectionJoin",
                                new Dictionary <string, string>()
                {
                    { "CollectionId", _settings?.CollectionId },
                    { "CollectionName", _settings?.CollectionName },
                    { "Backend", _tcManager?.CurrentCollection?.GetBackendType() },
                    { "User", CurrentUser }
                });

                request.PostSucceeded();
            }
            catch (Exception e)
            {
                // Not sure what to do here: joining the collection crashed.
                Logger.WriteError("TeamCollectionApi.HandleJoinTeamCollection() crashed", e);
                var msg = LocalizationManager.GetString("TeamCollection.ErrorJoining", "Could not join Team Collection");
                ErrorReport.NotifyUserOfProblem(e, msg);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");

                // Since we have already informed the user above, it is better to just report a success here.
                // Otherwise, they will also get a toast.
                request.PostSucceeded();
            }
        }
Exemplo n.º 2
0
        private void HandleReloadCollection(ApiRequest request)
        {
            // Does nothing if there is no current dialog.
            ReactDialog.CloseAllReactDialogs();

            // On Linux, the main window doesn't close if we invoke ReloadProjectAction immediately here.
            // Waiting for Idle processing allows the underlying dialog to actually close before its parent
            // tries to close.  Without this slight delay on Linux, the user has to manually close the main
            // window before the program reopens and reloads the collection.
            Application.Idle += ReloadOnIdle;
            request.PostSucceeded();
        }
Exemplo n.º 3
0
        public CollectionSettingsDialog(CollectionSettings collectionSettings,
                                        QueueRenameOfCollection queueRenameOfCollection, PageRefreshEvent pageRefreshEvent,
                                        TeamCollectionManager tcManager, XMatterPackFinder xmatterPackFinder)
        {
            _collectionSettings      = collectionSettings;
            _queueRenameOfCollection = queueRenameOfCollection;
            _pageRefreshEvent        = pageRefreshEvent;
            _xmatterPackFinder       = xmatterPackFinder;
            InitializeComponent();

            _language1Name.UseMnemonic = false;             // Allow & to be part of the language display names.
            _language2Name.UseMnemonic = false;             // This may be unlikely, but can't be ruled out.
            _language3Name.UseMnemonic = false;             // See https://issues.bloomlibrary.org/youtrack/issue/BL-9919.

            PendingFontSelections[0] = _collectionSettings.LanguagesZeroBased[0].FontName;
            PendingFontSelections[1] = _collectionSettings.LanguagesZeroBased[1].FontName;
            var have3rdLanguage = _collectionSettings.LanguagesZeroBased[2] != null;

            PendingFontSelections[2] = have3rdLanguage ?
                                       _collectionSettings.LanguagesZeroBased[2].FontName :
                                       "";
            PendingNumberingStyle = _collectionSettings.PageNumberStyle;
            PendingXmatter        = _collectionSettings.XMatterPackName;
            CollectionSettingsApi.DialogBeingEdited = this;

            if (_collectionSettings.IsSourceCollection)
            {
                _language1Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language1InSourceCollection", "Language 1", "In a local language collection, we say 'Local Language', but in a source collection, Local Language has no relevance, so we use this different label");
                _language2Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language2InSourceCollection", "Language 2", "In a local language collection, we say 'Language 2 (e.g. National Language)', but in a source collection, National Language has no relevance, so we use this different label");
                _language3Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language3InSourceCollection", "Language 3", "In a local language collection, we say 'Language 3 (e.g. Regional Language)', but in a source collection, National Language has no relevance, so we use this different label");
            }

            _showExperimentalBookSources.Checked  = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kExperimentalSourceBooks);
            _allowTeamCollection.Checked          = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections);
            _allowSpreadsheetImportExport.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kSpreadsheetImportExport);

            if (!ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections) && tcManager.CurrentCollectionEvenIfDisconnected == null)
            {
                this._tab.Controls.Remove(this._teamCollectionTab);
            }
            // Don't allow the user to disable the Team Collection feature if we're currently in a Team Collection.
            _allowTeamCollection.Enabled = !(_allowTeamCollection.Checked && tcManager.CurrentCollectionEvenIfDisconnected != null);

            // AutoUpdate applies only to Windows: see https://silbloom.myjetbrains.com/youtrack/issue/BL-2317.
            if (SIL.PlatformUtilities.Platform.IsWindows)
            {
                _automaticallyUpdate.Checked = Settings.Default.AutoUpdate;
            }
            else
            {
                _automaticallyUpdate.Hide();
            }

            // Without this, PendingDefaultBookshelf stays null unless the user changes it.
            // The result is the bookshelf selection gets cleared when other collection settings are saved. See BL-10093.
            PendingDefaultBookshelf = _collectionSettings.DefaultBookshelf;

            CollectionSettingsApi.BrandingChangeHandler = ChangeBranding;

            TeamCollectionApi.TheOneInstance.SetCallbackToReopenCollection(() =>
            {
                _restartRequired = true;
                ReactDialog.CloseCurrentModal();             // close the top Create dialog
                _okButton_Click(null, null);                 // close this dialog
            });

            UpdateDisplay();

            if (CollectionSettingsApi.FixEnterpriseSubscriptionCodeMode)
            {
                _tab.SelectedTab = _enterpriseTab;
            }

            if (tcManager.CurrentCollectionEvenIfDisconnected == null)
            {
                _noRenameTeamCollectionLabel.Visible = false;
            }
            else
            {
                _bloomCollectionName.Enabled = false;
            }
        }
Exemplo n.º 4
0
        public CollectionSettingsDialog(CollectionSettings collectionSettings, XMatterPackFinder xmatterPackFinder, QueueRenameOfCollection queueRenameOfCollection, PageRefreshEvent pageRefreshEvent, TeamCollectionManager tcManager)
        {
            _collectionSettings      = collectionSettings;
            _xmatterPackFinder       = xmatterPackFinder;
            _queueRenameOfCollection = queueRenameOfCollection;
            _pageRefreshEvent        = pageRefreshEvent;
            InitializeComponent();
            // moved from the Designer where it was deleted if the Designer was touched
            _xmatterList.Columns.AddRange(new[] { new ColumnHeader()
                                                  {
                                                      Width = 250
                                                  } });

            _language1Name.UseMnemonic              = false;    // Allow & to be part of the language display names.
            _language2Name.UseMnemonic              = false;    // This may be unlikely, but can't be ruled out.
            _language3Name.UseMnemonic              = false;    // See https://issues.bloomlibrary.org/youtrack/issue/BL-9919.
            _language1FontLabel.UseMnemonic         = false;
            _language2FontLabel.UseMnemonic         = false;
            _language3FontLabel.UseMnemonic         = false;
            CollectionSettingsApi.DialogBeingEdited = this;

            if (_collectionSettings.IsSourceCollection)
            {
                _language1Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language1InSourceCollection", "Language 1", "In a local language collection, we say 'Local Language', but in a source collection, Local Language has no relevance, so we use this different label");
                _language2Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language2InSourceCollection", "Language 2", "In a local language collection, we say 'Language 2 (e.g. National Language)', but in a source collection, National Language has no relevance, so we use this different label");
                _language3Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language3InSourceCollection", "Language 3", "In a local language collection, we say 'Language 3 (e.g. Regional Language)', but in a source collection, National Language has no relevance, so we use this different label");
            }

            _showExperimentalBookSources.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kExperimentalSourceBooks);
            _allowTeamCollection.Checked         = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections);
            if (!ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections) && tcManager.CurrentCollectionEvenIfDisconnected == null)
            {
                this._tab.Controls.Remove(this._teamCollectionTab);
            }
            // Don't allow the user to disable the Team Collection feature if we're currently in a Team Collection.
            _allowTeamCollection.Enabled = !(_allowTeamCollection.Checked && tcManager.CurrentCollectionEvenIfDisconnected != null);

            // AutoUpdate applies only to Windows: see https://silbloom.myjetbrains.com/youtrack/issue/BL-2317.
            if (SIL.PlatformUtilities.Platform.IsWindows)
            {
                _automaticallyUpdate.Checked = Settings.Default.AutoUpdate;
            }
            else
            {
                _automaticallyUpdate.Hide();
            }

            // Without this, PendingDefaultBookshelf stays null unless the user changes it.
            // The result is the bookshelf selection gets cleared when other collection settings are saved. See BL-10093.
            PendingDefaultBookshelf = _collectionSettings.DefaultBookshelf;

//		    _showSendReceive.CheckStateChanged += (sender, args) =>
//		                                              {
//		                                                  Settings.Default.ShowSendReceive = _showSendReceive.CheckState ==
//		                                                                                     CheckState.Checked;
//
//                                                          _restartRequired = true;
//		                                                  UpdateDisplay();
//		                                              };

            CollectionSettingsApi.BrandingChangeHandler = ChangeBranding;

            SetupEnterpriseBrowser();

            TeamCollectionApi.TheOneInstance.SetCallbackToReopenCollection(() =>
            {
                _restartRequired = true;
                ReactDialog.CloseCurrentModal();             // close the top Create dialog
                _okButton_Click(null, null);                 // close this dialog
            });

            UpdateDisplay();

            if (CollectionSettingsApi.FixEnterpriseSubscriptionCodeMode)
            {
                _tab.SelectedTab = _enterpriseTab;
            }

            if (tcManager.CurrentCollectionEvenIfDisconnected == null)
            {
                _noRenameTeamCollectionLabel.Visible = false;
            }
            else
            {
                _bloomCollectionName.Enabled = false;
            }

            // This code would mostly more naturally go in Designer. Unfortunately we can't run designer
            // until we get back in a state where all our dependencies are sufficiently consistent.

            _defaultBookshelfControl = ReactControl.Create("defaultBookshelfControlBundle");

            tabPage2.Controls.Add(_defaultBookshelfControl);
            _defaultBookshelfControl.Location = new Point(_xmatterDescription.Left, _xmatterDescription.Bottom + 30);
            // We'd like it to be as big as possible, not just big enough for the immediate content.
            // Until React takes over at least the whole tab, the pull-down part of the combo can't
            // stretch outside the Gecko control.
            _defaultBookshelfControl.Size = new Size(_xmatterList.Width, 200);
        }
Exemplo n.º 5
0
        public void RegisterWithApiHandler(BloomApiHandler apiHandler)
        {
            apiHandler.RegisterEndpointLegacy("uiLanguages", HandleUiLanguages, false);                               // App
            apiHandler.RegisterEndpointLegacy("currentUiLanguage", HandleCurrentUiLanguage, false);                   // App
            apiHandler.RegisterEndpointLegacy("bubbleLanguages", HandleBubbleLanguages, false);                       // Move to EditingViewApi
            apiHandler.RegisterEndpointLegacy("authorMode", HandleAuthorMode, false);                                 // Move to EditingViewApi
            apiHandler.RegisterEndpointLegacy("topics", HandleTopics, false);                                         // Move to EditingViewApi
            apiHandler.RegisterEndpointLegacy("common/error", HandleJavascriptError, false);                          // Common
            apiHandler.RegisterEndpointLegacy("common/preliminaryError", HandlePreliminaryJavascriptError, false);    // Common
            apiHandler.RegisterEndpointLegacy("common/saveChangesAndRethinkPageEvent", RethinkPageAndReloadIt, true); // Move to EditingViewApi
            apiHandler.RegisterEndpointLegacy("common/chooseFolder", HandleChooseFolder, true);
            apiHandler.RegisterEndpointLegacy("common/showInFolder", HandleShowInFolderRequest, true);                // Common
            apiHandler.RegisterEndpointLegacy("common/canModifyCurrentBook", HandleCanModifyCurrentBook, true);
            apiHandler.RegisterEndpointLegacy("common/showSettingsDialog", HandleShowSettingsDialog, false);          // Common
            apiHandler.RegisterEndpointLegacy("common/problemWithBookMessage", request =>
            {
                request.ReplyWithText(CommonMessages.GetProblemWithBookMessage(Path.GetFileName(_bookSelection.CurrentSelection?.FolderPath)));
            }, false);
            apiHandler.RegisterEndpointLegacy("common/clickHereForHelp", request =>
            {
                var problemFilePath = UrlPathString.CreateFromUrlEncodedString(request.RequiredParam("problem")).NotEncoded;
                request.ReplyWithText(CommonMessages.GetPleaseClickHereForHelpMessage(problemFilePath));
            }, false);
            // Used when something in JS land wants to copy text to or from the clipboard. For POST, the text to be put on the
            // clipboard is passed as the 'text' property of a JSON requestData.
            // Somehow the get version of this fires while initializing a page (probably hooking up CkEditor, an unwanted
            // invocation of the code that decides whether to enable the paste hyperlink button). This causes a deadlock
            // unless we make this endpoint requiresSync:false. I think this is safe as it doesn't interact with any other
            // Bloom objects.
            apiHandler.RegisterEndpointLegacy("common/clipboardText",
                                              request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    string result = "";                             // initial value is not used, delegate will set it.
                    Program.MainContext.Send(o =>
                    {
                        try
                        {
                            result = PortableClipboard.GetText();
                        }
                        catch (Exception e)
                        {
                            // Need to make sure to handle exceptions.
                            // If the worker thread dies with an unhandled exception,
                            // it causes the whole program to immediately crash without opportunity for error reporting
                            NonFatalProblem.Report(ModalIf.All, PassiveIf.None, "Error pasting text", exception: e);
                        }
                    }, null);
                    request.ReplyWithText(result);
                }
                else
                {
                    // post
                    var requestData = DynamicJson.Parse(request.RequiredPostJson());
                    string content  = requestData.text;
                    if (!string.IsNullOrEmpty(content))
                    {
                        Program.MainContext.Post(o =>
                        {
                            try
                            {
                                PortableClipboard.SetText(content);
                            }
                            catch (Exception e)
                            {
                                // Need to make sure to handle exceptions.
                                // If the worker thread dies with an unhandled exception,
                                // it causes the whole program to immediately crash without opportunity for error reporting
                                NonFatalProblem.Report(ModalIf.All, PassiveIf.None, "Error copying text", exception: e);
                            }
                        }, null);
                    }
                    request.PostSucceeded();
                }
            }, false, false);
            apiHandler.RegisterEndpointLegacy("common/checkForUpdates",
                                              request =>
            {
                WorkspaceView.CheckForUpdates();
                request.PostSucceeded();
            }, false);
            apiHandler.RegisterEndpointLegacy("common/channel",
                                              request =>
            {
                request.ReplyWithText(ApplicationUpdateSupport.ChannelName);
            }, false);
            // This is useful for debugging TypeScript code, especially on Linux.  I wouldn't necessarily expect
            // to see it used anywhere in code that gets submitted and merged.
            apiHandler.RegisterEndpointLegacy("common/debugMessage",
                                              request =>
            {
                var message = request.RequiredPostString();
                Debug.WriteLine("FROM JS: " + message);
                request.PostSucceeded();
            }, false);

            apiHandler.RegisterEndpointLegacy("common/loginData",
                                              request =>
            {
                var requestData = DynamicJson.Parse(request.RequiredPostJson());
                string token    = requestData.sessionToken;
                string email    = requestData.email;
                string userId   = requestData.userId;
                //Debug.WriteLine("Got login data " + email + " with token " + token + " and id " + userId);
                _parseClient.SetLoginData(email, userId, token, BookUpload.Destination);
                _doWhenLoggedIn?.Invoke();
                request.PostSucceeded();
            }, false);

            // At this point we open dialogs from c# code; if we opened dialogs from javascript, we wouldn't need this
            // api to do it. We just need a way to close a c#-opened dialog from javascript (e.g. the Close button of the dialog).
            //
            // This must set requiresSync:false because the API call which opened the dialog may already have
            // the lock in which case we would be deadlocked.
            // ErrorReport.NotifyUserOfProblem is a particularly problematic case. We tried to come up with some
            // other solutions for that including opening the dialog on Application.Idle. But the dialog needs
            // to give a real-time result so callers can know what do with button presses. Since some of those
            // callers are in libpalaso, we can't just ignore the result and handle the actions ourselves.
            apiHandler.RegisterEndpointLegacy("common/closeReactDialog", request =>
            {
                ReactDialog.CloseCurrentModal(request.GetPostStringOrNull());
                request.PostSucceeded();
            }, true, requiresSync: false);

            // TODO: move to the new App API (BL-9635)
            apiHandler.RegisterEndpointLegacy("common/reloadCollection", HandleReloadCollection, true);
        }
Exemplo n.º 6
0
 private void HandleShowCreateTeamCollectionDialog(ApiRequest request)
 {
     ReactDialog.ShowOnIdle("createTeamCollectionDialogBundle", new { defaultRepoFolder = DropboxUtils.GetDropboxFolderPath() }, 600, 580, null, null, "Create Team Collection");
     request.PostSucceeded();
 }
Exemplo n.º 7
0
        public LibraryView(LibraryModel model, LibraryListView.Factory libraryListViewFactory,
                           LibraryBookView.Factory templateBookViewFactory,
                           SelectedTabChangedEvent selectedTabChangedEvent,
                           SendReceiveCommand sendReceiveCommand,
                           TeamCollectionManager tcManager)
        {
            _model = model;
            InitializeComponent();
            splitContainer1.BackColor = Palette.BookListSplitterColor;             // controls the left vs. right splitter
            _toolStrip.Renderer       = new NoBorderToolStripRenderer();
            _toolStripLeft.Renderer   = new NoBorderToolStripRenderer();

            _collectionListView      = libraryListViewFactory();
            _collectionListView.Dock = DockStyle.Fill;
            splitContainer1.Panel1.Controls.Add(_collectionListView);

            _bookView = templateBookViewFactory();
            _bookView.TeamCollectionMgr = tcManager;
            _bookView.Dock = DockStyle.Fill;
            splitContainer1.Panel2.Controls.Add(_bookView);

            // When going down to Shrink Stage 3 (see WorkspaceView), we want the right-side toolstrip to take precedence
            // (Settings, Other Collection).
            // This essentially makes the TC Status button's zIndex less than the buttons on the right side.
            _toolStripLeft.SendToBack();

            splitContainer1.SplitterDistance = _collectionListView.PreferredWidth;
            _makeBloomPackButton.Visible     = model.IsShellProject;
            _sendReceiveButton.Visible       = Settings.Default.ShowSendReceive;

            if (sendReceiveCommand != null)
            {
#if Chorus
                _sendReceiveButton.Click  += (x, y) => sendReceiveCommand.Raise(this);
                _sendReceiveButton.Enabled = !SendReceiver.SendReceiveDisabled;
#endif
            }
            else
            {
                _sendReceiveButton.Enabled = false;
            }

            if (SIL.PlatformUtilities.Platform.IsMono)
            {
                BackgroundColorsForLinux();
            }

            selectedTabChangedEvent.Subscribe(c =>
            {
                if (c.To == this)
                {
                    Logger.WriteEvent("Entered Collections Tab");
                }
            });
            SetTeamCollectionStatus(tcManager);
            TeamCollectionManager.TeamCollectionStatusChanged += (sender, args) =>
            {
                if (IsHandleCreated && !IsDisposed)
                {
                    SafeInvoke.InvokeIfPossible("update TC status", this, false,
                                                () => SetTeamCollectionStatus(tcManager));
                }
            };
            _tcStatusButton.Click += (sender, args) =>
            {
                // Any messages for which reloading the collection is a useful action?
                var showReloadButton = tcManager.MessageLog.ShouldShowReloadButton;
                // Reinstate this to see messages from before we started up.
                // We think it might be too expensive to show a list as long as this might get.
                // Instead, in the short term we may add a button to show the file.
                // Later we may implement some efficient way to scroll through them.
                // tcManager.CurrentCollection?.MessageLog?.LoadSavedMessages();
                using (var dlg = new ReactDialog("teamCollectionDialogBundle", new
                {
                    showReloadButton
                }, "Team Collection"))
                {
                    dlg.ShowDialog(this);
                    tcManager.CurrentCollectionEvenIfDisconnected?.MessageLog.WriteMilestone(MessageAndMilestoneType
                                                                                             .LogDisplayed);
                }
            };
        }
Exemplo n.º 8
0
        // ENHANCE: Reduce duplication in HtmlErrorReporter and ProblemReportApi code. Some of the ProblemReportApi code can move to HtmlErrorReporter code.

        // ENHANCE: I think levelOfProblem would benefit from being required and being an enum.

        /// <summary>
        /// Shows a problem dialog.
        /// </summary>
        /// <param name="controlForScreenshotting"></param>
        /// <param name="exception"></param>
        /// <param name="detailedMessage"></param>
        /// <param name="levelOfProblem">"user", "nonfatal", or "fatal"</param>
        /// <param name="additionalPathsToInclude"></param>
        public static void ShowProblemDialog(Control controlForScreenshotting, Exception exception,
                                             string detailedMessage            = "", string levelOfProblem = "user", string shortUserLevelMessage = "", bool isShortMessagePreEncoded = false,
                                             string[] additionalPathsToInclude = null)
        {
            // Before we do anything that might be "risky", put the problem in the log.
            LogProblem(exception, detailedMessage, levelOfProblem);
            if (Program.RunningHarvesterMode)
            {
                Console.WriteLine(levelOfProblem + " Problem Detected: " + shortUserLevelMessage + "  " + detailedMessage + "  " + exception);
                return;
            }
            StartupScreenManager.CloseSplashScreen();             // if it's still up, it'll be on top of the dialog

            lock (_showingProblemReportLock)
            {
                if (_showingProblemReport)
                {
                    // If a problem is reported when already reporting a problem, that could
                    // be an unbounded recursion that freezes the program and prevents the original
                    // problem from being reported.  So minimally report the recursive problem and stop
                    // the recursion in its tracks.
                    //
                    // Alternatively, can happen if multiple async BloomAPI calls go out and return errors.
                    // It's probably not helpful to have multiple problem report dialogs at the same time
                    // in this case either (even if there are theoretically a finite (not infinite) number of them)
                    const string msg = "MULTIPLE CALLS to ShowProblemDialog. Suppressing the subsequent calls";
                    Console.Write(msg);
                    Logger.WriteEvent(msg);
                    return;                     // Abort
                }

                _showingProblemReport = true;
            }

            // We have a better UI for this problem
            // Note that this will trigger whether it's a plain 'ol System.IO.PathTooLongException, or our own enhanced subclass, Bloom.Utiles.PathTooLongException
            if (exception is System.IO.PathTooLongException)
            {
                Utils.LongPathAware.ReportLongPath((System.IO.PathTooLongException)exception);
                return;
            }

            GatherReportInfoExceptScreenshot(exception, detailedMessage, shortUserLevelMessage, isShortMessagePreEncoded);

            if (controlForScreenshotting == null)
            {
                controlForScreenshotting = Form.ActiveForm;
            }
            if (controlForScreenshotting == null)             // still possible if we come from a "Details" button
            {
                controlForScreenshotting = FatalExceptionHandler.ControlOnUIThread;
            }
            ResetScreenshotFile();
            // Originally, we used SafeInvoke for both the screenshot and the new dialog display. SafeInvoke was great
            // for trying to get a screenshot, but having the actual dialog inside
            // of it was causing problems for handling any errors in showing the dialog.
            // Now we use SafeInvoke only inside of this extracted method.
            TryGetScreenshot(controlForScreenshotting);

            if (BloomServer._theOneInstance == null)
            {
                // We got an error really early, before we can use HTML dialogs. Report using the old dialog.
                // Hopefully we're still on the one main thread.
                HtmlErrorReporter.ShowFallbackProblemDialog(levelOfProblem, exception, detailedMessage, shortUserLevelMessage, isShortMessagePreEncoded);
                return;
            }

            SafeInvoke.InvokeIfPossible("Show Problem Dialog", controlForScreenshotting, false, () =>
            {
                // Uses a browser ReactDialog (if possible) to show the problem report
                try
                {
                    // We call CloseSplashScreen() above too, where it might help in some cases, but
                    // this one, while apparently redundant might be wise to keep since closing the splash screen
                    // needs to be done on the UI thread.
                    StartupScreenManager.CloseSplashScreen();

                    if (!BloomServer.ServerIsListening)
                    {
                        // We can't use the react dialog!
                        HtmlErrorReporter.ShowFallbackProblemDialog(levelOfProblem, exception, detailedMessage, shortUserLevelMessage, isShortMessagePreEncoded);
                        return;
                    }

                    // Precondition: we must be on the UI thread for Gecko to work.
                    using (var dlg = new ReactDialog("problemReportBundle", new { level = levelOfProblem }, "Problem Report"))
                    {
                        _additionalPathsToInclude = additionalPathsToInclude;
                        dlg.FormBorderStyle       = FormBorderStyle.FixedToolWindow; // Allows the window to be dragged around
                        dlg.ControlBox            = true;                            // Add controls like the X button back to the top bar
                        dlg.Text = "";                                               // Remove the title from the WinForms top bar

                        dlg.Width  = 731;
                        dlg.Height = 616;

                        // ShowDialog will cause this thread to be blocked (because it spins up a modal) until the dialog is closed.
                        BloomServer._theOneInstance.RegisterThreadBlocking();
                        try
                        {
                            // Keep dialog on top of program window if possible.  See https://issues.bloomlibrary.org/youtrack/issue/BL-10292.
                            if (controlForScreenshotting is Bloom.Shell)
                            {
                                dlg.ShowDialog(controlForScreenshotting);
                            }
                            else
                            {
                                dlg.ShowDialog();
                            }
                        }
                        finally
                        {
                            BloomServer._theOneInstance.RegisterThreadUnblocked();
                            _additionalPathsToInclude = null;
                        }
                    }
                }
                catch (Exception problemReportException)
                {
                    Logger.WriteError("*** ProblemReportApi threw an exception trying to display", problemReportException);
                    // At this point our problem reporter has failed for some reason, so we want the old WinForms handler
                    // to report both the original error for which we tried to open our dialog and this new one where
                    // the dialog itself failed.
                    // In order to do that, we create a new exception with the original exception (if there was one) as the
                    // inner exception. We include the message of the exception we just caught. Then we call the
                    // old WinForms fatal exception report directly.
                    // In any case, both of the errors will be logged by now.
                    var message = "Bloom's error reporting failed: " + problemReportException.Message;

                    // Fallback to Winforms in case of trouble getting the browser up
                    var fallbackReporter = new WinFormsErrorReporter();
                    // ENHANCE?: If reporting a non-fatal problem failed, why is the program required to abort? It might be able to handle other tasks successfully
                    fallbackReporter.ReportFatalException(new ApplicationException(message, exception ?? problemReportException));
                }
                finally
                {
                    lock (_showingProblemReportLock)
                    {
                        _showingProblemReport = false;
                    }
                }
            });
        }
        private void ShowIdCollisionDialog()
        {
            var newThumbPath = ChooseBestUploadingThumbnailPath(_model.Book).ToLocalhost();
            var newTitle     = _model.Book.TitleBestForUserDisplay;
            var newLanguages = ConvertLanguageCodesToNames(LanguagesCheckedToUpload, _model.Book.BookData);

            if (_signLanguageCheckBox.Checked && !string.IsNullOrEmpty(CurrentSignLanguageName))
            {
                var newLangs = newLanguages.ToList();
                if (!newLangs.Contains(CurrentSignLanguageName))
                {
                    newLangs.Add(CurrentSignLanguageName);
                }
                newLanguages = newLangs;
            }

            var existingBookInfo = _model.ConflictingBookInfo;
            var updatedDateTime  = (DateTime)existingBookInfo.updatedAt;
            var createdDateTime  = (DateTime)existingBookInfo.createdAt;
            // Find the best title available (BL-11027)
            // Users can click on this title to bring up the existing book's page.
            var existingTitle = existingBookInfo.title?.Value;

            if (String.IsNullOrEmpty(existingTitle))
            {
                // If title is undefined (which should not be the case), then use the first title from allTitles.
                var allTitlesString = existingBookInfo.allTitles?.Value;
                if (!String.IsNullOrEmpty(allTitlesString))
                {
                    try
                    {
                        var allTitles = Newtonsoft.Json.Linq.JObject.Parse(allTitlesString);
                        foreach (var title in allTitles)
                        {
                            // title.Value is dynamic language code / title string pair
                            // title.Value.Value is the actual book title in the associated language
                            if (title?.Value?.Value != null)
                            {
                                existingTitle = title.Value.Value;
                                break;
                            }
                        }
                    }
                    catch
                    {
                        // ignore parse failure -- should never happen at this point.
                    }
                }
            }
            // If neither title nor allTitles are defined, just give a placeholder value.
            if (String.IsNullOrEmpty(existingTitle))
            {
                existingTitle = "Unknown";
            }

            var existingId      = existingBookInfo.objectId.ToString();
            var existingBookUrl = BloomLibraryUrlPrefix + "/book/" + existingId;

            var existingLanguages = ConvertLanguagePointerObjectsToNames(existingBookInfo.langPointers);
            var createdDate       = createdDateTime.ToString("d", CultureInfo.CurrentCulture);
            var updatedDate       = updatedDateTime.ToString("d", CultureInfo.CurrentCulture);
            var existingThumbUrl  = GetBloomLibraryThumbnailUrl(existingBookInfo);

            using (var dlg = new ReactDialog("uploadIDCollisionDlgBundle",
                                             // Props for dialog; must be the same as IUploadCollisionDlgProps in js-land.
                                             new
            {
                userEmail = _model.LoggedIn ? _userId.Text : "",
                newThumbUrl = newThumbPath,
                newTitle,
                newLanguages,
                existingTitle = existingTitle,
                existingLanguages,
                existingCreatedDate = createdDate,
                existingUpdatedDate = updatedDate,
                existingBookUrl,
                existingThumbUrl
            }
                                             ))
            {
                dlg.Width      = 770;
                dlg.Height     = 570;
                dlg.ControlBox = false;
                dlg.ShowDialog(Shell.GetShellOrOtherOpenForm());
            }
        }
        public ReactCollectionTabView(LibraryModel model, LibraryListView.Factory libraryListViewFactory,
                                      LibraryBookView.Factory templateBookViewFactory,
                                      SelectedTabChangedEvent selectedTabChangedEvent,
                                      SendReceiveCommand sendReceiveCommand,
                                      TeamCollectionManager tcManager)
        {
            _model = model;
            InitializeComponent();
            BackColor               = _reactControl.BackColor = Palette.GeneralBackground;
            _toolStrip.Renderer     = new NoBorderToolStripRenderer();
            _toolStripLeft.Renderer = new NoBorderToolStripRenderer();

            // When going down to Shrink Stage 3 (see WorkspaceView), we want the right-side toolstrip to take precedence
            // (Settings, Other Collection).
            // This essentially makes the TC Status button's zIndex less than the buttons on the right side.
            _toolStripLeft.SendToBack();

            //TODO splitContainer1.SplitterDistance = _collectionListView.PreferredWidth;
            _makeBloomPackButton.Visible = model.IsShellProject;
            _sendReceiveButton.Visible   = Settings.Default.ShowSendReceive;

            if (sendReceiveCommand != null)
            {
#if Chorus
                _sendReceiveButton.Click  += (x, y) => sendReceiveCommand.Raise(this);
                _sendReceiveButton.Enabled = !SendReceiver.SendReceiveDisabled;
#endif
            }
            else
            {
                _sendReceiveButton.Enabled = false;
            }

            if (SIL.PlatformUtilities.Platform.IsMono)
            {
                BackgroundColorsForLinux();
            }

            selectedTabChangedEvent.Subscribe(c =>
            {
                if (c.To == this)
                {
                    Logger.WriteEvent("Entered Collections Tab");
                }
            });
            SetTeamCollectionStatus(tcManager);
            TeamCollectionManager.TeamCollectionStatusChanged += (sender, args) =>
            {
                return;

                if (!IsDisposed)
                {
                    SafeInvoke.InvokeIfPossible("update TC status", this, false,
                                                () => SetTeamCollectionStatus(tcManager));
                }
            };
            _tcStatusButton.Click += (sender, args) =>
            {
                // Any messages for which reloading the collection is a useful action?
                var showReloadButton = tcManager.MessageLog.ShouldShowReloadButton;
                // Reinstate this to see messages from before we started up.
                // We think it might be too expensive to show a list as long as this might get.
                // Instead, in the short term we may add a button to show the file.
                // Later we may implement some efficient way to scroll through them.
                // tcManager.CurrentCollection?.MessageLog?.LoadSavedMessages();
                using (var dlg = new ReactDialog("teamCollectionDialog", new { showReloadButton }))
                {
                    dlg.ShowDialog(this);
                    tcManager.CurrentCollectionEvenIfDisconnected?.MessageLog.WriteMilestone(MessageAndMilestoneType.LogDisplayed);
                }
            };

            // We don't want this control initializing until team collections sync (if any) is done.
            // That could change, but for now we're not trying to handle async changes arriving from
            // the TC to the local collection, and as part of that, the collection tab doesn't expect
            // the local collection to change because of TC stuff once it starts loading.
            Controls.Remove(_reactControl);
        }