#78 [WIP] Style changes for coreos download page
Closed 9 months ago by abai. Opened 10 months ago by abai.
fedora-web/ abai/websites coreos-download  into  master

@@ -22,8 +22,12 @@ 

        </div>

      </div>

      <br>

-     <div id="coreos-download-app">

-     </div>

+   </div>

+   <div class="jumbotron pb-0 pt-3">

+     <div id="jumbotron-buttons"></div>

+   </div>

+   <div class="container">

+     <div id="coreos-download-app"></div>

    </div>

  </div>

  {% endblock %}

file modified
+155 -137
@@ -28,6 +28,30 @@ 

    "vmware": "VMware",

    "openstack": "OpenStack"

  }

+ var data = {

+   // currently selected stream

+   stream: 'testing',

+   // currently selected architecture

+   architecture: 'x86_64',

+   // current url to dir for stream

+   streamUrl: "",

+   // fetched {stream, metadata, architectures, updates} object from stream.json

+   streamData: null,

+   loading: false,

+   // loaded stream data to render

+   streamDisplay: {

+     cloudLaunchable: {},

+     bareMetal: {},

+     virtualized: {},

+     cloud: {}

+   },

+   // innerText of tab button

+   tabInnerText: {

+     cloud_launchable: "Cloud Launchable",

+     metal_virt: "Bare Metal & Virtualized",

+     cloud_operator: "For Cloud Operators"

+   }

+ }

  function getMember(obj, member) {

    return (member in obj) ? obj[member] : null;

  }
@@ -65,39 +89,105 @@ 

      downloads[download] = entry;

    }

  }

+ var jumbotron_buttons = new Vue ({

+   el: '#jumbotron-buttons',

+   data: data,

+   methods: {

+     getObjectUrl: function(path) {

+       return getArtifactUrl(data.streamUrl, path);

+     },

+     // Adapted from https://stackoverflow.com/a/6109105

+     timeSince: function(rfc3339_timestamp) {

+       var current = Date.now();

+       var timestamp = Date.parse(rfc3339_timestamp);

+       var elapsed = current - timestamp;

+       var msPerMinute = 60 * 1000;

+       var msPerHour = msPerMinute * 60;

+       var msPerDay = msPerHour * 24;

+       var msPerMonth = msPerDay * 30;

+       var msPerYear = msPerDay * 365;

+       function stringize(n, s) {

+         return n + ` ${s}` + (n == 1 ? "" : "s") + ' ago';

+       };

+       if (elapsed < msPerMinute) {

+         return stringize(Math.floor(elapsed/1000), "second");

+       } else if (elapsed < msPerHour) {

+         return stringize(Math.floor(elapsed/msPerMinute), "minute");

+       } else if (elapsed < msPerDay) {

+         return stringize(Math.floor(elapsed/msPerHour), "hour");

+       } else if (elapsed < msPerMonth) {

+         return stringize(Math.floor(elapsed/msPerDay), "day");

+       } else if (elapsed < msPerYear) {

+         return stringize(Math.floor(elapsed/msPerMonth), "month");

+       } else {

+         return stringize(Math.floor(elapsed/msPerYear), "year");

+       }

+     },

+     toggleHidden: function(e) {

+       const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];

+       switch(e.target.innerText) {

+         case data.tabInnerText.cloud_launchable:

+           show_id = 'cloud-launchable';

+           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

+           break;

+         case data.tabInnerText.metal_virt:

+           show_id = 'metal-virt';

+           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

+           break;

+         case data.tabInnerText.cloud_operator:

+           show_id = 'cloud-operator';

+           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

+           break;

+       }

+     },

+     getNavbar: function(h) {

+       cloud_icon = h('i', { class: "fas fa-cloud mr-2" })

+       nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, data.tabInnerText.cloud_launchable ]);

+       nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]);

  

- var coreos_download_app = new Vue({

-   el: '#coreos-download-app',

-   data: {

-     // currently selected stream

-     stream: 'testing',

-     // currently selected architecture

-     architecture: 'x86_64',

-     // current url to dir for stream

-     streamUrl: "",

-     // fetched {stream, metadata, architectures, updates} object from stream.json

-     streamData: null,

-     loading: false,

-     // loaded stream data to render

-     streamDisplay: {

-       cloudLaunchable: {},

-       bareMetal: {},

-       virtualized: {},

-       cloud: {}

+       server_icon = h('i', { class: "fas fa-server mr-2" })

+       nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, data.tabInnerText.metal_virt ]);

+       nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]);

+ 

+       cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })

+       nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, data.tabInnerText.cloud_operator ]);

+       nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);

+ 

+       navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);

+       return navbar;

      },

-     // innerText of tab button

-     tabInnerText: {

-       cloud_launchable: "Cloud Launchable",

-       metal_virt: "Bare Metal & Virtualized",

-       cloud_operator: "For Cloud Operators"

+     // Add dropdown options of streams

+     getStreamName: function(h) {

+       if (data.streamData === null) return;

+       option_default = h('option', { attrs: { value: "testing", selected: "selected" }}, "testing" );

+       selectOptions = h('select', { class: "mx-1" }, [ option_default ]);

+       streamName = h('p', {}, [

+         "Stream: ",

+         selectOptions,

+         " (",

+         h('span', {}, [

+           h('a', { attrs: { href: this.getObjectUrl(data.streamData.stream + '.json') } }, "JSON")

+         ]),

+         ")",

+         "—",

+         h('span', {}, this.timeSince(data.streamData.metadata['last-modified']))

+       ]);

+       return streamName;

      }

    },

+   render: function(h) {

+     navbar = this.getNavbar(h);

+     streamName = this.getStreamName(h);

+     container = h('div', { class: "container" }, [ streamName, navbar ]);

+     return container

+   }

+ })

+ var coreos_download_app = new Vue({

+   el: '#coreos-download-app',

    created: function() { this.refreshStream() },

-   watch: { stream: 'refreshStream' },

+   data: data,

+   watch: { 'data.stream': 'refreshStream' },

    methods: {

-     getObjectUrl: function(path) {

-       return getArtifactUrl(this.streamUrl, path);

-     },

      isAws: function(platform) {

        return platform == "aws";

      },
@@ -114,20 +204,20 @@ 

      // not deep-copy information from `streamData` or elsewhere into

      // `streamDisplay`.

      loadStreamDisplay: function() {

-       this.streamDisplay = {

+       data.streamDisplay = {

          cloudLaunchable: {},

          bareMetal: {},

          virtualized: {},

          cloud: {}

        };

-       if (this.streamData == null) {

+       if (data.streamData == null) {

          return;

        }

-       const architectures = getMember(this.streamData, "architectures");

+       const architectures = getMember(data.streamData, "architectures");

        if (architectures == null) {

          return;

        }

-       const architectureData = getMember(architectures, this.architecture);

+       const architectureData = getMember(architectures, data.architecture);

        if (architectureData == null) {

          return;

        }
@@ -147,11 +237,11 @@ 

                displayEntries.push({platform: prettyPlatform, region: region, release: release, image: image});

              }

            }

-           Vue.set(this.streamDisplay.cloudLaunchable, platform, {list: displayEntries});

+           Vue.set(data.streamDisplay.cloudLaunchable, platform, {list: displayEntries});

          }

          else {

            const image = getMember(images[platform], "image");

-           Vue.set(this.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});

+           Vue.set(data.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});

          }

        }

        const artifacts = getMember(architectureData, "artifacts");
@@ -176,32 +266,32 @@ 

                Vue.set(display, platform + "-" + format, displayEntry);

              }

              if (this.isCloudImage(platform)) {

-               addDisplayEntry(this.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);

+               addDisplayEntry(data.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);

              }

              if (this.isVirtualizedImage(platform)) {

-               addDisplayEntry(this.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);

+               addDisplayEntry(data.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);

              }

              if (this.isBareMetalImage(platform)) {

-               addDisplayEntry(this.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);

+               addDisplayEntry(data.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);

              }

            }

          }

        }

      },

      refreshStream: function() {

-       this.loading = true

-       this.streamUrl = baseUrl

-       fetchStreamData(this.streamUrl, this.stream).then(streamData => {

-         this.loading = false;

-         this.streamData = streamData;

+       data.loading = true

+       data.streamUrl = baseUrl

+       fetchStreamData(data.streamUrl, data.stream).then(streamData => {

+         data.loading = false;

+         data.streamData = streamData;

          this.loadStreamDisplay();

        });

      },

      toggleShowSignatureAndSha: function(imageType, platformFormat, contentType) {

-       if (!(platformFormat in this.streamDisplay[imageType])) {

+       if (!(platformFormat in data.streamDisplay[imageType])) {

          return;

        }

-       const artifact = this.streamDisplay[imageType][platformFormat];

+       const artifact = data.streamDisplay[imageType][platformFormat];

        if (!(contentType in artifact.downloads)) {

          return;

        }
@@ -209,90 +299,21 @@ 

        artifact.downloads[contentType].showSignatureAndSha = !prev;

      },

      showSignatureAndSha: function(imageType, platformFormat, contentType) {

-       if (!(platformFormat in this.streamDisplay[imageType])) {

+       if (!(platformFormat in data.streamDisplay[imageType])) {

          return false;

        }

-       const artifact = this.streamDisplay[imageType][platformFormat];

+       const artifact = data.streamDisplay[imageType][platformFormat];

        if (!(contentType in artifact.downloads)) {

          return false;

        }

        return artifact.downloads[contentType].showSignatureAndSha;

-     },

-     // Adapted from https://stackoverflow.com/a/6109105

-     timeSince: function(rfc3339_timestamp) {

-       var current = Date.now();

-       var timestamp = Date.parse(rfc3339_timestamp);

-       var elapsed = current - timestamp;

-       var msPerMinute = 60 * 1000;

-       var msPerHour = msPerMinute * 60;

-       var msPerDay = msPerHour * 24;

-       var msPerMonth = msPerDay * 30;

-       var msPerYear = msPerDay * 365;

-       function stringize(n, s) {

-         return n + ` ${s}` + (n == 1 ? "" : "s") + ' ago';

-       };

-       if (elapsed < msPerMinute) {

-         return stringize(Math.floor(elapsed/1000), "second");

-       } else if (elapsed < msPerHour) {

-         return stringize(Math.floor(elapsed/msPerMinute), "minute");

-       } else if (elapsed < msPerDay) {

-         return stringize(Math.floor(elapsed/msPerHour), "hour");

-       } else if (elapsed < msPerMonth) {

-         return stringize(Math.floor(elapsed/msPerDay), "day");

-       } else if (elapsed < msPerYear) {

-         return stringize(Math.floor(elapsed/msPerMonth), "month");

-       } else {

-         return stringize(Math.floor(elapsed/msPerYear), "year");

-       }

-     },

-     toggleHidden: function(e) {

-       const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];

-       switch(e.target.innerText) {

-         case this.tabInnerText.cloud_launchable:

-           show_id = 'cloud-launchable';

-           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

-           break;

-         case this.tabInnerText.metal_virt:

-           show_id = 'metal-virt';

-           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

-           break;

-         case this.tabInnerText.cloud_operator:

-           show_id = 'cloud-operator';

-           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));

-           break;

-       }

-     },

-     getNavbar: function(h) {

-       nav_cloud_launchable_btn = h('button', { class: "nav-link active", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.cloud_launchable);

-       nav_cloud_launchable = h('li', { class: "nav-item mr-3 ml-3" }, [ nav_cloud_launchable_btn ]);

- 

-       nav_metal_virt_btn = h('button', { class: "nav-link", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.metal_virt);

-       nav_metal_virt = h('li', { class: "nav-item mr-3" }, [ nav_metal_virt_btn ]);

- 

-       nav_cloud_operator_btn = h('button', { class: "nav-link", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.cloud_operator);

-       nav_cloud_operator = h('li', { class: "nav-item" }, [ nav_cloud_operator_btn ]);

- 

-       navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);

-       return navbar

      }

    },

    render: function(h) {

-     if (this.loading) {

+     if (data.loading) {

        return h('div', {}, "Loading...");

      }

-     else if (this.streamData) {

-       streamName = h('p', {}, [

-         "Stream: ",

-         h('span', { "class":"font-weight-bold" }, this.streamData.stream),

-         " (",

-         h('span', {}, [

-           h('a', { attrs: { href: this.getObjectUrl(this.streamData.stream + '.json') } }, "JSON")

-         ]),

-         ")",

-         "—",

-         h('span', {}, this.timeSince(this.streamData.metadata['last-modified']))

-       ]);

- 

+     else if (data.streamData) {

        cloudLaunchableTitle = h('h3', { class:"font-weight-light" }, "Cloud Launchable");

        cloudLaunchableSection = {};

        cloudLaunchable = {};
@@ -306,19 +327,19 @@ 

        cloudSection = {};

        cloud = {};

  

-       if (this.streamDisplay.cloudLaunchable) {

-         cloudLaunchableSection = h('div', {}, Object.entries(this.streamDisplay.cloudLaunchable).map(function(entry) {

+       if (data.streamDisplay.cloudLaunchable) {

+         cloudLaunchableSection = h('div', {}, Object.entries(data.streamDisplay.cloudLaunchable).map(function(entry) {

            platform = entry[0];

            displayInfo = entry[1];

            if (coreos_download_app.isAws(platform)) {

              if (displayInfo.list) {

                return h('div', {}, displayInfo.list.map(function(amiInfo) {

-                 return h('div', {}, [

+                 return h('div', { class: "p-2 m-2" }, [

                    amiInfo.platform ? h('div', { class: "font-weight-bold" }, amiInfo.platform) : null,

                    amiInfo.region ? h('div', {}, [ "(", amiInfo.region, ")" ]) : null,

                    amiInfo.release ? h('div', { class: "ml-2" }, [

                      h('span', {}, [ amiInfo.release, " " ]),

-                     h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)

+                     h('span', { class: "text-secondary" }, data.streamData.stream)

                    ]) : null,

                    amiInfo.image ? h('div', { class: "ml-2" }, [

                      h('a', {
@@ -371,7 +392,7 @@ 

              }, "Verify signature & SHA256")

            ]),

            coreos_download_app.showSignatureAndSha(imageType, platformFormat, contentType) ? h('div', { class: "bg-gray-100 p-2 my-2" }, [ h('p', {}, [

-               displayDownloads.sha256 ? h('div', {}, [

+               displayDownloads.sha256 ? h('div', { class: "overflow-auto" }, [

                  "SHA256: ",

                  displayDownloads.sha256

                ]) : null,
@@ -415,12 +436,12 @@ 

          return h('div', {}, Object.entries(displayArtifacts).map(function(entry) {

            platformFormat = entry[0];

            displayInfo = entry[1];

-           return h('div', { class: "my-2" }, [

+           return h('div', { class: "p-2 m-2" }, [

              displayInfo.platform ? h('div', { class: "font-weight-bold" }, displayInfo.platform) : null,

              displayInfo.extension ? h('div', {}, [ "(", displayInfo.extension, ")" ]) : null,

              displayInfo.release ? h('div', { class: "ml-2" }, [

                h('span', {}, [ displayInfo.release, " " ]),

-               h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)

+               h('span', { class: "text-secondary" }, data.streamData.stream)

              ]) : null,

              displayInfo.downloads ? h('div', { class: "ml-2" }, [

                createDownloadsSubSection(displayInfo.downloads.disk, 'disk', false, imageType),
@@ -430,24 +451,24 @@ 

            ]);

          }));

        }

-       if (this.streamDisplay.bareMetal) {

-         bareMetalSection = createArtifactsSection(this.streamDisplay.bareMetal, 'bareMetal');

+       if (data.streamDisplay.bareMetal) {

+         bareMetalSection = createArtifactsSection(data.streamDisplay.bareMetal, 'bareMetal');

        }

        else {

          bareMetalSection = h('div', {}, "No bare metal images found.");

        }

        bareMetal = h('div', { class: "col-12 py-2 my-2" }, [ bareMetalSection ]);

  

-       if (this.streamDisplay.virtualized) {

-         virtualizedSection = createArtifactsSection(this.streamDisplay.virtualized, 'virtualized');

+       if (data.streamDisplay.virtualized) {

+         virtualizedSection = createArtifactsSection(data.streamDisplay.virtualized, 'virtualized');

        }

        else {

          virtualizedSection = h('div', {}, "No virtualized images found.");

        }

        virtualized = h('div', { class: "col-12 py-2 my-2" }, [ virtualizedSection ]);

-       

-       if (this.streamDisplay.cloud) {

-         cloudSection = createArtifactsSection(this.streamDisplay.cloud, 'cloud');

+ 

+       if (data.streamDisplay.cloud) {

+         cloudSection = createArtifactsSection(data.streamDisplay.cloud, 'cloud');

        }

        else {

          cloudSection = h('div', {}, "No cloud images found.");
@@ -462,17 +483,14 @@ 

          ])

        ]);

  

-       let navbar = this.getNavbar(h);

- 

-       let bare_metal_container = h('div', { class: "col-12 p-0" }, [ bareMetalTitle, verifyBlurb, bareMetal ]);

-       let virtualized_container = h('div', { class: "col-12 p-0" }, [ virtualizedTitle, verifyBlurb, virtualized ]);

+       let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, verifyBlurb, bareMetal ]);

+       let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, verifyBlurb, virtualized ]);

  

-       let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-launchable", hidden: false} }, [ cloudLaunchableTitle, streamName, cloudLaunchable ]);

-       let metal_virt_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "metal-virt", hidden: true } }, [ bare_metal_container, virtualized_container ]);

+       let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-launchable", hidden: false} }, [ cloudLaunchableTitle, cloudLaunchable ]);

+       let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: "metal-virt", hidden: true } }, [ bare_metal_container, virtualized_container ]);

        let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-operator", hidden: true } }, [ cloudTitle, verifyBlurb, cloud ]);

  

        return h('div', {}, [

-         navbar,

          cloud_launchable_container,

          metal_virt_container,

          cloud_operators_container

Mainly focus on styling of fedora coreos download page, changes include adding icons inside tabs and using bootstrap jumbotron instead of container.

Styling follows the design of @duffy, conversation happened here:
https://pagure.io/fedora-websites/issue/964#comment-576026

Works around adding stable stream will be on separate PR since we want to use stable stream metadata very soon and I believe design work and metadata work can happen in parallel, if not mistaken.

2 new commits added

  • coreos-download: use jumbotron instead of container for tabs
  • coreos-download: add icons and change style for buttons
10 months ago

@abai ack on the WIP in status. Ping me when it's ready to go.

2 new commits added

  • coreos-download: use jumbotron instead of container for tabs
  • coreos-download: add icons and change style for buttons
10 months ago

1 new commit added

  • coreos-download: add dropdown list for streams
10 months ago

Added a dropdown list for streams, we could add to this list to select from different streams and re-render the page.

Pull-Request has been closed by abai

9 months ago