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(); } }
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(); }
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; } }
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); }
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); }
private void HandleShowCreateTeamCollectionDialog(ApiRequest request) { ReactDialog.ShowOnIdle("createTeamCollectionDialogBundle", new { defaultRepoFolder = DropboxUtils.GetDropboxFolderPath() }, 600, 580, null, null, "Create Team Collection"); request.PostSucceeded(); }
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); } }; }
// 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); }