diff --git a/cypress/integration/theme.spec.js b/cypress/integration/theme.spec.js new file mode 100644 index 000000000..d6223ca55 --- /dev/null +++ b/cypress/integration/theme.spec.js @@ -0,0 +1,94 @@ +const A11Y_OPTS = { + runOnly: { + type: "tag", + values: ["section508", "best-practice", "wcag21aa", "wcag2aa"] + } +}; + +context("Accessibility (a11y)", () => { + context("Logged out", () => { + it.skip("overview", () => { + cy.clearSession(); + cy.setTheme("theme-light"); + cy.visit("/account/login"); + cy.injectAxe(); + cy.wait(500); + cy.checkA11y(A11Y_OPTS); + }); + }); + + context("Logged in", () => { + beforeEach(() => { + cy.clearSession(); + cy.setTheme("theme-light"); + cy.server(); + // overview page + cy.route("GET", "*api/v1/repos*", "fixture:overview_page.json"); + // add repos page + cy.route( + "GET", + "*api/v1/user/source/repos*", + "fixture:add_repositories.json" + ); + // settings page + cy.route("GET", "*api/v1/repos/*/octocat", "fixture:repository.json"); + // repo and build page + cy.stubBuilds(); + cy.stubBuild(); + cy.stubStepsWithLogs(); + // hooks page + cy.route("GET", "*api/v1/hooks/github/octocat*", "fixture:hooks_5.json"); + cy.route( + "GET", + "*api/v1/repos/*/octocat/builds/1*", + "fixture:build_success.json" + ); + cy.route( + "GET", + "*api/v1/repos/*/octocat/builds/2*", + "fixture:build_failure.json" + ); + cy.route( + "GET", + "*api/v1/repos/*/octocat/builds/3*", + "fixture:build_running.json" + ); + }); + after(() => { + cy.visit("/"); + cy.server({ enable: false }); + }); + + it.skip("overview", () => { + cy.checkA11yForPage("/", A11Y_OPTS); + }); + + it.skip("add repos", () => { + cy.checkA11yForPage("/account/add-repos", A11Y_OPTS); + }); + + it.skip("settings", () => { + cy.checkA11yForPage("/github/octocat/settings", A11Y_OPTS); + }); + + it.skip("repo page", () => { + cy.checkA11yForPage("/someorg/somerepo", A11Y_OPTS); + }); + + it.skip("hooks page", () => { + cy.login("/github/octocat/hooks"); + cy.injectAxe(); + cy.wait(500); + cy.get("[data-test=hook]").click({ multiple: true }); + cy.checkA11y(A11Y_OPTS); + }); + + it.skip("build page", () => { + cy.login("/someorg/somerepo/1"); + cy.injectAxe(); + cy.wait(500); + cy.get("[data-test=step-header]").click({ multiple: true }); + cy.checkA11y(A11Y_OPTS); + }); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 6f15a8d74..79100f278 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -186,3 +186,9 @@ Cypress.Commands.add("checkA11yForPage", (path = "/", opts = {}) => { cy.wait(500); cy.checkA11y(opts); }); + +Cypress.Commands.add("setTheme", theme => { + cy.window().then(win => { + win.localStorage.setItem("vela-theme", theme); + }); +}); diff --git a/src/elm/Build.elm b/src/elm/Build.elm index 7b9bd21c6..702f8df37 100755 --- a/src/elm/Build.elm +++ b/src/elm/Build.elm @@ -528,7 +528,7 @@ buildStatusStyles markdown buildStatus buildNumber = [ div [ class "build-animation", class "-not-running", statusToClass buildStatus ] [] ] in - List.append animation markdown + markdown ++ animation {-| topParticles : returns an svg frame to parallax scroll on a running build, set to the top of the build diff --git a/src/elm/Interop.elm b/src/elm/Interop.elm index a55950e6f..88bbaf286 100644 --- a/src/elm/Interop.elm +++ b/src/elm/Interop.elm @@ -4,21 +4,35 @@ Use of this source code is governed by the LICENSE file in this repository. --} -port module Interop exposing (onSessionChange, storeSession) +port module Interop exposing (onSessionChange, onThemeChange, setTheme, storeSession) import Json.Decode as Decode import Json.Encode as Encode --- inbound port +-- SESSION +{-| inbound +-} port onSessionChange : (Decode.Value -> msg) -> Sub msg +{-| outbound +-} +port storeSession : Encode.Value -> Cmd msg --- outbound port -port storeSession : Encode.Value -> Cmd msg +-- THEME + + +{-| inbound +-} +port onThemeChange : (Decode.Value -> msg) -> Sub msg + + +{-| outbound +-} +port setTheme : Encode.Value -> Cmd msg diff --git a/src/elm/Main.elm b/src/elm/Main.elm index f57fb8107..12eb27e1e 100644 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -108,6 +108,7 @@ import Vela , Step , StepNumber , Steps + , Theme(..) , UpdateRepositoryPayload , User , Viewing @@ -115,6 +116,7 @@ import Vela , buildUpdateRepoIntPayload , buildUpdateRepoStringPayload , decodeSession + , decodeTheme , defaultAddRepositoryPayload , defaultBuilds , defaultHooks @@ -122,7 +124,9 @@ import Vela , defaultSession , encodeAddRepository , encodeSession + , encodeTheme , encodeUpdateRepository + , stringToTheme ) @@ -138,6 +142,7 @@ type alias Flags = , velaFeedbackURL : String , velaDocsURL : String , velaSession : Maybe Session + , velaTheme : String } @@ -165,6 +170,7 @@ type alias Model = , inTimeout : Maybe Int , entryURL : Url , hookBuilds : HookBuilds + , theme : Theme } @@ -226,6 +232,7 @@ init flags url navKey = , inTimeout = Nothing , entryURL = url , hookBuilds = Dict.empty + , theme = stringToTheme flags.velaTheme } ( newModel, newPage ) = @@ -240,6 +247,9 @@ init flags url navKey = ( newModel , Cmd.batch [ newPage + + -- for themes, we rely on ports to apply the class on + , Interop.setTheme <| encodeTheme model.theme , setTimeZone , setTime ] @@ -259,6 +269,7 @@ type Msg | ChangeRepoTimeout String | RefreshSettings Org Repo | ClickHook Org Repo BuildNumber + | SetTheme Theme | ClickLogLine Org Repo BuildNumber StepNumber Int | ClickStep Org Repo (Maybe BuildNumber) (Maybe StepNumber) | GotoPage Pagination.Page @@ -584,6 +595,13 @@ update msg model = , action ) + SetTheme theme -> + if theme == model.theme then + ( model, Cmd.none ) + + else + ( { model | theme = theme }, Interop.setTheme <| encodeTheme theme ) + ClickLogLine org repo buildNumber stepNumber lineNumber -> let ( steps, action ) = @@ -714,6 +732,7 @@ subscriptions : Model -> Sub Msg subscriptions model = Sub.batch [ Interop.onSessionChange decodeOnSessionChange + , Interop.onThemeChange decodeOnThemeChange , every Util.oneSecondMillis <| Tick OneSecond , every Util.fiveSecondsMillis <| Tick (FiveSecond <| refreshData model) ] @@ -734,6 +753,16 @@ decodeOnSessionChange sessionJson = SessionChanged Nothing +decodeOnThemeChange : Decode.Value -> Msg +decodeOnThemeChange inTheme = + case Decode.decodeValue decodeTheme inTheme of + Ok theme -> + SetTheme theme + + Err _ -> + SetTheme Dark + + {-| refreshPage : refreshes Vela data based on current page and build status -} refreshPage : Model -> RefreshData -> Cmd Msg @@ -917,7 +946,7 @@ view model = in { title = "Vela - " ++ title , body = - [ lazy2 viewHeader model.session { feedbackLink = model.velaFeedbackURL, docsLink = model.velaDocsURL } + [ lazy2 viewHeader model.session { feedbackLink = model.velaFeedbackURL, docsLink = model.velaDocsURL, theme = model.theme } , viewNav model , div [ class "util" ] [ Build.viewBuildHistory model.time model.zone model.page model.builds.org model.builds.repo model.builds.builds ] , main_ [] @@ -1425,8 +1454,8 @@ navButton model = text "" -viewHeader : Maybe Session -> { feedbackLink : String, docsLink : String } -> Html Msg -viewHeader maybeSession { feedbackLink, docsLink } = +viewHeader : Maybe Session -> { feedbackLink : String, docsLink : String, theme : Theme } -> Html Msg +viewHeader maybeSession { feedbackLink, docsLink, theme } = let session : Session session = @@ -1452,13 +1481,28 @@ viewHeader maybeSession { feedbackLink, docsLink } = ] ] , div [ class "help-links" ] - [ a [ href feedbackLink, attribute "aria-label" "go to feedback" ] [ text "feedback" ] + [ viewThemeToggle theme + , a [ href feedbackLink, attribute "aria-label" "go to feedback" ] [ text "feedback" ] , a [ href docsLink, attribute "aria-label" "go to docs" ] [ text "docs" ] , FeatherIcons.terminal |> FeatherIcons.withSize 18 |> FeatherIcons.toHtml [] ] ] +viewThemeToggle : Theme -> Html Msg +viewThemeToggle theme = + let + ( newTheme, icon, themeAria ) = + case theme of + Dark -> + ( Light, SvgBuilder.themeLight, "activate light mode" ) + + Light -> + ( Dark, SvgBuilder.themeDark, "activate dark mode" ) + in + button [ class "theme-toggle", attribute "aria-label" themeAria, onClick (SetTheme newTheme) ] [ icon 24 ] + + -- HELPERS diff --git a/src/elm/Main/index.d.ts b/src/elm/Main/index.d.ts index b5a9f9345..9e3203502 100644 --- a/src/elm/Main/index.d.ts +++ b/src/elm/Main/index.d.ts @@ -57,6 +57,8 @@ export type Flags = { readonly velaDocsURL: string; /** @property velaSession used for passsing in an existing Vela session to Elm */ readonly velaSession: Session | null; + /** @property velaTheme: Theme | null */ + readonly velaTheme: Theme; }; /** @@ -66,6 +68,8 @@ export type Flags = { export type Ports = { readonly storeSession: ToJS; readonly onSessionChange: ToElm; + readonly setTheme: ToJS; + readonly onThemeChange: ToElm; }; /** @@ -86,7 +90,7 @@ export type ToElm = { }; /** - * The format of the session that we are working with in Vela + * The shape of session that we are working with in Vela * */ export type Session = { @@ -94,3 +98,9 @@ export type Session = { readonly token: string; readonly entrypoint: string; }; + +/** + * Supported themes + * + */ +export type Theme = "theme-light" | "theme-dark"; diff --git a/src/elm/SvgBuilder.elm b/src/elm/SvgBuilder.elm index 18c5fd51b..a4d19e99d 100644 --- a/src/elm/SvgBuilder.elm +++ b/src/elm/SvgBuilder.elm @@ -24,6 +24,8 @@ module SvgBuilder exposing , stepRunning , stepStatusToIcon , stepSuccess + , themeDark + , themeLight , velaLogo ) @@ -69,22 +71,53 @@ velaLogo size = ] +themeDark : Int -> Html msg +themeDark size = + svg + [ width <| String.fromInt size + , height <| String.fromInt size + , viewBox "0 0 24 24" + , class "theme-dark-icon" + ] + [ Svg.path [ d "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" ] [] + ] + + +themeLight : Int -> Html msg +themeLight size = + svg + [ width <| String.fromInt size + , height <| String.fromInt size + , stroke "currentColor" + , strokeWidth "2" + , viewBox "0 0 24 24" + , class "theme-light-icon" + ] + [ Svg.circle [ cx "12", cy "12", r "5" ] [] + , Svg.line [ x1 "12", y1 "1", x2 "12", y2 "3" ] [] + , Svg.line [ x1 "12", y1 "21", x2 "12", y2 "23" ] [] + , Svg.line [ x1 "4.22", y1 "4.22", x2 "5.64", y2 "5.64" ] [] + , Svg.line [ x1 "18.36", y1 "18.36", x2 "19.78", y2 "19.78" ] [] + , Svg.line [ x1 "1", y1 "12", x2 "3", y2 "12" ] [] + , Svg.line [ x1 "21", y1 "12", x2 "23", y2 "12" ] [] + , Svg.line [ x1 "4.22", y1 "19.78", x2 "5.64", y2 "18.36" ] [] + , Svg.line [ x1 "18.36", y1 "5.64", x2 "19.78", y2 "4.22" ] [] + ] + + {-| buildPending : produces svg icon for build status - pending -} buildPending : Html msg buildPending = svg [ class "build-icon -pending" - , strokeWidth "2" , viewBox "0 0 408 408" , width "44" , height "44" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-build-status-icon", class "-pending" ] - [ Svg.path [ d "M51 153c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm306 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm-153 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51z" ] - [] - ] + [ Svg.path [ d "M51 153c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm306 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm-153 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51z" ] + [] ] @@ -93,17 +126,15 @@ buildPending = buildRunning : Html msg buildRunning = svg - [ class "build-icon" + [ class "build-icon -running" , strokeWidth "2" , viewBox "0 0 44 44" , width "44" , height "44" , ariaHidden ] - [ Svg.g [ class "status-svg" ] - [ Svg.path [ class "-linecap-round", d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] - , Svg.path [ class "-linecap-square", d "M22 10v12.75L30 27" ] [] - ] + [ Svg.path [ d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] + , Svg.path [ d "M22 10v12.75L30 27" ] [] ] @@ -112,17 +143,15 @@ buildRunning = buildSuccess : Html msg buildSuccess = svg - [ class "build-icon" + [ class "build-icon -success" , strokeWidth "2" , viewBox "0 0 44 44" , width "44" , height "44" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-linecap-square" ] - [ Svg.path [ d "M15 20.1l6.923 6.9L42 5" ] [] - , Svg.path [ class "-linecap-round", d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] - ] + [ Svg.path [ d "M15 20.1l6.923 6.9L42 5" ] [] + , Svg.path [ d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] ] @@ -131,17 +160,15 @@ buildSuccess = buildFailure : Html msg buildFailure = svg - [ class "build-icon" + [ class "build-icon -failure" , strokeWidth "2" , viewBox "0 0 44 44" , width "44" , height "44" , ariaHidden ] - [ Svg.g [ class "status-svg" ] - [ Svg.path [ class "-linecap-round", d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] - , Svg.path [ class "-linecap-square", d "M15 15l14 14M29 15L15 29" ] [] - ] + [ Svg.path [ d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] + , Svg.path [ d "M15 15l14 14M29 15L15 29" ] [] ] @@ -164,8 +191,8 @@ buildStatusAnimation dashes y classNames = List.append classes [ class "build-animation" , strokeWidth "4" - , width "144" - , height "144" + , width "" + , height "4" , viewBox "" , ariaHidden ] @@ -182,16 +209,16 @@ stepPending : Html msg stepPending = svg [ class "-icon -pending" - , strokeWidth "2" , viewBox "0 0 408 408" , width "32" , height "32" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-step-icon", class "-pending" ] - [ Svg.path [ d "M51 153c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm306 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm-153 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51z" ] - [] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M51 153c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm306 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51zm-153 0c-28.05 0-51 22.95-51 51s22.95 51 51 51 51-22.95 51-51-22.95-51-51-51z" ] + [] ] @@ -200,17 +227,23 @@ stepPending = stepRunning : Html msg stepRunning = svg - [ class "-icon" + [ class "-icon -running" , strokeWidth "2" , viewBox "0 0 44 44" , width "32" , height "32" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-step-icon", class "-running" ] - [ Svg.path [ class "-linecap-round", d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] - , Svg.path [ class "-linecap-square", d "M22 10v12.75L30 27" ] [] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M22 10v12.75L30 27" + ] + [] ] @@ -219,17 +252,23 @@ stepRunning = stepSuccess : Html msg stepSuccess = svg - [ class "-icon" + [ class "-icon -success" , strokeWidth "2" , viewBox "0 0 44 44" , width "32" , height "32" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-linecap-square", class "-step-icon", class "-success" ] - [ Svg.path [ d "M15 20.1l6.923 6.9L42 5" ] [] - , Svg.path [ class "-linecap-round", d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M15 20.1l6.923 6.9L42 5" ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" + ] + [] ] @@ -238,17 +277,23 @@ stepSuccess = stepFailure : Html msg stepFailure = svg - [ class "-icon" + [ class "-icon -failure" , strokeWidth "2" , viewBox "0 0 44 44" , width "32" , height "32" , ariaHidden ] - [ Svg.g [ class "status-svg", class "-step-icon", class "-failure" ] - [ Svg.path [ class "-linecap-round", d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] - , Svg.path [ class "-linecap-square", d "M15 15l14 14M29 15L15 29" ] [] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" + ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M15 15l14 14M29 15L15 29" ] + [] ] @@ -259,17 +304,14 @@ hookSuccess = svg [ class "hook-status" , class "-success" - , class "-no-fill" , strokeWidth "2" , viewBox "0 0 44 44" , width "20" , height "20" , ariaHidden ] - [ Svg.g [] - [ Svg.path [ d "M15 20.1l6.923 6.9L42 5" ] [] - , Svg.path [ d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] - ] + [ Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M15 20.1l6.923 6.9L42 5" ] [] + , Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] ] @@ -280,16 +322,13 @@ hookFailure = svg [ class "hook-status" , class "-failure" - , class "-no-fill" , strokeWidth "2" , viewBox "0 0 44 44" , width "20" , height "20" ] - [ Svg.g [] - [ Svg.path [ d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] - , Svg.path [ d "M15 15l14 14M29 15L15 29" ] [] - ] + [ Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] + , Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M15 15l14 14M29 15L15 29" ] [] ] @@ -299,19 +338,11 @@ buildHistoryPending : Int -> Html msg buildHistoryPending _ = svg [ class "-icon -pending" - , strokeWidth "2" , viewBox "0 0 28 28" - , width "28" - , height "28" - ] - [ Svg.g [ class "build-history-svg", class "-build-history-icon", class "-pending" ] - [ Svg.path [ class "-path-1", d "M-281-124h1440V900H-281z" ] [] - , Svg.g [] - [ Svg.path [ class "-path-2", d "M0 0h28v28H0z" ] [] - , Svg.circle [ class "-path-3", cx "14", cy "14", r "2" ] [] - ] - ] + , width "26" + , height "26" ] + [ Svg.circle [ cx "14", cy "14", r "2" ] [] ] {-| buildHistoryRunning : produces svg icon for build history status - running @@ -322,17 +353,10 @@ buildHistoryRunning _ = [ class "-icon -running" , strokeWidth "2" , viewBox "0 0 28 28" - , width "28" - , height "28" - ] - [ Svg.g [ class "build-history-svg", class "-build-history-icon", class "-running" ] - [ Svg.path [ class "-path-1", d "M-309-124h1440V900H-309z" ] [] - , Svg.g [] - [ Svg.path [ class "-path-2", d "M0 0h28v28H0z" ] [] - , Svg.path [ class "-path-3", d "M14 7v7.5l5 2.5" ] [] - ] - ] + , width "26" + , height "26" ] + [ Svg.path [ d "M14 7v7.5l5 2.5" ] [] ] {-| buildHistorySuccess : produces svg icon for build history status - running @@ -343,17 +367,10 @@ buildHistorySuccess _ = [ class "-icon -success" , strokeWidth "2" , viewBox "0 0 28 28" - , width "28" - , height "28" - ] - [ Svg.g [ class "build-history-svg", class "-build-history-icon", class "-success" ] - [ Svg.path [ class "-path-1", d "M-113-124h1440V900H-113z" ] [] - , Svg.g [] - [ Svg.path [ class "-path-2", d "M0 0h28v28H0z" ] [] - , Svg.path [ class "-path-3", d "M6 15.9227L10.1026 20 22 7" ] [] - ] - ] + , width "26" + , height "26" ] + [ Svg.path [ d "M6 15.9227L10.1026 20 22 7" ] [] ] {-| buildHistoryFailure : produces svg icon for build history status - failure @@ -364,17 +381,10 @@ buildHistoryFailure _ = [ class "-icon -failure" , strokeWidth "2" , viewBox "0 0 28 28" - , width "28" - , height "28" - ] - [ Svg.g [ class "build-history-svg", class "-build-history-icon" ] - [ Svg.path [ class "-path-1", d "M-253-124h1440V900H-253z" ] [] - , Svg.g [] - [ Svg.path [ class "-path-2", d "M0 0h28v28H0z" ] [] - , Svg.path [ class "-path-3", d "M8 8l12 12M20 8L8 20" ] [] - ] - ] + , width "26" + , height "26" ] + [ Svg.path [ d "M8 8l12 12M20 8L8 20" ] [] ] {-| radio : produces svg icon for input radio select @@ -382,29 +392,21 @@ buildHistoryFailure _ = radio : Bool -> Html msg radio checked = svg - [ class "-icon -radio" - , strokeWidth "1" - , viewBox "0 0 28 28" + [ class "-icon" + , class "-radio" + , strokeWidth "2" + , viewBox "0 0 30 30" , width "22" , height "22" ] <| if checked then - [ Svg.g [ fill "none", Svg.Attributes.fillRule "evenodd" ] - [ Svg.path [ fill "#282828", d "M-414-909h1440V115H-414z" ] [] - , Svg.g [] - [ Svg.circle [ stroke "#0CF", cx "14", cy "14", r "13.5" ] [] - , Svg.circle [ fill "#0CF", stroke "#0CF", cx "14", cy "14", r "7" ] [] - ] - ] + [ Svg.circle [ cx "15", cy "15", r "13" ] [] + , Svg.circle [ class "-inner", cx "15", cy "15", r "6" ] [] ] else - [ Svg.g [ fill "none", Svg.Attributes.fillRule "evenodd" ] - [ Svg.circle [ stroke "#0CF", cx "14", cy "14", r "13.5" ] [] - , Svg.circle [ stroke "#0CF", cx "14", cy "14", r "13.5" ] [] - , Svg.path [ stroke "none", d "M.5.5h27v27H.5z" ] [] - ] + [ Svg.circle [ cx "15", cy "15", r "13" ] [] ] @@ -413,25 +415,18 @@ radio checked = checkbox : Bool -> Html msg checkbox checked = svg - [ class "-icon -radio" - , strokeWidth "1" + [ class "-icon -check" + , strokeWidth "2" , viewBox "0 0 28 28" , width "22" , height "22" ] <| if checked then - [ Svg.g [ fill "none", Svg.Attributes.fillRule "evenodd" ] - [ Svg.path [ stroke "#0CF", fill "#0CF", d "M.5.5h27v27H.5z" ] [] - , Svg.path [ stroke "#282828", Svg.Attributes.fillRule "2", Svg.Attributes.strokeLinecap "square", d "M6 15.9227L10.1026 20 22 7" ] [] - ] - ] + [ Svg.path [ class "-checked", strokeLinecap "square", d "M6 15.9227L10.1026 20 22 7" ] [] ] else - [ Svg.g [ fill "none", Svg.Attributes.fillRule "evenodd" ] - [ Svg.path [ stroke "#0CF", fill "none", d "M.5.5h27v27H.5z" ] [] - ] - ] + [] {-| statusToIcon : takes build status string and returns Icon from SvgBuilder diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index 946c6b560..b123414ce 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -31,6 +31,7 @@ module Vela exposing , Step , StepNumber , Steps + , Theme(..) , UpdateRepositoryPayload , User , Viewing @@ -48,6 +49,7 @@ module Vela exposing , decodeSourceRepositories , decodeStep , decodeSteps + , decodeTheme , decodeUser , defaultAddRepositoryPayload , defaultBuilds @@ -58,7 +60,9 @@ module Vela exposing , defaultUser , encodeAddRepository , encodeSession + , encodeTheme , encodeUpdateRepository + , stringToTheme ) import Dict exposing (Dict) @@ -73,6 +77,11 @@ import RemoteData exposing (RemoteData(..), WebData) -- COMMON +type Theme + = Light + | Dark + + type alias Org = String @@ -90,6 +99,39 @@ type alias StepNumber = +-- THEME + + +stringToTheme : String -> Theme +stringToTheme theme = + case theme of + "theme-light" -> + Light + + _ -> + Dark + + +decodeTheme : Decoder Theme +decodeTheme = + Decode.string + |> Decode.andThen + (\str -> + Decode.succeed <| stringToTheme str + ) + + +encodeTheme : Theme -> Encode.Value +encodeTheme theme = + case theme of + Light -> + Encode.string "theme-light" + + _ -> + Encode.string "theme-dark" + + + -- SESSION diff --git a/src/scss/_main.scss b/src/scss/_main.scss new file mode 100644 index 000000000..8e3c6e7e4 --- /dev/null +++ b/src/scss/_main.scss @@ -0,0 +1,1731 @@ +// Copyright (c) 2019 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +html { + font-family: var(--font-family); +} + +body { + color: var(--color-text); + background-color: var(--color-bg); + + transition-property: background-color, color; + transition-duration: 0.2s; + + font-size: 18px; +} + +a { + color: var(--color-primary); + &:hover { + text-decoration: none; + } +} + +header { + display: flex; + align-items: center; + justify-content: space-between; + + padding: 1em var(--horizontal-pad); + + border-top-width: 0; + border-bottom: var(--line-width) solid; + border-left-width: 0; + border-image-source: linear-gradient( + to right, + var(--color-primary) 55%, + transparent 55%, + transparent 58%, + var(--color-primary) 58%, + var(--color-primary) 69%, + transparent 69%, + transparent 75%, + var(--color-secondary) 75%, + var(--color-secondary) 76%, + transparent 76% + ); + border-image-slice: 1; + background: radial-gradient( + circle at 50% -200%, + var(--color-bg-light) -7%, + transparent 50% + ); +} + +.details { + > .summary { + position: relative; + + overflow: hidden; + + padding-right: 1.5em; + padding-bottom: 0.3em; + + cursor: pointer; + + // hide details marker on Chrome + &::-webkit-details-marker { + display: none !important; + } + + //hide details marker on Firefox + &:first-of-type { + list-style-type: none !important; + } + + // super hacky way to remove + // marker on details element + // + // text-indent: -1em; + // Firefox - hide marker + // &::-webkit-details-marker, + // &::marker { + // display: none; + // color: var(--color-coal); + // } + // also could work with: + // display: block; + // nothing seems to work on Chrome + } + .details-icon-expand { + position: absolute; + top: 0.2em; + right: 0; + + transition: all 0.2s; + transform-origin: 50% 49%; + + color: var(--color-primary); + } + &[open] .details-icon-expand { + transform: rotate(0.5turn); + } +} + +.identity { + display: flex; + align-items: center; +} + +.identity-logo-link { + margin-right: 1em; + + text-decoration: none; + // override global `vertical-align: middle` for SVGs + svg { + vertical-align: initial; + } +} + +// Vela logo +.vela-logo { + &-star { + fill: var(--color-lavender); + } + &-outer { + fill: var(--color-cyan); + } + &-inner { + fill: var(--color-text); + } +} + +.help-links { + display: flex; + align-items: center; + + font-size: 80%; + > a { + position: relative; + + display: inline-block; + + margin-right: 1em; + &:not(:last-child)::after { + @include slashes; + } + &:last-child { + text-decoration: none; + + font-weight: bold; + } + } +} + +.cli-help { + vertical-align: top; +} + +.identity-name { + position: relative; + z-index: 9999; + ul { + position: absolute; + top: 1rem; + left: 0; + + width: max-content; + min-width: 100%; + padding: 0; + + list-style: none; + + border: 1px solid var(--color-bg-light); + background-color: var(--color-bg-dark); + box-shadow: 0 0 2px black; + + font-size: 80%; + > li { + padding: 0.5em 1em; + } + @supports (clip-path: inset(50%)) { + &::after { + position: absolute; + top: -5px; + left: calc(50% - 6px); + + display: block; + + width: 10px; + height: 10px; + + content: ""; + transform: rotate(135deg); + + border: inherit; + border-radius: 0 0 0 0.25em; + background-color: inherit; + + clip-path: polygon(0% 0%, 100% 100%, 0% 100%); + } + } + } +} + +.theme-light .identity-name ul { + background: var(--color-white); +} + +.content-wrap { + margin: 0 var(--horizontal-pad) var(--horizontal-pad); +} + +button, +.button { + margin: 0.5em 0; + padding: 0.5em 2em; + + cursor: pointer; + text-decoration: none; + + color: var(--color-bg); + border: none; + background-image: linear-gradient( + to right, + var(--color-cyan) 0%, + var(--color-lavender) 60%, + var(--color-cyan) 100% + ); + background-size: 200% auto; + + font-size: 16px; + font-weight: bold; + + & + button, + + .button { + margin-left: 0.5em; + } + &:hover { + background-position: right center; + } + &:disabled { + color: var(--color-bg-dark); + border-color: var(--color-bg-light); + background-color: var(--color-bg-light); + background-image: none; + } + &.inverted { + color: var(--color-primary); + border-width: var(--line-width); + border-style: solid; + border-color: var(--color-primary); + background-color: var(--color-bg); + background-image: none; + &:not([disabled]):hover { + color: var(--color-text); + border-color: var(--color-text); + } + &:disabled { + pointer-events: none; + color: var(--color-bg-light); + border-color: var(--color-bg-light); + } + } +} + +.navigation { + display: flex; + align-items: center; + justify-content: space-between; + + padding: 0 3em; + + border-top-width: 0; + border-bottom: var(--line-width) solid; + border-left-width: 0; + border-image-source: linear-gradient( + to right, + var(--color-bg-light) 15%, + transparent 15%, + transparent 18%, + var(--color-bg-light) 18%, + var(--color-bg-light) 18.8%, + transparent 18.8% + ); + border-image-slice: 1; + li { + position: relative; + + display: inline-block; + + margin-right: 1em; + &:not(:last-child)::after { + @include slashes; + } + &:last-child { + text-decoration: none; + + font-weight: bold; + } + } +} + +.login-source-icon { + display: inline-block; + + margin-right: 0.6em; +} + +.overview { + line-height: 2em; +} + +.repo-item { + position: relative; + + display: flex; + justify-content: space-between; + + margin: 2em 0; + padding: 0.5em; + + border-top: var(--line-width) solid; + border-left: var(--line-width) solid; + border-image-source: linear-gradient( + to right, + var(--color-bg-light) 75%, + transparent 75%, + transparent 77%, + var(--color-bg-light) 77%, + var(--color-bg-light) 84%, + transparent 84% + ); + border-image-slice: 1; + > summary { + margin: 0.5em 0 0 1em; + + text-indent: 0; + } +} + +.-item { + display: flex; + align-items: center; + justify-content: space-between; + + width: 100%; + margin: 0.5em 0 0 1em; + padding: 0.5em 1em; + + background-color: var(--color-bg-dark); +} + +.theme-light .-item { + background-color: var(--color-white); + border: 1px solid var(--color-bg-dark); +} + +.-item .-actions { + display: flex; + align-items: center; +} + +.-actions .-view { + padding: 0.33em 2em; + + @extend .button; +} + +.-actions .-view > a { + text-decoration: none; + + color: var(--color-bg); +} + +// loading ellipsis +.loading-ellipsis::after { + display: inline-block; + overflow: hidden; + + width: 0; + + content: "\2026"; + /* ellipsis character */ + animation: ellipsis steps(4, end) 900ms infinite; + vertical-align: bottom; +} + +.util { + display: flex; + + height: 3em; +} + +.source-actions { + display: flex; + align-items: center; + justify-content: space-between; +} + +.source-actions .-filter, +.source-repos .-filter { + display: flex; + align-items: center; + flex: 1; + flex-direction: row; + + margin: 0 1em; + + border-bottom: 2px solid var(--color-primary); +} + +.source-actions .-filter input, +.source-repos .-filter input { + width: 100%; + padding: 0.5em 0.8em; + + color: var(--color-text); + border: none; + background: var(--color-bg); + + font-size: 18px; + &::placeholder { + color: var(--color-bg-light); + } +} + +.org .summary { + display: flex; + align-items: center; + flex-direction: row; + &::-webkit-details-marker { + display: flex; + + margin-right: 16px; + + vertical-align: middle; + } +} + +.filtered-repos { + margin-top: 2em; +} + +.-no-repos { + width: 100%; + margin: 0.5em 0 0 1em; + padding: 1.2em 1em; + + background-color: var(--color-bg-dark); +} + +.org-header { + display: flex; + align-items: center; + flex: 1; +} + +.repo-count { + margin-left: 12px; + &::before { + margin-right: 0.3em; + + content: "["; + } + &::after { + margin-left: 0.3em; + + content: "]"; + } +} + +.repo-add { + display: flex; + align-items: center; + + min-width: 150px; + margin: 0.425em 0; + padding: 0.2em 1em; + + vertical-align: top; + > svg { + margin-right: 1em; + } +} + +.-added-container { + display: flex; + flex-direction: row; +} + +.repo-add--added { + color: var(--color-green); + border: var(--line-width) solid var(--color-green); + + @extend .repo-add; +} + +.repo-add--adding { + color: var(--color-yellow); + border: var(--line-width) solid var(--color-yellow); + + @extend .repo-add; +} + +.repo-add--adding-text { + margin-left: 12px; +} + +.repo-add--failed { + cursor: pointer; + + color: var(--color-red); + border: var(--line-width) solid var(--color-red); + background: none; + + @extend .repo-add; + + &:hover svg { + transition: transform 0.2s ease-in-out; + transform: rotate(70deg); + } + + > svg { + margin-right: 1em; + + transition: transform 0.2s ease-in-out; + } +} + +a.-btn { + margin: 0.5em 0; + padding: 0.3em 2em; + + text-decoration: none; + + font-size: 16px; + font-weight: bold; +} + +.-btn.-inverted, +button.-inverted { + color: var(--color-primary); + border-width: var(--line-width); + border-style: solid; + border-color: var(--color-primary); + background-color: var(--color-bg); + background-image: none; + &:not([disabled]):hover { + color: var(--color-text); + border-color: var(--color-text); + } + &:disabled { + color: var(--color-bg-light); + border-color: var(--color-bg-light); + } +} + +.-btn.-repo-timeout { + margin: 0; + margin-left: 1em; + padding: 4px 12px; + + font-size: 12px; + font-weight: 300; + &:disabled { + color: var(--color-bg); + border: var(--line-width) solid var(--color-bg-light); + background-color: var(--color-bg); + background-image: none; + &:hover { + color: inherit; + border-color: inherit; + } + } +} +.nav-buttons { + display: flex; +} +.-btn.-hooks { + margin-right: 0.4em; +} + +.-btn.-overview { + margin: 1em 1em 1em 0; +} + +.-btn.-solid, +button.-solid { + color: var(--color-bg); + border-width: var(--line-width); + border-style: solid; + border-color: var(--color-primary); + background-color: var(--color-primary); + background-image: none; + &:hover { + color: var(--color-primary); + border-color: var(--color-primary); + background-color: var(--color-bg); + } + + &.loading { + color: var(--color-bg-light); + border: var(--line-width) dashed var(--color-bg-light); + &:hover { + color: inherit; + border-color: inherit; + } + } +} + +.-btn.-view { + margin-left: 0.4em; +} + +.btn-refresh { + &.loading { + color: var(--color-bg-light); + border: var(--line-width) dashed var(--color-bg-light); + &:not([disabled]):hover { + color: inherit; + border-color: inherit; + } + } +} + +.btn-login { + display: inline-flex; + align-items: center; +} + +// breadcrumb styles +.crumb { + font-weight: 300; +} + +.crumb--current { + font-weight: bold; +} + +// builds styles for /:org/:repo/:build_number +.builds { + display: flex; + flex-direction: column; +} + +.large-loader { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.large-loader .-spinner { + width: 40px; + height: 40px; + + animation: spin 3s linear infinite; + + border: 2px solid var(--color-text); + border-top: 2px solid var(--color-bg); + border-radius: 50%; +} + +.small-loader { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.small-loader .-spinner { + width: 20px; + height: 20px; + + animation: spin 3s linear infinite; + + border: 2px solid var(--color-offwhite); + border-top: 2px solid var(--color-bg); + border-radius: 50%; +} + +.small-loader .-label { + margin-left: 0.8em; + + font-size: 14px; + font-weight: 300; +} + +.build-container { + overflow: hidden; + + width: 100%; + margin: 12px 0; +} + +.build { + position: relative; + + display: flex; + flex-direction: row; + justify-content: space-between; + + border: 2px solid var(--color-bg-dark); + border-right: 0px; + border-left: 0px; + + font-family: Helvetica; + font-size: 18px; + font-weight: 300; +} + +.build .status { + position: relative; + + display: flex; + flex-direction: column; + justify-content: space-around; + + margin-top: -2px; + margin-bottom: -2px; +} + +.build-icon { + margin: 36px; + stroke: var(--color-bg); + + * { + fill: none; + } +} + +.build-icon.-pending { + padding: 8px; + fill: var(--color-bg); + border: 2px solid var(--color-bg-dark); + border-radius: 7px; +} + +.-build-status-icon.-pending { + fill: var(--color-bg); +} + +.build .status.-pending { + background: var(--color-bg-light); +} + +.build .status.-running { + background: var(--color-yellow); +} + +.build .status.-success { + background: var(--color-green); +} + +.build .status.-failure, +.build .status.-error { + background: var(--color-red); +} + +.build .info { + position: relative; + + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + + padding: 12px 0; + + background: var(--color-bg-dark); +} + +.theme-light .build .info { + background: var(--color-white); + border: 1px solid var(--color-bg-dark); + border-left: none; +} + +.build .row { + display: flex; + flex-direction: row; + justify-content: space-between; + + padding: 0 24px; +} + +.build .error { + color: var(--color-red-light); + + font-size: 16px; +} + +.theme-light { + .build .error { + color: var(--color-red); + } +} + +.build .error .message { + margin-left: 0.2em; +} + +.git-info { + display: flex; + flex-direction: row; +} + +.git-info .commit { + margin: 0 8px 0 0px; +} + +.git-info .branch { + margin: 0 8px 0 8px; +} + +.git-info .sender { + margin: 0 8px 0 8px; +} + +.time-info { + display: flex; + flex-direction: row; + + font-weight: 300; +} + +.time-info .age { + margin: 0 4px 0 4px; +} + +.time-info .delimiter { + margin: 0 8px; + + color: var(--color-secondary); +} + +.time-info .duration { + margin: 0 0 0 4px; + + font-family: var(--font-code); +} + +.build-animation { + position: absolute; + + width: 100%; +} + +.-running-start { + stroke: none; +} + +.-running-particles { + stroke: var(--color-yellow); +} + +.build-animation.-bottom { + bottom: 0; +} + +.build-animation.-bottom.-running { + animation: build-status-parallax-running 26s linear 26s infinite, + build-status-parallax-start 26s linear none; +} + +.build-animation.-top.-running { + animation: build-status-parallax-running 22s linear 22s infinite, + build-status-parallax-start 22s linear none; +} + +.build-animation.-bottom.-start { + animation: build-status-parallax-start 26s linear none; +} + +.build-animation.-top.-start { + animation: build-status-parallax-start 22s linear none; +} + +.build-animation.-top.-cover { + width: 12vw; + + animation: build-particles-source 5s ease-in-out infinite; + animation-direction: alternate; +} + +.build-animation.-bottom.-cover { + width: 16vw; + + animation: build-particles-source 5s ease-in-out infinite; + animation-direction: alternate-reverse; +} + +.build-animation.-running.-frame-0 { + left: 0%; +} + +.build-animation.-running.-frame-1 { + left: -100%; +} + +.build-animation.-running.-frame-2 { + left: -200%; +} + +.build.-success { + border-top: 2px solid var(--color-green); + border-bottom: 2px solid var(--color-green); +} + +.build.-failure { + border-top: 2px solid var(--color-red); + border-bottom: 2px solid var(--color-red); +} + +.-animation-dashes-1 { + stroke-dasharray: 20 220 5 360; +} + +.-animation-dashes-2 { + stroke-dasharray: 70 270 8 300; +} + +.-animation-dashes-3 { + stroke-dasharray: 1 240 8 220 12 400 10 180; +} + +.build-history { + display: flex; + align-items: center; + flex-direction: row; + + margin-left: 3em; +} + +.build-history .-build { + position: relative; +} + +.build-history .-build .-icon { + stroke: var(--color-bg); + fill: none; + &.-running { + background-color: var(--color-yellow); + } + + &.-failure, + &.-error { + background-color: var(--color-red); + } + + &.-success { + background-color: var(--color-green); + } +} + +.build-history .-build .-tooltip { + position: absolute; + // not sure what to here to display the tooltip on top! + z-index: 9999; + top: 125%; + + display: flex; + visibility: hidden; + flex-direction: column; + + width: 300px; + padding: 0.2em 0; + + text-align: center; + + color: var(--color-text); + border: solid 1px var(--color-bg-light); + border-radius: 3px; + background-color: var(--color-bg-dark); + + font-weight: 300; +} + +.build-history .-build:hover .-tooltip { + visibility: visible; +} + +.build-history .-build:hover .-tooltip::after { + position: absolute; + bottom: 100%; /* At the top of the tooltip */ + + margin-left: 0.5em; + + content: " "; + + border-width: 5px; + border-style: solid; + border-color: transparent transparent var(--color-bg-light) transparent; +} + +.build-history .-build .-info { + padding: 0.2em 0.6em; + + font-size: 14px; +} + +.build-history .-build .-line { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.build-history .-build .-line.-header { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.build-history .-build .-line .-label { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.build-history .-build .-number { + margin-right: 0.5em; + &:before { + content: "#"; + } +} + +.build-history .-build .-event { + font-style: italic; +} + +.steps { + position: relative; +} + +.step { + display: flex; +} + +.step.-line { + background: linear-gradient( + 90deg, + hsla(0, 0, 0, 0) calc(3.25em - 1px), + hsla(0, 0%, 75%, 1) calc(3.25em), + hsla(0, 0, 0, 0) calc(3.25em + 1px) + ); +} +.step.-line.-last { + background-repeat: no-repeat; + background-size: 100% 2em; +} +.step .-status { + margin-top: 0.65em; + margin-right: 1em; + margin-left: 2.35em; +} + +.step .-icon-container { + padding-top: 12px; + padding-bottom: 12px; + + background: var(--color-bg); +} + +.step-status-icon { + margin-top: 18px; + margin-right: 36px; + margin-left: 36px; +} + +.step .-icon.-pending { + fill: var(--color-bg-light); + padding: 8px; + + border: 2px solid var(--color-bg-light); + border-radius: 7px; +} + +.-icon { + fill: none; + &.-success { + stroke: var(--color-green); + } + &.-running { + stroke: var(--color-yellow); + } + &.-failure, + &.-error { + stroke: var(--color-red); + } + &.-pending { + stroke: var(--color-bg-light); + fill: var(--color-bg-light); + } +} + +.step .-view { + flex: 1; + + margin: 1em 0; + padding: 0em; + + border-top: var(--line-width) solid; + border-left: var(--line-width) solid; + border-image-source: linear-gradient( + to right, + var(--color-bg-light) 75%, + transparent 75%, + transparent 77%, + var(--color-bg-light) 77%, + var(--color-bg-light) 84%, + transparent 84% + ); + border-image-slice: 1; +} + +.step .-view.-running { + border-color: var(--color-yellow); +} + +.step .summary { + padding-top: 0.35em; + padding-bottom: 0.35em; +} + +.step .-info { + display: flex; + flex-direction: row; + justify-content: space-between; + + margin-left: 1.2em; +} + +.step .-info .-duration { + font-family: var(--font-code); +} + +.loading-logs { + padding: 0.5em 0; + margin-left: 0.3em; + margin-top: 0.3em; +} + +.logs-container { + background-color: var(--color-bg-dark); + padding: 0.5em 0; +} + +.theme-light .logs-container { + background-color: var(--color-white); + border-top: 1px solid var(--color-bg-dark); + border-right: 1px solid var(--color-bg-dark); + border-bottom: 1px solid var(--color-bg-dark); +} + +.logs { + font-size: 14px; + font-weight: 300; +} + +.logs .line { + margin: 0 0.5em; + display: flex; +} + +.logs .line .wrapper { + padding-left: 1em; + flex: 1; + display: flex; + flex-direction: row; +} + +.logs .wrapper.-focus { + background: var(--color-focus); +} + +.logs .line .-line-num { + margin-right: 1em; + + color: var(--color-offwhite); + + font-family: var(--font-code); + > a { + text-decoration: none; + &:hover { + text-decoration: underline; + } + } +} + +.step-error { + color: var(--color-red-light); + font-size: 14px; + margin-left: 1.5em; +} + +.theme-light .step-error { + color: var(--color-red); +} + +.step-error .message { + margin-left: 0.2em; +} + +.animated { + animation-duration: 1s; + + animation-fill-mode: both; +} + +.bounceInRight { + animation-name: bounceInRight; +} + +.fadeOutRightBig { + animation-name: fadeOutRightBig; +} + +.alerts { + ol { + display: flex; + flex-direction: column-reverse; + } + + li { + flex: 0 0 auto; + } +} + +.alert-container-attributes { + position: fixed; + right: 0; + bottom: 10px; + + width: 100%; + max-width: 530px; + margin: 0; + padding: 0; + + list-style-type: none; +} + +.alert-item-attributes { + max-height: 100px; + margin: 1em 1em 0 1em; + + transition: max-height 1.2s, margin-top 1.2s; +} + +.alert-container { + width: 500px; + padding: 1em; + + cursor: pointer; + + color: white; + border-radius: 5px; + border-radius: 0px; + background-color: var(--color-bg-dark); + box-shadow: 0 5px 5px -5px hsla(0, 0%, 0%, 0.5); + + font-size: 14px; +} + +.alert-container .-title { + margin: 0; + + font-size: 1em; +} + +.alert-container .-message { + display: flex; + overflow-y: auto; + flex-direction: row; + justify-content: space-between; + + max-height: 3.25em; + margin-top: 0.25em; + margin-bottom: 0; + + font-size: 0.9em; +} + +.alert-container.-success { + border: 1px solid var(--color-green); +} + +.alert-container.-warning { + border: 1px solid var(--color-yellow); +} + +.alert-container.-error { + border: 1px solid var(--color-red); +} + +.hooks { + width: 100%; + padding-bottom: 0.5em; + + background: var(--color-bg-dark); +} + +.theme-light .hooks { + background: var(--color-white); +} + +.hooks .loading { + display: flex; + + margin-top: 0.5em; + margin-left: 0.4em; +} + +.hooks .row { + display: flex; + align-items: center; + flex-direction: row; + + margin: 0.3em 0; + > .hook-summary { + position: relative; + + overflow: hidden; + + padding: 0; + + cursor: pointer; + + // hide details marker on Chrome + &::-webkit-details-marker { + display: none !important; + } + + //hide details marker on Firefox + &:first-of-type { + list-style-type: none !important; + } + } + .chevron { + margin-right: 0.8em; + margin-left: 1em; + + transition: all 0.2s; + transform-origin: 50% 49%; + + color: var(--color-primary); + } + &[open] .chevron { + transform: rotate(0.5turn); + } +} + +.hooks .row.preview { + display: flex; + align-items: center; + flex-direction: row; +} + +.hooks .headers { + display: flex; + align-items: center; + flex-direction: row; + + margin-top: 0.3em; + padding-top: 0.3em; + padding-bottom: 0.3em; + + border-bottom: 1px solid var(--color-bg-light); +} + +.hooks .header { + flex: 1; + + text-align: center; + + font-size: 18px; +} + +.hooks .row .preview { + margin: 0.4em 0; + + font-size: 14px; +} + +.hook-status { + fill: none; +} + +.hook-status.-success { + stroke: var(--color-green); +} + +.hook-status.-failure { + stroke: var(--color-red); +} + +.hooks .row .cell { + align-items: center; + flex: 1; + justify-content: center; + + text-align: center; + + font-weight: 300; +} + +.hooks .source-id { + min-width: 330px; +} + +.hooks .first-cell { + width: 84px; +} + +.hooks .row .cell.source-id { + display: flex; + flex-direction: row; + justify-content: center; +} + +.hooks .row .cell.source-id .text { + padding: 3px 12px; + flex: 1; + text-align: center; + background: var(--color-bg); +} + +.preview .status.success { + color: var(--color-green); +} + +.preview .status.failure { + color: var(--color-red-light); +} + +.theme-light { + .preview .status.success { + color: var(--color-green-dark); + } + + .preview .status.failure { + color: var(--color-red); + } +} + +.hooks .row .info { + display: flex; + display: inline-block; + flex-direction: row; + + width: 100%; + padding: 0.4em 1em; + + border-bottom: 1px solid var(--color-bg-light); + border-left: 2px solid var(--color-bg-light); + + font-size: 14px; +} + +.hooks .row:last-child .info { + border-bottom: none; +} + +.hooks .row .info.-pending { + border-left-color: var(--color-bg-light); +} + +.hooks .row .info.-running { + border-left-color: var(--color-yellow); +} + +.hooks .row .info.-success { + border-left-color: var(--color-green); +} + +.hooks .row .info.-failure, +.hooks .row .info.-error { + border-left-color: var(--color-red); +} + +.hooks .row .info .element span.-m-l, +.hooks .row .info .element .-m-l { + margin-left: 0.4em; +} + +.hooks .row .info .element span.-m-r, +.hook-build .details .element .-m-r { + margin-right: 0.4em; +} + +.hooks .row .info .element .error-label { + color: var(--color-red-light); +} + +.theme-light { + .hooks .row .info .element .error-label { + color: var(--color-red); + } +} + +.hooks .row .info .element .hook-build-status.-pending { + color: var(--color-offwhite); +} + +.hooks .row .info .element .hook-build-status.-running { + color: var(--color-yellow); +} + +.hooks .row .info .element .hook-build-status.-success { + color: var(--color-green); +} + +.hooks .row .info .element .hook-build-status.-failure, +.hooks .row .info .element .hook-build-status.-error { + color: var(--color-red-light); +} + +.theme-light { + .hooks .row .info .element .hook-build-status.-running { + color: var(--color-yellow-dark); + } + + .hooks .row .info .element .hook-build-status.-success { + color: var(--color-green-dark); + } + + .hooks .row .info .element .hook-build-status.-failure, + .hooks .row .info .element .hook-build-status.-error { + color: var(--color-red); + } +} + +.repo-settings { + display: flex; + flex-direction: column; +} + +.repo-settings .row { + display: flex; + flex-direction: row; +} + +.repo-settings .category { + width: 22em; + margin-right: 2em; +} + +.repo-settings .category .header { + margin-bottom: 0.5em; + padding-bottom: 0.2em; + + border-bottom: 2px solid var(--color-secondary); +} + +.repo-settings .category .header .text { + position: relative; + top: 0.1em; + + font-size: 22px; + font-weight: bold; +} + +.repo-settings .category .description { + font-size: 14px; + font-weight: 300; +} + +.inputs { + margin: 1em; +} + +.checkbox { + margin-bottom: 0.5em; +} + +.radio { + display: flex; + align-items: center; +} + +.-icon { + stroke: var(--color-primary); + + &.-check { + background-color: var(--color-primary); + stroke: var(--color-bg); + } + + &.-check, + &.-radio { + fill: none; + } + + .-inner { + fill: var(--color-primary); + } +} + +.checkbox .label { + font-size: 16px; + font-weight: bold; +} + +.checkbox .field-info { + margin-left: 0.25em; + + font-weight: 300; +} + +.inputs.repo-timeout input { + width: 5em; + padding-top: 0.5em; + padding-right: 0em; + padding-bottom: 0.5em; + padding-left: 1em; + + text-align: center; + + color: var(--color-text); + border: none; + border-bottom: 2px solid var(--color-primary); + background: none; + + font-size: 14px; +} + +.repo-timeout input:focus { + border-color: var(--color-primary); +} + +.repo-timeout { + margin-top: 1em; + margin-left: 3em; +} + +.repo-timeout .label { + margin-left: 0.75em; + + font-size: 16px; + font-weight: 300; +} + +.repo-timeout input:invalid { + color: var(--color-red-light); + + caret-color: var(--color-text); +} + +.theme-light .repo-timeout input:invalid { + color: var(--color-red); + + caret-color: var(--color-text); +} + +.timeout-help { + padding: 8px 12px; + + border-radius: 4px; + background: var(--color-bg-light); + + font-size: 14px; + font-weight: 300; +} + +.checkbox input[type="checkbox"], +.checkbox input[type="radio"] { + position: relative; + right: 1.55em; + bottom: 0.1em; + + opacity: 0; +} + +.checkbox label { + position: relative; +} + +.checkbox label::before, +.checkbox label::after { + position: absolute; +} + +.checkbox label::before { + top: calc(50% - 0.45em); + left: -1.875em; + + width: 1.15em; + height: 1.15em; + + content: ""; +} + +.checkbox.radio label::before { + top: calc(50% - 0.575em); + left: -1.8em; + + width: 1.15em; + height: 1.15em; + + content: ""; +} + +.checkbox input[type="checkbox"] + label::after, +.checkbox input[type="radio"] + label::after { + content: none; +} + +.checkbox input[type="checkbox"]:focus + label::before, +.checkbox input[type="radio"]:focus + label::before { + outline: var(--color-primary) auto 5px; +} + +.theme-toggle { + display: inline-block; + position: relative; + margin: 0 1em 0 0; + padding: 0; + font-weight: normal; + background: none; + color: var(--color-bg-light); + fill: var(--color-bg-light); +} + +.theme-light .theme-toggle { + color: var(--color-gray-light); + fill: var(--color-gray-light); +} + +.pager-actions { + display: flex; + justify-content: space-between; +} diff --git a/src/scss/_mixins.scss b/src/scss/_mixins.scss index f41d08052..580b2b61d 100644 --- a/src/scss/_mixins.scss +++ b/src/scss/_mixins.scss @@ -2,8 +2,6 @@ // // Use of this source code is governed by the LICENSE file in this repository. -@import "variables"; - @mixin slashes { position: absolute; right: -0.7rem; diff --git a/src/scss/_reset.scss b/src/scss/_reset.scss index 57a2db9d1..ad4927e77 100644 --- a/src/scss/_reset.scss +++ b/src/scss/_reset.scss @@ -248,14 +248,6 @@ img { border-style: none; } -/** - * Change the fill color to match the text color in all browsers (opinionated). - */ - -svg:not([fill]) { - fill: currentColor; -} - /** * Hide the overflow in IE. */ diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss new file mode 100644 index 000000000..06e386e54 --- /dev/null +++ b/src/scss/_themes.scss @@ -0,0 +1,46 @@ +// Copyright (c) 2019 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// Notes; +// - don't use --color-secondary for text unless you confirmed contrast + +// dark theme (default) +body, +body.theme-dark { + --color-bg-dark: var(--color-coal-dark); + --color-bg: var(--color-coal); + --color-bg-light: var(--color-coal-light); + + --color-text: var(--color-offwhite); + + --color-primary-dark: var(--color-cyan-dark); + --color-primary: var(--color-cyan); + --color-primary-light: var(--color-cyan-light); + + --color-secondary-dark: var(--color-lavender-dark); + --color-secondary: var(--color-lavender); + --color-secondary-light: var(--color-lavender-light); + + --color-focus: var(--color-slate); +} + +// light theme +body.theme-light { + --color-bg-dark: var(--color-gray-light); + --color-bg: var(--color-offwhite); + // --color-bg-light: var(--color-white); + --color-bg-light: var(--color-bg-dark); + + --color-text: var(--color-coal); + + --color-primary-dark: var(--color-lavender-dark); + --color-primary: var(--color-lavender); // ok for text + --color-primary-light: var(--color-lavender-light); + + --color-secondary-dark: var(--color-cyan-dark); + --color-secondary: var(--color-cyan); + --color-secondary-light: var(--color-cyan-light); + + --color-focus: var(--color-bg-dark); +} diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index 6082c0589..6d160c918 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -3,29 +3,37 @@ // Use of this source code is governed by the LICENSE file in this repository. :root { - // main colors - --color-offwhite: hsla(0, 0%, 90%, 1); - --color-gray: hsla(0, 0%, 34%, 1); - --color-darkcoal: hsla(0, 0%, 12%, 1); - --color-coal: hsla(0, 0%, 16%, 1); - --color-cyan: hsla(192, 100%, 50%, 1); - --color-lightcyan: hsla(192, 100%, 75%, 1); - --color-lavender: hsla(286, 29%, 55%, 1); - --color-scarlet: hsla(8, 100%, 50%, 1); - --color-slate: hsla(211.8,8.8%,37.8%, 0.5); - - // for status indications - --color-red: hsla(353, 69%, 45%, 1); - --color-green: hsla(89, 63%, 44%, 1); - --color-yellow: hsla(56, 100%, 50%, 1); - - // dark theme (default) - --color-bg: var(--color-coal); - --color-bg-darker: var(--color-darkcoal); - --color-text: var(--color-offwhite); - --color-primary: var(--color-cyan); - --color-secondary: var(--color-lavender); - --color-focus: var(--color-slate); + // primary colors + --color-cyan-dark: hsl(192, 100%, 30%); // good for text on offwhite + --color-cyan: hsl(192, 100%, 50%); // good for text on coal + --color-cyan-light: hsl(192, 100%, 65%); + --color-lavender-dark: hsl(286, 29%, 40%); + --color-lavender: hsl(286, 29%, 51%); // good for text on offwhite + --color-lavender-light: hsl(286, 29%, 65%); // good for text on coal + + // grays + --color-coal-dark: hsl(0, 0%, 12%); + --color-coal: hsl(0, 0%, 16%); // main dark bg + --color-coal-light: hsl(0, 0%, 34%); + + --color-white: hsl(0, 0%, 100%); + --color-offwhite: hsl(0, 0%, 98%); // main light bg + --color-gray-light: hsl(0, 0%, 85%); + + // status indications + --color-red-dark: hsl(353, 77%, 40%); + --color-red: hsl(353, 77%, 50%); // passes contrast on light bg + --color-red-light: hsl(353, 77%, 66%); // passes contrast on coal bg + + --color-green-dark: hsl(89, 71%, 29.6%); // passes contrast on light + --color-green: hsl(89, 71%, 48%); // passes contrast on coal + --color-green-light: hsl(89, 71%, 60%); + + --color-yellow-dark: hsl(48, 100%, 27%); // passes contrast on light + --color-yellow: hsl(48, 100%, 50%); // passes contrast on dark + --color-yellow-light: hsl(48, 100%, 75%); + + --color-slate: hsla(211.8, 8.8%, 37.8%, 0.5); // some common attributes --horizontal-pad: 3em; diff --git a/src/scss/style.scss b/src/scss/style.scss index 909edd4ab..4f832a3f2 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -2,1700 +2,9 @@ // // Use of this source code is governed by the LICENSE file in this repository. -@import "reset"; @import "variables"; -@import "animations"; +@import "reset"; @import "mixins"; - -html { - font-family: var(--font-family); -} - -body { - color: var(--color-text); - background-color: var(--color-bg); - - font-size: 18px; -} - -a { - color: var(--color-cyan); - &:hover { - text-decoration: none; - } -} - -header { - display: flex; - align-items: center; - justify-content: space-between; - - padding: 1em var(--horizontal-pad); - - border-top-width: 0; - border-bottom: var(--line-width) solid; - border-left-width: 0; - border-image-source: linear-gradient( - to right, - var(--color-primary) 55%, - transparent 55%, - transparent 58%, - var(--color-primary) 58%, - var(--color-primary) 69%, - transparent 69%, - transparent 75%, - var(--color-secondary) 75%, - var(--color-secondary) 76%, - transparent 76% - ); - border-image-slice: 1; - background: radial-gradient( - circle at 50% -200%, - var(--color-gray) -7%, - transparent 50% - ); -} - -.details { - > .summary { - position: relative; - - overflow: hidden; - - padding-right: 1.5em; - padding-bottom: 0.3em; - - cursor: pointer; - - // hide details marker on Chrome - &::-webkit-details-marker { - display: none !important; - } - - //hide details marker on Firefox - &:first-of-type { - list-style-type: none !important; - } - - // super hacky way to remove - // marker on details element - // - // text-indent: -1em; - // Firefox - hide marker - // &::-webkit-details-marker, - // &::marker { - // display: none; - // color: var(--color-coal); - // } - // also could work with: - // display: block; - // nothing seems to work on Chrome - } - .details-icon-expand { - position: absolute; - top: 0.2em; - right: 0; - - transition: all 0.2s; - transform-origin: 50% 49%; - - color: var(--color-primary); - } - &[open] .details-icon-expand { - transform: rotate(0.5turn); - } -} - -.identity { - display: flex; - align-items: center; -} - -.identity-logo-link { - margin-right: 1em; - - text-decoration: none; - // override global `vertical-align: middle` for SVGs - svg { - vertical-align: initial; - } -} - -// Vela logo -.vela-logo { - &-star { - fill: var(--color-lavender); - } - &-outer { - fill: var(--color-lightcyan); - } - &-inner { - fill: var(--color-text); - } -} - -.help-links { - display: flex; - align-items: center; - - font-size: 80%; - > a { - position: relative; - - display: inline-block; - - margin-right: 1em; - &:not(:last-child)::after { - @include slashes; - } - &:last-child { - text-decoration: none; - - font-weight: bold; - } - } -} - -.cli-help { - vertical-align: top; -} - -.identity-name { - position: relative; - z-index: 9999; - ul { - position: absolute; - top: 1rem; - left: 0; - - width: max-content; - min-width: 100%; - padding: 0; - - list-style: none; - - border: 1px solid var(--color-gray); - background-color: var(--color-bg-darker); - box-shadow: 0 0 2px black; - - font-size: 80%; - > li { - padding: 0.5em 1em; - } - @supports (clip-path: inset(50%)) { - &::after { - position: absolute; - top: -5px; - left: calc(50% - 6px); - - display: block; - - width: 10px; - height: 10px; - - content: ""; - transform: rotate(135deg); - - border: inherit; - border-radius: 0 0 0 0.25em; - background-color: inherit; - - clip-path: polygon(0% 0%, 100% 100%, 0% 100%); - } - } - } -} - -.content-wrap { - margin: 0 var(--horizontal-pad); -} - -button, -.button { - margin: 0.5em 0; - padding: 0.5em 2em; - - cursor: pointer; - transition: background 0.5s; - text-decoration: none; - - color: var(--color-bg); - border: none; - background-image: linear-gradient( - to right, - var(--color-cyan) 0%, - var(--color-lavender) 60%, - var(--color-cyan) 100% - ); - background-size: 200% auto; - - font-size: 16px; - font-weight: bold; - - & + button, - + .button { - margin-left: 0.5em; - } - &:hover { - background-position: right center; - } - &:disabled { - color: var(--color-darkcoal); - border-color: var(--color-gray); - background-color: var(--color-gray); - background-image: none; - } - &.inverted { - color: var(--color-cyan); - border-width: var(--line-width); - border-style: solid; - border-color: var(--color-cyan); - background-color: var(--color-bg); - background-image: none; - &:hover { - color: var(--color-lightcyan); - border-color: var(--color-lightcyan); - } - &:disabled { - color: var(--color-gray); - border-color: var(--color-gray); - } - } -} - -.navigation { - display: flex; - align-items: center; - justify-content: space-between; - - padding: 0 3em; - - border-top-width: 0; - border-bottom: var(--line-width) solid; - border-left-width: 0; - border-image-source: linear-gradient( - to right, - var(--color-gray) 15%, - transparent 15%, - transparent 18%, - var(--color-gray) 18%, - var(--color-gray) 18.8%, - transparent 18.8% - ); - border-image-slice: 1; - li { - position: relative; - - display: inline-block; - - margin-right: 1em; - &:not(:last-child)::after { - @include slashes; - } - &:last-child { - text-decoration: none; - - font-weight: bold; - } - } -} - -.login-source-icon { - display: inline-block; - - margin-right: 0.6em; -} - -.overview { - line-height: 2em; -} - -.repo-item { - position: relative; - - display: flex; - justify-content: space-between; - - margin: 2em 0; - padding: 0.5em; - - border-top: var(--line-width) solid; - border-left: var(--line-width) solid; - border-image-source: linear-gradient( - to right, - var(--color-gray) 75%, - transparent 75%, - transparent 77%, - var(--color-gray) 77%, - var(--color-gray) 84%, - transparent 84% - ); - border-image-slice: 1; - > summary { - margin: 0.5em 0 0 1em; - - text-indent: 0; - } -} - -.-item { - display: flex; - align-items: center; - justify-content: space-between; - - width: 100%; - margin: 0.5em 0 0 1em; - padding: 0.5em 1em; - - background-color: var(--color-darkcoal); -} - -.-item .-actions { - display: flex; - align-items: center; -} - -.-actions .-view { - padding: 0.33em 2em; - - @extend .button; -} - -.-actions .-view > a { - text-decoration: none; - - color: var(--color-bg); -} - -// loading ellipsis -.loading-ellipsis::after { - display: inline-block; - overflow: hidden; - - width: 0; - - content: "\2026"; - /* ellipsis character */ - animation: ellipsis steps(4, end) 900ms infinite; - vertical-align: bottom; -} - -.util { - display: flex; - - height: 3em; -} - -.source-actions { - display: flex; - align-items: center; - justify-content: space-between; -} - -.source-actions .-filter, -.source-repos .-filter { - display: flex; - align-items: center; - flex: 1; - flex-direction: row; - - margin: 0 1em; - - border-bottom: 1px solid var(--color-primary); -} - -.source-actions .-filter input, -.source-repos .-filter input { - width: 100%; - padding: 0.5em 0.8em; - - color: var(--color-text); - border: none; - background: var(--color-bg); - - font-size: 18px; - &::placeholder { - color: var(--color-gray); - } -} - -.org .summary { - display: flex; - align-items: center; - flex-direction: row; - &::-webkit-details-marker { - display: flex; - - margin-right: 16px; - - vertical-align: middle; - } -} - -.filtered-repos { - margin-top: 2em; -} - -.-no-repos { - width: 100%; - margin: 0.5em 0 0 1em; - padding: 1.2em 1em; - - background-color: var(--color-bg-darker); -} - -.org-header { - display: flex; - align-items: center; - flex: 1; -} - -.repo-count { - margin-left: 12px; - &::before { - margin-right: 0.3em; - - content: "["; - } - &::after { - margin-left: 0.3em; - - content: "]"; - } -} - -.repo-add { - display: flex; - align-items: center; - - min-width: 150px; - margin: 0.425em 0; - padding: 0.2em 1em; - - vertical-align: top; - > svg { - margin-right: 1em; - } -} - -.-added-container { - display: flex; - flex-direction: row; -} - -.repo-add--added { - color: var(--color-green); - border: var(--line-width) solid var(--color-green); - - @extend .repo-add; -} - -.repo-add--adding { - color: var(--color-yellow); - border: var(--line-width) solid var(--color-yellow); - - @extend .repo-add; -} - -.repo-add--adding-text { - margin-left: 12px; -} - -.repo-add--failed { - cursor: pointer; - - color: var(--color-red); - border: var(--line-width) solid var(--color-red); - background: none; - - @extend .repo-add; - - &:hover svg { - transition: transform 0.2s ease-in-out; - transform: rotate(70deg); - } - - > svg { - margin-right: 1em; - - transition: transform 0.2s ease-in-out; - } -} - -a.-btn { - margin: 0.5em 0; - padding: 0.3em 2em; - - text-decoration: none; - - font-size: 16px; - font-weight: bold; -} - -.-btn.-inverted, -button.-inverted { - color: var(--color-primary); - border-width: var(--line-width); - border-style: solid; - border-color: var(--color-primary); - background-color: var(--color-bg); - background-image: none; - &:hover { - color: var(--color-lightcyan); - border-color: var(--color-lightcyan); - } -} - -.-btn.-repo-timeout { - margin: 0; - margin-left: 1em; - padding: 4px 12px; - - font-size: 12px; - font-weight: 300; - &:disabled { - color: var(--color-offwhite); - border: var(--line-width) solid var(--color-gray); - background-color: var(--color-coal); - background-image: none; - &:hover { - color: inherit; - border-color: inherit; - } - } -} -.nav-buttons { - display: flex; -} -.-btn.-hooks { - margin-right: 0.4em; -} - -.-btn.-overview { - margin: 1em 1em 1em 0; -} - -.-btn.-solid, -button.-solid { - transition: 0.1s; - - color: var(--color-bg-darker); - border-width: var(--line-width); - border-style: solid; - border-color: var(--color-primary); - background-color: var(--color-primary); - background-image: none; - &:hover { - color: var(--color-lightcyan); - border-color: var(--color-lightcyan); - background-color: var(--color-bg-darker); - } - - &.loading { - color: var(--color--gray); - border: var(--line-width) dashed var(--color-gray); - &:hover { - color: inherit; - border-color: inherit; - } - } -} - -.-btn.-view { - margin-left: 0.4em; -} - -.btn-refresh { - &.loading { - color: var(--color--gray); - border: var(--line-width) dashed var(--color-gray); - &:hover { - color: inherit; - border-color: inherit; - } - } -} - -.btn-login { - display: inline-flex; - align-items: center; -} - -// breadcrumb styles -.crumb { - font-weight: 300; -} - -.crumb--current { - font-weight: bold; -} - -// builds styles for /:org/:repo/:build_number -.builds { - display: flex; - flex-direction: column; -} - -.large-loader { - display: flex; - flex-direction: row; - justify-content: flex-start; -} - -.large-loader .-spinner { - width: 40px; - height: 40px; - - animation: spin 3s linear infinite; - - border: 2px solid var(--color-text); - border-top: 2px solid var(--color-bg); - border-radius: 50%; -} - -.small-loader { - display: flex; - flex-direction: row; - justify-content: flex-start; -} - -.small-loader .-spinner { - width: 20px; - height: 20px; - - animation: spin 3s linear infinite; - - border: 2px solid var(--color-offwhite); - border-top: 2px solid var(--color-bg); - border-radius: 50%; -} - -.small-loader .-label { - margin-left: 0.8em; - - font-size: 14px; - font-weight: 300; -} - -.build-container { - overflow: hidden; - - width: 100%; - margin: 12px 0; -} - -.build { - position: relative; - - display: flex; - flex-direction: row; - justify-content: space-between; - - border: 2px solid var(--color-bg-darker); - border-right: 0px; - border-left: 0px; - - font-family: Helvetica; - font-size: 18px; - font-weight: 300; -} - -.build .status { - position: relative; - - display: flex; - flex-direction: column; - justify-content: space-around; - - margin-top: -2px; - margin-bottom: -2px; -} - -.build-icon { - margin: 36px; -} - -.build-icon.-pending { - padding: 8px; - - border: 2px solid var(--color-bg-darker); - border-radius: 7px; -} - -.-build-status-icon.-pending { - fill: var(--color-bg-darker); -} - -.status-svg { - stroke: var(--color-bg); - stroke-width: 2; - fill: none; - fill-rule: evenodd; -} - -.status-svg.-linecap-square { - stroke-linecap: square; -} - -.status-svg.-linecap-round { - stroke-linecap: round; -} - -.build .status.-pending { - background: var(--color-gray); -} - -.build .status.-running { - background: var(--color-yellow); -} - -.build .status.-success { - background: var(--color-green); -} - -.build .status.-failure, -.build .status.-error { - background: var(--color-red); -} - -.build .info { - position: relative; - - display: flex; - flex: 1; - flex-direction: column; - justify-content: center; - - padding: 12px 0; - - background: var(--color-bg-darker); -} - -.build .row { - display: flex; - flex-direction: row; - justify-content: space-between; - - padding: 0 24px; -} - -.build .error { - color: var(--color-scarlet); - - font-size: 16px; -} - -.build .error .message { - margin-left: 0.2em; -} - -.git-info { - display: flex; - flex-direction: row; -} - -.git-info .commit { - margin: 0 8px 0 0px; -} - -.git-info .branch { - margin: 0 8px 0 8px; -} - -.git-info .sender { - margin: 0 8px 0 8px; -} - -.time-info { - display: flex; - flex-direction: row; - - font-weight: 300; -} - -.time-info .age { - margin: 0 4px 0 4px; -} - -.time-info .delimiter { - margin: 0 8px; - - color: var(--color-secondary); -} - -.time-info .duration { - margin: 0 0 0 4px; - - font-family: var(--font-code); -} - -.build-animation { - position: absolute; - - width: 100%; -} - -.-running-start { - stroke: none; -} - -.-running-particles { - stroke: var(--color-yellow); -} - -.build-animation.-top { - top: -2px; -} - -.build-animation.-bottom { - bottom: -2px; -} - -.build-animation.-bottom.-running { - animation: build-status-parallax-running 26s linear 26s infinite, - build-status-parallax-start 26s linear none; -} - -.build-animation.-top.-running { - animation: build-status-parallax-running 22s linear 22s infinite, - build-status-parallax-start 22s linear none; -} - -.build-animation.-bottom.-start { - animation: build-status-parallax-start 26s linear none; -} - -.build-animation.-top.-start { - animation: build-status-parallax-start 22s linear none; -} - -.build-animation.-top.-cover { - width: 12vw; - - animation: build-particles-source 5s ease-in-out infinite; - animation-direction: alternate; -} - -.build-animation.-bottom.-cover { - width: 16vw; - - animation: build-particles-source 5s ease-in-out infinite; - animation-direction: alternate-reverse; -} - -.build-animation.-running.-frame-0 { - left: 0%; -} - -.build-animation.-running.-frame-1 { - left: -100%; -} - -.build-animation.-running.-frame-2 { - left: -200%; -} - -.build-animation.-not-running { - position: absolute; - top: -2px; - bottom: -2px; - - width: 100%; -} - -.build-animation.-not-running.-failure, -.build-animation.-not-running.-error { - border-top: 2px solid var(--color-red); - border-bottom: 2px solid var(--color-red); -} - -.build-animation.-not-running.-success { - border-top: 2px solid var(--color-green); - border-bottom: 2px solid var(--color-green); -} - -.-animation-dashes-1 { - stroke-dasharray: 20 220 5 360; -} - -.-animation-dashes-2 { - stroke-dasharray: 70 270 8 300; -} - -.-animation-dashes-3 { - stroke-dasharray: 1 240 8 220 12 400 10 180; -} - -.build-history { - display: flex; - align-items: center; - flex-direction: row; - - margin-left: 3em; -} - -.build-history .-build { - position: relative; -} - -.build-history .-build .-icon.-running { - fill: var(--color-yellow); -} - -.build-history .-build .-icon.-failure, -.build-history .-build .-icon.-error { - stroke: var(--color-red); -} - -.build-history .-build .-icon.-success { - stroke: var(--color-bg-darker); -} - -.build-history-svg.-build-history-icon { - fill: none; - fill-rule: evenodd; -} - -.build-history-svg.-build-history-icon.-pending { - fill: var(--color-gray); - stroke: var(--color-gray); -} - -.build-history-svg.-build-history-icon.-running { - stroke: var(--color-yellow); -} - -.build-history-svg.-build-history-icon.-success { - stroke: var(--color-green); -} - -.build-history .-build .-icon.-failure .-path-1, -.build-history .-build .-icon.-error .-path-1 { - fill: var(--color-bg-darker); -} - -.build-history .-build .-icon.-failure .-path-2, -.build-history .-build .-icon.-error .-path-2 { - fill: var(--color-red); -} - -.build-history .-build .-icon.-success .-path-2 { - fill: var(--color-green); -} - -.build-history .-build .-icon.-running .-path-2 { - fill: var(--color-yellow); -} - -.build-history .-build .-icon.-running .-path-2 { - fill: var(--color-yellow); -} - -.build-history .-build .-icon.-failure .-path-3, -.build-history .-build .-icon.-error .-path-3 { - stroke: var(--color-bg-darker); - stroke-width: 2; - stroke-linecap: square; -} - -.build-history .-build .-icon.-pending .-path-3 { - stroke: var(--color-bg-darker); - fill: var(--color-bg-darker); -} - -.build-history .-build .-icon.-success .-path-3 { - stroke: var(--color-bg-darker); -} - -.build-history .-build .-icon.-running .-path-3 { - stroke: var(--color-bg-darker); -} - -.build-history .-build .-tooltip { - position: absolute; - // not sure what to here to display the tooltip on top! - z-index: 9999; - top: 125%; - - display: flex; - visibility: hidden; - flex-direction: column; - - width: 300px; - padding: 0.2em 0; - - text-align: center; - - color: var(--color-text); - border: solid 1px var(--color-gray); - border-radius: 3px; - background-color: var(--color-bg-darker); - - font-weight: 300; -} - -.build-history .-build:hover .-tooltip { - visibility: visible; -} - -.build-history .-build:hover .-tooltip::after { - position: absolute; - bottom: 100%; /* At the top of the tooltip */ - - margin-left: 0.5em; - - content: " "; - - border-width: 5px; - border-style: solid; - border-color: transparent transparent var(--color-gray) transparent; -} - -.build-history .-build .-info { - padding: 0.2em 0.6em; - - font-size: 14px; -} - -.build-history .-build .-line { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.build-history .-build .-line.-header { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.build-history .-build .-line .-label { - display: flex; - flex-direction: row; - justify-content: flex-start; -} - -.build-history .-build .-number { - margin-right: 0.5em; - &:before { - content: "#"; - } -} - -.build-history .-build .-event { - font-style: italic; -} - -.steps { - position: relative; -} - -.step { - display: flex; -} - -.step.-line { - background: linear-gradient( - 90deg, - hsla(0, 0, 0, 0) calc(3.25em - 1px), - hsla(0, 0%, 75%, 1) calc(3.25em), - hsla(0, 0, 0, 0) calc(3.25em + 1px) - ); -} -.step.-line.-last { - background-repeat: no-repeat; - background-size: 100% 2em; -} -.step .-status { - margin-top: 0.65em; - margin-right: 1em; - margin-left: 2.35em; -} - -.step .-icon-container { - padding-top: 12px; - padding-bottom: 12px; - - background: var(--color-bg); -} - -.step-status-icon { - margin-top: 18px; - margin-right: 36px; - margin-left: 36px; -} - -.step .-icon.-pending { - padding: 8px; - - border: 2px solid var(--color-gray); - border-radius: 7px; -} - -.-step-icon.-pending { - stroke: var(--color-gray); - fill: var(--color-gray); -} - -.-step-icon.-running { - stroke: var(--color-yellow); -} - -.-step-icon.-success { - stroke: var(--color-green); -} - -.-step-icon.-failure, -.-step-icon.-error { - stroke: var(--color-red); -} - -.step .-view { - flex: 1; - - margin: 1em 0; - padding: 0em; - - border-top: var(--line-width) solid; - border-left: var(--line-width) solid; - border-image-source: linear-gradient( - to right, - var(--color-gray) 75%, - transparent 75%, - transparent 77%, - var(--color-gray) 77%, - var(--color-gray) 84%, - transparent 84% - ); - border-image-slice: 1; -} - -.step .-view.-running { - border-color: var(--color-yellow); -} - -.step .summary { - padding-top: 0.35em; - padding-bottom: 0.35em; -} - -.step .-info { - display: flex; - flex-direction: row; - justify-content: space-between; - - margin-left: 1.2em; -} - -.step .-info .-duration { - font-family: var(--font-code); -} - -.loading-logs { - padding: 0.5em 0; - margin-left: 0.3em; - margin-top: 0.3em; -} - -.logs-container { - background-color: var(--color-bg-darker); - padding: 0.5em 0; -} - -.logs { - font-size: 14px; - font-weight: 300; -} - -.logs .line { - margin: 0 0.5em; - display: flex; -} - -.logs .line .wrapper { - padding-left: 1em; - flex: 1; - display: flex; - flex-direction: row; -} - -.logs .wrapper.-focus { - background: var(--color-focus); -} - -.logs .line .-line-num { - margin-right: 1em; - - color: var(--color-offwhite); - - font-family: var(--font-code); - > a { - text-decoration: none; - &:hover { - text-decoration: underline; - } - } -} - -.step-error { - color: var(--color-scarlet); - font-size: 14px; - margin-left: 1.5em; -} - -.step-error .message{ - margin-left: 0.2em; -} - -.animated { - animation-duration: 1s; - - animation-fill-mode: both; -} - -.bounceInRight { - animation-name: bounceInRight; -} - -.fadeOutRightBig { - animation-name: fadeOutRightBig; -} - -.alerts { - ol { - display: flex; - flex-direction: column-reverse; - } - - li { - flex: 0 0 auto; - } -} - -.alert-container-attributes { - position: fixed; - right: 0; - bottom: 10px; - - width: 100%; - max-width: 530px; - margin: 0; - padding: 0; - - list-style-type: none; -} - -.alert-item-attributes { - max-height: 100px; - margin: 1em 1em 0 1em; - - transition: max-height 1.2s, margin-top 1.2s; -} - -.alert-container { - width: 500px; - padding: 1em; - - cursor: pointer; - - color: white; - border-radius: 5px; - border-radius: 0px; - background-color: var(--color-bg-darker); - box-shadow: 0 5px 5px -5px hsla(0, 0%, 0%, 0.5); - - font-size: 14px; -} - -.alert-container .-title { - margin: 0; - - font-size: 1em; -} - -.alert-container .-message { - display: flex; - overflow-y: auto; - flex-direction: row; - justify-content: space-between; - - max-height: 3.25em; - margin-top: 0.25em; - margin-bottom: 0; - - font-size: 0.9em; -} - -.alert-container.-success { - border: 1px solid var(--color-green); -} - -.alert-container.-warning { - border: 1px solid var(--color-yellow); -} - -.alert-container.-error { - border: 1px solid var(--color-red); -} - -.hooks { - width: 100%; - padding-bottom: 0.5em; - - background: var(--color-darkcoal); -} - -.hooks .loading { - display: flex; - - margin-top: 0.5em; - margin-left: 0.4em; -} - -.hooks .row { - display: flex; - align-items: center; - flex-direction: row; - - margin: 0.3em 0; - > .hook-summary { - position: relative; - - overflow: hidden; - - padding: 0; - - cursor: pointer; - - // hide details marker on Chrome - &::-webkit-details-marker { - display: none !important; - } - - //hide details marker on Firefox - &:first-of-type { - list-style-type: none !important; - } - } - .chevron { - margin-right: 0.8em; - margin-left: 1em; - - transition: all 0.2s; - transform-origin: 50% 49%; - - color: var(--color-primary); - } - &[open] .chevron { - transform: rotate(0.5turn); - } -} - -.hooks .row.preview { - display: flex; - align-items: center; - flex-direction: row; -} - -.hooks .headers { - display: flex; - align-items: center; - flex-direction: row; - - margin-top: 0.3em; - padding-top: 0.3em; - padding-bottom: 0.3em; - - border-bottom: 1px solid var(--color-gray); -} - -.hooks .header { - flex: 1; - - text-align: center; - - font-size: 18px; -} - -.hooks .row .preview { - margin: 0.4em 0; - - font-size: 14px; -} - -.hook-status.-no-fill { - fill: none; -} - -.hook-status.-success { - stroke: var(--color-green); - color: var(--color-green); -} - -.hook-status.-failure { - stroke: var(--color-scarlet); - color: var(--color-scarlet); -} - -.hooks .row .cell { - align-items: center; - flex: 1; - justify-content: center; - - text-align: center; - - font-weight: 300; -} - -.hooks .source-id { - min-width: 330px; -} - -.hooks .first-cell { - width: 84px; -} - -.hooks .row .cell.source-id { - display: flex; - flex-direction: row; - justify-content: center; -} - -.hooks .row .cell.source-id .text { - padding: 3px 12px; - flex: 1; - text-align: center; - color: var(--color-offwhite); - background: var(--color-gray); -} - -.preview .status.success { - color: var(--color-green); -} - -.preview .status.failure { - color: var(--color-scarlet); -} - -.hooks .row .info { - display: flex; - display: inline-block; - flex-direction: row; - - width: 100%; - padding: 0.4em 1em; - - border-bottom: 1px solid var(--color-gray); - border-left: 1px solid var(--color-gray); - - font-size: 14px; -} - -.hooks .row .info.-pending { - border-left-color: var(--color-gray); -} - -.hooks .row .info.-running { - border-left-color: var(--color-yellow); -} - -.hooks .row .info.-success { - border-left-color: var(--color-green); -} - -.hooks .row .info.-failure, -.hooks .row .info.-error { - border-left-color: var(--color-scarlet); -} - -.hooks .row .info .element span.-m-l, -.hooks .row .info .element .-m-l { - margin-left: 0.4em; -} - -.hooks .row .info .element span.-m-r, -.hook-build .details .element .-m-r { - margin-right: 0.4em; -} - -.hooks .row .info .element .error-label { - color: var(--color-scarlet); -} - -.hooks .row .info .element .hook-build-status.-pending { - color: var(--color-offwhite); -} - -.hooks .row .info .element .hook-build-status.-running { - color: var(--color-yellow); -} - -.hooks .row .info .element .hook-build-status.-success { - color: var(--color-green); -} - -.hooks .row .info .element .hook-build-status.-failure, -.hooks .row .info .element .hook-build-status.-error { - color: var(--color-scarlet); -} - -.repo-settings { - display: flex; - flex-direction: column; -} - -.repo-settings .row { - display: flex; - flex-direction: row; -} - -.repo-settings .category { - width: 22em; - margin-right: 2em; -} - -.repo-settings .category .header { - margin-bottom: 0.5em; - padding-bottom: 0.2em; - - border-bottom: 2px solid var(--color-lavender); -} - -.repo-settings .category .header .text { - position: relative; - top: 0.1em; - - font-size: 22px; - font-weight: bold; -} - -.repo-settings .category .description { - font-size: 14px; - font-weight: 300; -} - -.inputs { - margin: 1em; -} - -.checkbox { - margin-bottom: 0.5em; -} - -.radio { - display: flex; - align-items: center; -} - -.checkbox .label { - font-size: 16px; - font-weight: bold; -} - -.checkbox .field-info { - margin-left: 0.25em; - - font-weight: 300; -} - -.inputs.repo-timeout input { - width: 5em; - padding-top: 0.5em; - padding-right: 0em; - padding-bottom: 0.5em; - padding-left: 1em; - - text-align: center; - - color: white; - border: none; - border-bottom: 1px solid var(--color-lavender); - background: none; - - font-size: 14px; -} - -.repo-timeout input:focus { - border-color: var(--color-cyan); -} - -.repo-timeout { - margin-top: 1em; - margin-left: 3em; -} - -.repo-timeout .label { - margin-left: 0.75em; - - font-size: 16px; - font-weight: 300; -} - -.repo-timeout input:invalid { - color: var(--color-red); - - caret-color: var(--color-offwhite); -} - -.timeout-help { - padding: 8px 12px; - - border-radius: 4px; - background: var(--color-gray); - - font-size: 14px; - font-weight: 300; -} - -.checkbox input[type="checkbox"], -.checkbox input[type="radio"] { - position: relative; - right: 1.55em; - bottom: 0.1em; - - opacity: 0; -} - -.checkbox label { - position: relative; -} - -.checkbox label::before, -.checkbox label::after { - position: absolute; -} - -.checkbox label::before { - top: calc(50% - 0.45em); - left: -1.875em; - - width: 1.15em; - height: 1.15em; - - content: ""; -} - -.checkbox.radio label::before { - top: calc(50% - 0.575em); - left: -1.8em; - - width: 1.15em; - height: 1.15em; - - content: ""; -} - -.checkbox input[type="checkbox"] + label::after, -.checkbox input[type="radio"] + label::after { - content: none; -} - -.checkbox input[type="checkbox"]:focus + label::before, -.checkbox input[type="radio"]:focus + label::before { - outline: var(--color-cyan) auto 5px; -} - -.pager-actions { - display: flex; - justify-content: space-between; -} +@import "animations"; +@import "main"; +@import "themes"; diff --git a/src/static/index.ts b/src/static/index.ts index 54279c6ef..6be807d5d 100644 --- a/src/static/index.ts +++ b/src/static/index.ts @@ -2,7 +2,7 @@ // // Use of this source code is governed by the LICENSE file in this repository. -import { Elm, Flags, App, Config, Session } from "../elm/Main"; +import { Elm, Flags, App, Config, Session, Theme } from "../elm/Main"; import "../scss/style.scss"; // Vela consts @@ -16,6 +16,13 @@ const currentSessionState: Session | null = storedSessionState ? JSON.parse(storedSessionState) : null; +// setup for stored theme +const themeKey: string = "vela-theme"; +const defaultTheme: string = "theme-dark"; +const storedThemeState: string | null = localStorage.getItem(themeKey); +const currentThemeState: Theme = + (storedThemeState as Theme) || (defaultTheme as Theme); + // Vela flags; configuration for bootstrapping Vela Elm UI const flags: Flags = { isDev: process.env.NODE_ENV === "development", @@ -30,7 +37,8 @@ const flags: Flags = { process.env.VELA_DOCS_URL || envOrNull("VELA_DOCS_URL", "$VELA_DOCS_URL") || docsURL, - velaSession: currentSessionState || null + velaSession: currentSessionState || null, + velaTheme: currentThemeState || (defaultTheme as Theme) }; // create the configuration object for Elm @@ -53,6 +61,18 @@ app.ports.storeSession.subscribe(sessionMessage => { setTimeout(() => app.ports.onSessionChange.send(sessionMessage), 0); }); +app.ports.setTheme.subscribe(theme => { + let body: HTMLElement = document.getElementsByTagName("body")[0]; + + if (!body.classList.contains(theme)) { + body.className = ""; + body.classList.add(theme); + } + + localStorage.setItem(themeKey, theme); + setTimeout(() => app.ports.onThemeChange.send(theme), 0); +}); + /** * envOrNull is a basic helper that returns a substituted * environment variable or null