diff --git a/FrontEnd/main.scss b/FrontEnd/main.scss index a3f98f559..82db2417c 100644 --- a/FrontEnd/main.scss +++ b/FrontEnd/main.scss @@ -22,6 +22,7 @@ $mobile-breakpoint: 740px; @import 'styles/breadcrumbs'; @import 'styles/build_logs'; @import 'styles/build_monitor'; +@import 'styles/build_results'; @import 'styles/copyable_input'; @import 'styles/error'; @import 'styles/github_highlighting'; diff --git a/FrontEnd/styles/build_results.scss b/FrontEnd/styles/build_results.scss new file mode 100644 index 000000000..c51aebd4a --- /dev/null +++ b/FrontEnd/styles/build_results.scss @@ -0,0 +1,156 @@ +// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ------------------------------------------------------------------------- +// Build results page, showing all builds for a package. +// ------------------------------------------------------------------------- + +.build-results { + margin: 0; + padding: 0; + + li { + margin: 5px 0; + + @media screen and (max-width: $mobile-breakpoint) { + margin: 20px 0; + } + } + + .row { + display: grid; + grid-template-columns: 3fr 7fr; + + .row-labels { + display: flex; + grid-row: 2; + flex-direction: column; + justify-content: center; + + p { + margin: 0; + } + } + + .column-labels { + display: flex; + grid-column: 2; + flex-direction: row; + } + + .results { + display: flex; + grid-column: 2; + flex-direction: row; + } + + &:not(:first-child) { + .row-labels { + grid-row: unset; + } + + .column-labels { + display: none; + } + + @media (max-width: $mobile-breakpoint) { + .column-labels { + display: flex; + } + } + } + + @media (max-width: $mobile-breakpoint) { + grid-template-columns: 1fr; + + .row-labels, + .column-labels, + .results { + grid-column: unset; + grid-row: unset; + } + } + } + + .column-labels > div { + display: flex; + flex-direction: column; + flex-basis: 0; + flex-grow: 1; + align-items: center; + justify-content: flex-start; + padding: 5px 0; + font-size: 14px; + font-weight: 600; + + small { + font-weight: normal; + } + } + + .results > div { + position: relative; + display: flex; + flex-basis: 0; + flex-grow: 1; + align-items: center; + justify-content: center; + height: 35px; + margin: 0 3px; + background-color: var(--grid-default-background); + + &.succeeded > a, + &.failed > a { + padding-left: 25px; + background-position: left center; + background-repeat: no-repeat; + background-size: 18px; + } + + &.succeeded { + background-color: var(--grid-succeeded-background); + + a { + background-image: var(--image-build-succeeded); + } + } + + &.failed { + background-color: var(--grid-failed-background); + + a { + background-image: var(--image-build-failed); + } + } + + > .generated-docs { + position: absolute; + right: 5px; + display: inline-block; + width: 25px; + height: 25px; + background-position: center; + background-repeat: no-repeat; + background-size: 15px; + background-color: var(--grid-callout-background); + background-image: var(--image-documentation); + border-radius: 50%; + } + } + + .column-labels > div > span { + font-size: 16px; + background-position: top 4px right; + } +} diff --git a/FrontEnd/styles/colors.scss b/FrontEnd/styles/colors.scss index 938669322..6a00ce32e 100644 --- a/FrontEnd/styles/colors.scss +++ b/FrontEnd/styles/colors.scss @@ -96,6 +96,8 @@ --breadcrumb: var(--light-grey); --breadcrumb-header: var(--grey); + --separator-text: var(--light-grey); + --announcement-background: var(--very-light-grey); --bordered-button-background: var(--very-very-light-grey); @@ -126,9 +128,12 @@ --branch-text: var(--dark-green); --grid-default-background: var(--very-very-light-grey); + --grid-default-text: var(--light-grey); --grid-default-border: var(--very-light-grey); --grid-compatible-background: var(--light-green); + --grid-compatible-text: var(--white); --grid-incompatible-background: var(--light-grey); + --grid-incompatible-text: var(--grey); --grid-succeeded-background: var(--very-light-grey); --grid-failed-background: var(--very-light-grey); --grid-callout-background: var(--white); @@ -250,6 +255,8 @@ --breadcrumb: var(--dark-grey); + --separator-text: var(--dark-grey); + --announcement-background: var(--very-dark-grey); --bordered-button-background: var(--very-very-dark-grey); @@ -272,6 +279,7 @@ --rule-thin-background: var(--dark-grey); --grid-default-background: var(--very-very-dark-grey); + --grid-default-text: var(--dark-grey); --grid-default-border: var(--very-dark-grey); --grid-compatible-background: var(--mid-green); --grid-incompatible-background: var(--very-dark-grey); diff --git a/FrontEnd/styles/layout.scss b/FrontEnd/styles/layout.scss index aa9e32fe2..4c6960706 100644 --- a/FrontEnd/styles/layout.scss +++ b/FrontEnd/styles/layout.scss @@ -27,7 +27,7 @@ main > .inner { .two-column { display: grid; - grid-template-columns: 3fr 1fr; + grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); gap: 60px; > :last-child { diff --git a/FrontEnd/styles/matrix.scss b/FrontEnd/styles/matrix.scss index 8bea5fb01..c162935d3 100644 --- a/FrontEnd/styles/matrix.scss +++ b/FrontEnd/styles/matrix.scss @@ -18,8 +18,8 @@ // ------------------------------------------------------------------------- .matrices { - display: grid; - gap: 20px; + display: flex; + flex-direction: column; a { color: var(--page-text); @@ -27,168 +27,78 @@ } .matrix { + display: flex; + flex-direction: column; + gap: 20px; margin: 0; - padding: 0; - - li { - margin: 5px 0; - - @media screen and (max-width: $mobile-breakpoint) { - margin: 20px 0; - } - } - - .row { - display: grid; - grid-template-columns: 3fr 7fr; - - .row-labels { - display: flex; - grid-row: 2; - flex-direction: column; - justify-content: center; - - p { - margin: 0; - } - } + padding: 20px 0; + list-style: none; - .column-labels { - display: flex; - grid-column: 2; - flex-direction: row; - } + .version { + display: flex; + flex-direction: column; + gap: 5px; - .results { + .label { display: flex; - grid-column: 2; - flex-direction: row; - } + gap: 0.5ch; - // Show the column labels only for the first row on desktop. - // Note: This is a *desktop only* media query. - @media not all and (max-width: $mobile-breakpoint) { - &:not(:first-child) { - .row-labels { - grid-row: unset; - } + span { + overflow: hidden; + flex-shrink: 0; + white-space: nowrap; + text-overflow: ellipsis; - .column-labels { - display: none; + &.longest { + flex-shrink: 1; } } - } - - @media (max-width: $mobile-breakpoint) { - grid-template-columns: 1fr; - .row-labels, - .column-labels, - .results { - grid-column: unset; - grid-row: unset; + .separator { + font-size: 14px; + font-weight: 600; + color: var(--separator-text); } } - } - - .column-labels > div { - display: flex; - flex-direction: column; - flex-basis: 0; - flex-grow: 1; - align-items: center; - justify-content: flex-start; - padding: 5px 0; - font-size: 14px; - font-weight: 600; - - small { - font-weight: normal; - } - } - - .results > div { - display: flex; - flex-basis: 0; - flex-grow: 1; - align-items: center; - justify-content: center; - height: 35px; - margin: 0 3px; - } - &.compatibility { .results { - & > div { - background-position: center center; - background-repeat: no-repeat; - background-size: 20px; - } - - & > .pending, - & > .unknown { - background-color: var(--grid-default-background); - background-image: var(--image-compatibility-unknown); - } - - & > .compatible { - background-color: var(--grid-compatible-background); - background-image: var(--image-compatible); - } - - & > .incompatible { - background-color: var(--grid-incompatible-background); - background-image: var(--image-incompatible); - } - } - } - - &.builds { - .column-labels > div > span { - font-size: 16px; - background-position: top 4px right; - } - - .results > div { - position: relative; - background-color: var(--grid-default-background); - - &.succeeded > a, - &.failed > a { - padding-left: 25px; - background-position: left center; - background-repeat: no-repeat; - background-size: 18px; - } - - &.succeeded { - background-color: var(--grid-succeeded-background); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + gap: 5px; + + .result { + display: flex; + gap: 0.5ch; + align-items: center; + justify-content: center; + min-height: 30px; + font-size: 14px; + font-weight: 600; + + &.pending, + &.unknown { + color: var(--grid-default-text); + background-color: var(--grid-default-background); + } - a { - background-image: var(--image-build-succeeded); + &.compatible { + color: var(--grid-compatible-text); + background-color: var(--grid-compatible-background); } - } - &.failed { - background-color: var(--grid-failed-background); + &.incompatible { + color: var(--grid-incompatible-text); + background-color: var(--grid-incompatible-background); + } - a { - background-image: var(--image-build-failed); + small { + font-size: 9px; } } - > .generated-docs { - position: absolute; - right: 5px; - display: inline-block; - width: 25px; - height: 25px; - background-position: center; - background-repeat: no-repeat; - background-size: 15px; - background-color: var(--grid-callout-background); - background-image: var(--image-documentation); - border-radius: 50%; + @media screen and (max-width: $mobile-breakpoint) { + grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); + grid-auto-rows: 1fr; } } } diff --git a/FrontEnd/styles/package.scss b/FrontEnd/styles/package.scss index a175efec9..b19bfd1ab 100644 --- a/FrontEnd/styles/package.scss +++ b/FrontEnd/styles/package.scss @@ -393,7 +393,7 @@ span.beta, span.branch { padding-right: 18px; font-weight: 600; - background-position: top 2px right; + background-position: center right; background-repeat: no-repeat; background-size: 14px; } diff --git a/Sources/App/Views/PackageController/Builds/BuildIndex+View.swift b/Sources/App/Views/PackageController/Builds/BuildIndex+View.swift index 8ba3a36da..cc3bda51a 100644 --- a/Sources/App/Views/PackageController/Builds/BuildIndex+View.swift +++ b/Sources/App/Views/PackageController/Builds/BuildIndex+View.swift @@ -87,7 +87,7 @@ enum BuildIndex { }) ), .ul( - .class("matrix builds"), + .class("build-results"), .group(model.buildMatrix[swiftVersion].map(\.node)) ) ) diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index fd199650e..41db21957 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -578,8 +578,8 @@ extension API.PackageController.GetRoute.Model { return .a( .href(SiteURL.package(.value(repositoryOwner), .value(repositoryName), .builds).relativeURL()), .ul( - .class("matrix compatibility"), - .forEach(rows) { compatibilityListItem($0.labelParagraphNode, cells: $0.results.all) } + .class("matrix"), + .forEach(rows) { compatibilityListItem($0.labelNode, cells: $0.results.all) } ) ) } @@ -590,26 +590,21 @@ extension API.PackageController.GetRoute.Model { return .a( .href(SiteURL.package(.value(repositoryOwner), .value(repositoryName), .builds).relativeURL()), .ul( - .class("matrix compatibility"), - .forEach(rows) { compatibilityListItem($0.labelParagraphNode, cells: $0.results.all) } + .class("matrix"), + .forEach(rows) { compatibilityListItem($0.labelNode, cells: $0.results.all) } ) ) } func compatibilityListItem( - _ labelParagraphNode: Node, + _ labelNode: Node, cells: [CompatibilityMatrix.BuildResult] ) -> Node { return .li( - .class("row"), - .div( - .class("row-labels"), - labelParagraphNode - ), - // Matrix CSS should include *both* the column labels, and the column values status boxes in *every* row. + .class("version"), .div( - .class("column-labels"), - .forEach(cells) { $0.headerNode } + .class("label"), + labelNode ), .div( .class("results"), @@ -681,20 +676,15 @@ private extension License.Kind { private extension CompatibilityMatrix.BuildResult where T: BuildResultPresentable { - var headerNode: Node { + var cellNode: Node { .div( + .class("result \(status.cssClass)"), + .title(title), .text(parameter.displayName), .unwrap(parameter.note) { .small(.text("(\($0))")) } ) } - var cellNode: Node { - .div( - .class("\(status.cssClass)"), - .title(title) - ) - } - var title: String { switch status { case .compatible: diff --git a/Sources/App/Views/PackageController/PackageShow.swift b/Sources/App/Views/PackageController/PackageShow.swift index 5520178a2..666cb021c 100644 --- a/Sources/App/Views/PackageController/PackageShow.swift +++ b/Sources/App/Views/PackageController/PackageShow.swift @@ -20,13 +20,6 @@ enum PackageShow { var name: String var kind: App.Version.Kind - var node: Node { - .span( - .class(cssClass), - .text(name) - ) - } - var cssClass: String { switch kind { case .defaultBranch: return "branch" @@ -34,6 +27,13 @@ enum PackageShow { case .release: return "stable" } } + + func node(longest: Bool = false) -> Node { + .span( + .class(longest ? "longest \(cssClass)" : cssClass), + .text(name) + ) + } } struct BuildStatusRow: Codable, Equatable, Sendable { @@ -50,13 +50,19 @@ enum PackageShow { self.results = namedResult.results } - var labelParagraphNode: Node { + var labelNode: Node { guard !references.isEmpty else { return .empty } - return .p( - .group( - listPhrase(nodes: references.map(\.node)) - ) - ) + + let longestReference = references.map(\.name).max(by: { $0.count < $1.count }) ?? "" + + var versionsAndSeparators = references.flatMap { reference -> [Node] in + let isLongest = reference.name == longestReference + return [reference.node(longest: isLongest), .span(.class("separator"), .text("/"))] + } + if versionsAndSeparators.isEmpty == false { + versionsAndSeparators.removeLast() + } + return .group(versionsAndSeparators) } } } diff --git a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/BuildIndex_document.1.html b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/BuildIndex_document.1.html index 4934741af..05d76d736 100644 --- a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/BuildIndex_document.1.html +++ b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/BuildIndex_document.1.html @@ -105,7 +105,7 @@

Build Results


Swift 6.1

-