diff --git a/src/Bootstrap/dist/css/bootstrap-theme.css b/src/Bootstrap/dist/css/bootstrap-theme.css index f425dbd711..f60570eb2f 100644 --- a/src/Bootstrap/dist/css/bootstrap-theme.css +++ b/src/Bootstrap/dist/css/bootstrap-theme.css @@ -1290,7 +1290,7 @@ img.reserved-indicator-icon { border-bottom: 1px solid lightgray; } .page-package-details-v2 .package-header { - margin-bottom: 36px; + margin-bottom: 35px; } .page-package-details-v2 .package-title { margin-bottom: 30px; @@ -1428,6 +1428,37 @@ img.reserved-indicator-icon { margin-top: 15px; margin-bottom: 15px; } +.page-package-details-v2 .installation-instructions { + background-color: #F3F2F1; +} +.page-package-details-v2 .installation-instructions .installation-instructions-dropdown { + font-size: 14px; + background-color: #F3F2F1; + height: 31px; + border-left: 0px; + border-top: 0px; + border-right: 0px; +} +.page-package-details-v2 .installation-instructions .instructions-displayed { + background-color: #FAF9F8; + font-family: 'Segoe UI'; + font-size: 14px; +} +.page-package-details-v2 .installation-instructions .instructions-displayed pre { + background-color: #FAF9F8; + border: 0px; +} +.page-package-details-v2 button { + width: 32px; + height: 32px; + float: right; + padding: 0px; +} +.page-package-details-v2 button .ms-Icon--Copy { + color: #FFFFFF; + width: 16px; + height: 16px; +} .page-package-details-v2 .package-details-main { overflow-wrap: break-word; word-wrap: break-word; @@ -1647,6 +1678,7 @@ img.reserved-indicator-icon { border-bottom-color: #0078D4; border-bottom-width: 2px; font-weight: bold; + margin-bottom: -1px; } .page-package-details-v2 .body-tabs .nav-tabs > li > a { border-left: 0px; diff --git a/src/Bootstrap/dist/css/bootstrap.css b/src/Bootstrap/dist/css/bootstrap.css index 75533a671a..0c36156fc7 100644 --- a/src/Bootstrap/dist/css/bootstrap.css +++ b/src/Bootstrap/dist/css/bootstrap.css @@ -2687,6 +2687,59 @@ fieldset[disabled] .btn-danger.focus { color: #df001e; background-color: #fff; } +.btn-blue { + color: #333; + background-color: #0078D4; + border-color: #7f7f7f; +} +.btn-blue:focus, +.btn-blue.focus { + color: #333; + background-color: #005ba1; + border-color: #3f3f3f; +} +.btn-blue:hover { + color: #333; + background-color: #005ba1; + border-color: #606060; +} +.btn-blue:active, +.btn-blue.active, +.open > .dropdown-toggle.btn-blue { + color: #333; + background-color: #005ba1; + background-image: none; + border-color: #606060; +} +.btn-blue:active:hover, +.btn-blue.active:hover, +.open > .dropdown-toggle.btn-blue:hover, +.btn-blue:active:focus, +.btn-blue.active:focus, +.open > .dropdown-toggle.btn-blue:focus, +.btn-blue:active.focus, +.btn-blue.active.focus, +.open > .dropdown-toggle.btn-blue.focus { + color: #333; + background-color: #00477d; + border-color: #3f3f3f; +} +.btn-blue.disabled:hover, +.btn-blue[disabled]:hover, +fieldset[disabled] .btn-blue:hover, +.btn-blue.disabled:focus, +.btn-blue[disabled]:focus, +fieldset[disabled] .btn-blue:focus, +.btn-blue.disabled.focus, +.btn-blue[disabled].focus, +fieldset[disabled] .btn-blue.focus { + background-color: #0078D4; + border-color: #7f7f7f; +} +.btn-blue .badge { + color: #0078D4; + background-color: #333; +} .btn-link { font-weight: 400; color: #337ab7; diff --git a/src/Bootstrap/less/buttons.less b/src/Bootstrap/less/buttons.less index 7a31b6334c..1d5b5f4745 100644 --- a/src/Bootstrap/less/buttons.less +++ b/src/Bootstrap/less/buttons.less @@ -87,6 +87,10 @@ .btn-danger { .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); } +// Blue button for installation instructions +.btn-blue { + .button-variant(@btn-default-color; #0078D4; @btn-default-border); +} // Link buttons diff --git a/src/Bootstrap/less/theme/page-display-package-v2.less b/src/Bootstrap/less/theme/page-display-package-v2.less index 0977992259..0dcdd556ef 100644 --- a/src/Bootstrap/less/theme/page-display-package-v2.less +++ b/src/Bootstrap/less/theme/page-display-package-v2.less @@ -12,7 +12,7 @@ } .package-header { - margin-bottom: 36px; + margin-bottom: 35px; } .package-title { @@ -160,6 +160,43 @@ margin-bottom: 15px; } + .installation-instructions { + background-color: #F3F2F1; + + .installation-instructions-dropdown { + font-size: 14px; + background-color: #F3F2F1; + height: 31px; + border-left: 0px; + border-top: 0px; + border-right: 0px; + } + + .instructions-displayed { + background-color: #FAF9F8; + font-family: 'Segoe UI'; + font-size: 14px; + + pre { + background-color: #FAF9F8; + border: 0px; + } + } + } + + button { + width: 32px; + height: 32px; + float: right; + padding: 0px; + + .ms-Icon--Copy { + color: #FFFFFF; + width: 16px; + height: 16px; + } + } + .package-details-main { .break-word; @@ -438,6 +475,7 @@ border-bottom-color: #0078D4; border-bottom-width: 2px; font-weight: bold; + margin-bottom: -1px; } .nav-tabs > li > a { diff --git a/src/NuGetGallery/Scripts/gallery/page-display-package-v2.js b/src/NuGetGallery/Scripts/gallery/page-display-package-v2.js index c7f6e00b21..58ff82105e 100644 --- a/src/NuGetGallery/Scripts/gallery/page-display-package-v2.js +++ b/src/NuGetGallery/Scripts/gallery/page-display-package-v2.js @@ -28,8 +28,7 @@ // If the deprecation information container has content, configure it as an expander. window.nuget.configureExpander("deprecation-content-container", "ChevronDown", null, "ChevronUp"); configureExpanderWithEnterKeydown(deprecationContainer); - } - else { + } else { // If the container does not have content, remove its expander attributes expanderAttributes.forEach(attribute => deprecationContainer.removeAttr(attribute)); @@ -48,67 +47,90 @@ }); } - // Configure package manager copy buttons - function configureCopyButton(id) { - var copyButton = $('#' + id + '-button'); - copyButton.popover({ trigger: 'manual' }); - - copyButton.click(function () { - var text = $('#' + id + '-text').text().trim(); - window.nuget.copyTextToClipboard(text, copyButton); - copyButton.popover('show'); - //This is workaround for Narrator announce the status changes of copy button to achieve accessibility. - copyButton.attr('aria-pressed', 'true'); - setTimeout(function () { - copyButton.popover('destroy'); - }, 1000); - setTimeout(function () { - copyButton.attr('aria-pressed', 'false'); - }, 1500); - window.nuget.sendMetric("CopyInstallCommand", 1, { - ButtonId: id, - PackageId: packageId, - PackageVersion: packageVersion - }); - }); - } - - for (var i in packageManagers) - { - configureCopyButton(packageManagers[i]); - } + // Set up our state for the currently selected package manager. + var currentPackageManagerId = packageManagers[0]; + var packageManagerSelector = $('.installation-instructions-dropdown'); + // Restore previously selected package manager and body tab. var storage = window['localStorage']; - if (storage) { - // set preferred installation instruction tab - var installationKey = 'preferred_tab'; + var packageManagerStorageKey = 'preferred_package_manager'; + var bodyStorageKey = 'preferred_body_tab'; - // Restore preferred tab selection from localStorage. - var preferredInstallationTab = storage.getItem(installationKey); - if (preferredInstallationTab) { - $('#' + preferredInstallationTab).tab('show'); + if (storage) { + // Restore preferred package manager selection from localStorage. + var preferredPackageManagerId = storage.getItem(packageManagerStorageKey); + if (preferredPackageManagerId) { + updatePackageManager(preferredPackageManagerId, true); } - // Make sure we save the user's preferred tab to localStorage. - $('.package-manager-tab').on('shown.bs.tab', function (e) { - storage.setItem(installationKey, e.target.id); - }); - - // set preferred body tab - var bodyKey = 'preferred_body_tab'; - // Restore preferred body tab selection from localStorage. - var preferredBodyTab = storage.getItem(bodyKey); + var preferredBodyTab = storage.getItem(bodyStorageKey); if (preferredBodyTab) { $('#' + preferredBodyTab).tab('show'); } // Make sure we save the user's preferred body tab to localStorage. $('.body-tab').on('shown.bs.tab', function (e) { - storage.setItem(bodyKey, e.target.id); + storage.setItem(bodyStorageKey, e.target.id); }); } + packageManagerSelector.on('change', function (e) { + var newIndex = e.target.selectedIndex; + var newPackageManagerId = e.target[newIndex].value; + + updatePackageManager(newPackageManagerId, false); + + // Make sure we save the user's preferred package manager to localStorage. + if (storage) { + storage.setItem(packageManagerStorageKey, currentPackageManagerId); + } + }); + + // Used to switch installation instructions when a new package manager is selected + function updatePackageManager(newPackageManagerId, updateSelector) { + var currentInstructions = $('#' + currentPackageManagerId + '-instructions'); + var newInstructions = $('#' + newPackageManagerId + '-instructions'); + + // Ignore if the new instructions do not exist. This may happen if we restore + // a preferred package manager that has been renamed or removed. + if (newInstructions.length === 0) { + return; + } + + currentInstructions.addClass('hidden'); + newInstructions.removeClass('hidden'); + + currentPackageManagerId = newPackageManagerId; + + if (updateSelector) { + packageManagerSelector[0].value = preferredPackageManagerId; + } + } + + // Configure package manager copy button + var copyButton = $('.installation-instructions button'); + copyButton.popover({ trigger: 'manual' }); + + copyButton.click(function () { + var text = $('#' + currentPackageManagerId + '-text').text().trim(); + window.nuget.copyTextToClipboard(text, copyButton); + copyButton.popover('show'); + //This is workaround for Narrator announce the status changes of copy button to achieve accessibility. + copyButton.attr('aria-pressed', 'true'); + setTimeout(function () { + copyButton.popover('destroy'); + }, 1000); + setTimeout(function () { + copyButton.attr('aria-pressed', 'false'); + }, 1500); + window.nuget.sendMetric("CopyInstallCommand", 1, { + ButtonId: currentPackageManagerId, + PackageId: packageId, + PackageVersion: packageVersion + }); + }); + if (window.nuget.isGaAvailable()) { // TO-DO add telemetry events for when each tab is clicked, see https://github.com/nuget/nugetgallery/issues/8613 diff --git a/src/NuGetGallery/Views/Packages/DisplayPackageV2.cshtml b/src/NuGetGallery/Views/Packages/DisplayPackageV2.cshtml index 0f6e074486..ad9189dd79 100644 --- a/src/NuGetGallery/Views/Packages/DisplayPackageV2.cshtml +++ b/src/NuGetGallery/Views/Packages/DisplayPackageV2.cshtml @@ -167,63 +167,40 @@ } } -@helper CommandTab(PackageManagerViewModel packageManager, bool active) -{ - -} - @helper CommandPanel(PackageManagerViewModel packageManager, bool active) { var thirdPartyPackageManager = packageManager as ThirdPartyPackageManagerViewModel; -
-
-
- @{ - var lastIndex = packageManager.InstallPackageCommands.Length - 1; - var cs = packageManager.InstallPackageCommands.Select((c, i) => i < lastIndex ? c + Environment.NewLine : c); - } - @* Writing out the install command must be on a single line to avoid undesired whitespace in the
 tag. *@
-                
@foreach (var c in cs) {@c}
-
- - +
+
+
+
+ @{ + var lastIndex = packageManager.InstallPackageCommands.Length - 1; + var cs = packageManager.InstallPackageCommands.Select((c, i) => i < lastIndex ? c + Environment.NewLine : c); + } + @* Writing out the install command must be on a single line to avoid undesired whitespace in the
 tag. *@
+                    
@foreach (var c in cs) {@c}
-
- @switch (packageManager.AlertLevel) - { - case AlertLevel.Info: - @ViewHelpers.AlertInfo( - @ - @Html.Raw(packageManager.AlertMessage) - ); - break; - case AlertLevel.Warning: - @ViewHelpers.AlertWarning( - @ - @Html.Raw(packageManager.AlertMessage) - ); - break; - default: - break; - } + @switch (packageManager.AlertLevel) + { + case AlertLevel.Info: + @ViewHelpers.AlertInfo( + @ + @Html.Raw(packageManager.AlertMessage) + ); + break; + case AlertLevel.Warning: + @ViewHelpers.AlertWarning( + @ + @Html.Raw(packageManager.AlertMessage) + ); + break; + default: + break; + } +
} @@ -482,25 +459,34 @@ } @if (Model.Available) { -
- -
- @{ firstPackageManager = true; } - @foreach (var packageManager in packageManagers) - { - @CommandPanel(packageManager, active: firstPackageManager) + @{ var firstPackageManager = true; } + @foreach (var packageManager in packageManagers) + { + @CommandPanel(packageManager, active: firstPackageManager) - firstPackageManager = false; - } -
+ firstPackageManager = false; + }
}