diff --git a/homework/App.js b/homework/App.js index 5f81a47a1..814b272ce 100644 --- a/homework/App.js +++ b/homework/App.js @@ -12,23 +12,56 @@ class App { * @param {string} url The GitHub URL for obtaining the organization's repositories. */ async initialize(url) { - // Add code here to initialize your app - // 1. Create the fixed HTML elements of your page - // 2. Make an initial XMLHttpRequest using Util.fetchJSON() to populate your element + */ const root = document.getElementById('root'); - Util.createAndAppend('h1', root, { text: 'It works!' }); // TODO: replace with your own code + /** Util.createAndAppend('h1', root, { text: 'It works!' }); // TODO: replace with your own code*/ try { const repos = await Util.fetchJSON(url); + repos.sort((a, b) => a.name.localeCompare(b.name)); this.repos = repos.map(repo => new Repository(repo)); // TODO: add your own code here + this.dropDown(root); } catch (error) { this.renderError(error); } } + dropDown(root) { + const header = Util.createAndAppend('header', root, { id: 'header' }); + Util.createAndAppend('p', header, { text: 'HYF Repositories', class: 'header' }); + const select = Util.createAndAppend('select', header, { id: 'repo-select' }); + + this.repos.forEach((repo, index) => { + Util.createAndAppend('option', select, { + text: repo.repository.name, + value: index, + }); + }); + + const mainContainer = Util.createAndAppend('div', root, { id: 'main' }); + const repoContainer = Util.createAndAppend('div', mainContainer, { + class: 'repo-container white-frame', + }); + + const contributorsContainer = Util.createAndAppend('div', mainContainer, { + class: 'contributor-container white-frame', + }); + + select.addEventListener('change', () => { + App.clearContainer(repoContainer); + this.repos[select.value].render(repoContainer); + this.fetchContributorsAndRender(select.value, contributorsContainer); + }); + this.repos[0].render(repoContainer); + this.fetchContributorsAndRender(0, contributorsContainer); + } + /** * Removes all child elements from a container element * @param {*} container Container element to clear @@ -44,20 +77,19 @@ class App { * repo and its contributors as HTML elements in the DOM. * @param {number} index The array index of the repository. */ - async fetchContributorsAndRender(index) { + async fetchContributorsAndRender(index, contributorsContainer) { try { + App.clearContainer(contributorsContainer); const repo = this.repos[index]; const contributors = await repo.fetchContributors(); - const container = document.getElementById('container'); - App.clearContainer(container); - - const leftDiv = Util.createAndAppend('div', container); - const rightDiv = Util.createAndAppend('div', container); - - const contributorList = Util.createAndAppend('ul', rightDiv); - - repo.render(leftDiv); + Util.createAndAppend('p', contributorsContainer, { + text: 'Contributors', + class: 'contributor-header', + }); + const contributorList = Util.createAndAppend('ul', contributorsContainer, { + class: 'contributor-list', + }); contributors .map(contributor => new Contributor(contributor)) @@ -72,6 +104,8 @@ class App { * @param {Error} error An Error object describing the error. */ renderError(error) { + const root = document.getElementById('root'); + Util.createAndAppend('div', root, { text: error, class: 'alert-error' }); console.log(error); // TODO: replace with your own code } } diff --git a/homework/Contributor.js b/homework/Contributor.js index 0bfd5a15e..9050218ae 100644 --- a/homework/Contributor.js +++ b/homework/Contributor.js @@ -13,7 +13,27 @@ class Contributor { * @param {HTMLElement} container The container element in which to render the contributor. */ render(container) { - // TODO: replace the next line with your code. - Util.createAndAppend('pre', container, JSON.stringify(this.contributor, null, 2)); + /** TODO: replace the next line with your code. + Util.createAndAppend('pre', container, JSON.stringify(this.contributor, null, 2));*/ + const link = Util.createAndAppend('a', container, { + href: this.contributor.html_url, + target: '_blank', + class: 'contributor-item', + }); + + Util.createAndAppend('img', link, { + src: this.contributor.avatar_url, + alt: this.contributor.login, + class: 'image', + }); + + const contributorDetails = Util.createAndAppend('div', link, { + class: 'contributor-data', + }); + Util.createAndAppend('div', contributorDetails, { text: this.contributor.login }); + Util.createAndAppend('div', contributorDetails, { + text: this.contributor.contributions, + class: 'contributor-badge', + }); } } diff --git a/homework/Repository.js b/homework/Repository.js index 267960a5a..3026873f0 100644 --- a/homework/Repository.js +++ b/homework/Repository.js @@ -13,8 +13,44 @@ class Repository { * @param {HTMLElement} container The container element in which to render the repository. */ render(container) { - // TODO: replace the next line with your code. - Util.createAndAppend('pre', container, JSON.stringify(this.repository, null, 2)); + /* TODO: replace the next line with your code. + Util.createAndAppend('pre', container, JSON.stringify(this.repository, null, 2));*/ + const repo = Object.assign({}, this.repository, { + updated_at: new Date(this.repository.updated_at).toLocaleString(), + }); + + const repoTitles = { description: 'Description: ', forks: 'Forks: ', updated_at: 'Updated: ' }; + + const table = Util.createAndAppend('table', container, { class: 'table' }); + const tbody = Util.createAndAppend('tBody', table); + + const firstRow = Util.createAndAppend('tr', tbody); + let leftCell = Util.createAndAppend('td', firstRow); + let rightCell = Util.createAndAppend('td', firstRow); + + Util.createAndAppend('span', leftCell, { text: 'Repository: ', class: 'repo-child left-cell' }); + Util.createAndAppend('a', rightCell, { + text: this.repository.name, + href: this.repository.html_url, + target: '_blank', + class: 'repo-child right-cell', + }); + + Object.keys(repoTitles).forEach(key => { + const tr = Util.createAndAppend('tr', tbody); + leftCell = Util.createAndAppend('td', tr); + rightCell = Util.createAndAppend('td', tr); + + Util.createAndAppend('span', leftCell, { + text: repoTitles[key], + class: 'repo-child left-cell', + }); + + Util.createAndAppend('span', rightCell, { + text: repo[key], + class: 'repo-child right-cell', + }); + }); } /** diff --git a/homework/index.html b/homework/index.html index cc4b45bcb..858ec2287 100644 --- a/homework/index.html +++ b/homework/index.html @@ -18,7 +18,9 @@ -
+
+
+
diff --git a/homework/index.js b/homework/index.js index d8a04f271..1be88d6fa 100644 --- a/homework/index.js +++ b/homework/index.js @@ -1,19 +1,21 @@ 'use strict'; { - function fetchJSON(url, cb) { - const xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.responseType = 'json'; - xhr.onload = () => { - if (xhr.status < 400) { - cb(null, xhr.response); - } else { - cb(new Error(`Network error: ${xhr.status} - ${xhr.statusText}`)); - } - }; - xhr.onerror = () => cb(new Error('Network request failed')); - xhr.send(); + function fetchJSON(url) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'json'; + xhr.onload = () => { + if (xhr.status < 400) { + resolve(xhr.response); + } else { + reject(new Error(`Network error: ${xhr.status} - ${xhr.statusText}`)); + } + }; + xhr.onerror = () => reject(new Error('Network request failed')); + xhr.send(); + }); } function createAndAppend(name, parent, options = {}) { @@ -30,18 +32,103 @@ return elem; } - function main(url) { - fetchJSON(url, (err, data) => { - const root = document.getElementById('root'); - if (err) { + function renderContributors(contributors, contributorsContainer) { + createAndAppend('p', contributorsContainer, { text: 'Contributors', id: 'contributors' }); + contributors.forEach(contributor => { + const contributorsDetailsDiv = createAndAppend('div', contributorsContainer, { + class: 'contributor-details', + }); + createAndAppend('img', contributorsDetailsDiv, { + src: contributor.avatar_url, + alt: contributor.login, + class: 'image', + }); + createAndAppend('a', contributorsDetailsDiv, { + text: contributor.login, + href: contributor.html_url, + target: '_blank', + class: 'contributor-data', + }); + createAndAppend('p', contributorsDetailsDiv, { + text: contributor.contributions, + class: 'contributor-badge', + }); + }); + } + + function fetchAndRenderData(selectedRepo, repoContainer, contributorsContainer, root) { + repoContainer.innerHTML = ''; + contributorsContainer.innerHTML = ''; + + const updatedAt = new Date(selectedRepo.updated_at); + createAndAppend('span', repoContainer, { text: 'Repository: ', class: 'repo-child' }); + createAndAppend('a', repoContainer, { + text: `${selectedRepo.name}`, + href: selectedRepo.html_url, + target: '_blank', + class: 'repo-child right-cell', + }); + createAndAppend('p', repoContainer, { + text: `Description: ${selectedRepo.description}`, + class: 'repo-child', + }); + createAndAppend('p', repoContainer, { + text: `Fork: ${selectedRepo.forks}`, + class: 'repo-child', + }); + createAndAppend('p', repoContainer, { + text: `Updated: ${updatedAt.toLocaleString()}`, + class: 'repo-child', + }); + + fetchJSON(selectedRepo.contributors_url) + .then(contributors => { + renderContributors(contributors, contributorsContainer); + }) + .catch(err => { createAndAppend('div', root, { text: err.message, class: 'alert-error' }); - } else { - createAndAppend('pre', root, { text: JSON.stringify(data, null, 2) }); - } + }); + } + + function dropDown(root, repos) { + const header = createAndAppend('div', root, { id: 'header' }); + createAndAppend('p', header, { text: 'HYF Repositories', class: 'header' }); + const select = createAndAppend('select', header, { id: 'select' }); + + repos.sort((a, b) => a.name.localeCompare(b.name)); + + repos.forEach((repo, index) => { + createAndAppend('option', select, { + text: repo.name, + value: index, + }); + }); + + const mainContainer = createAndAppend('div', root, { id: 'main' }); + const repoContainer = createAndAppend('div', mainContainer, { id: 'repo-container' }); + const contributorsContainer = createAndAppend('div', mainContainer, { + id: 'contributor-container', }); + + select.addEventListener('change', () => { + const selectedRepo = repos[select.value]; + fetchAndRenderData(selectedRepo, repoContainer, contributorsContainer, root); + }); + fetchAndRenderData(repos[0], repoContainer, contributorsContainer, root); + } + + function main(url) { + const root = document.getElementById('root'); + fetchJSON(url) + .then(repos => { + dropDown(root, repos); + }) + .catch(err => { + createAndAppend('div', root, { text: err.message, class: 'alert-error' }); + }); } - const REPOS_URL = 'https://api.github.com/orgs/foocoding/repos?per_page=100'; + const HYF_REPOS_URL = 'https://api.github.com/orgs/HackYourFuture/repos?per_page=100'; - window.onload = () => main(REPOS_URL); + window.onload = () => main(HYF_REPOS_URL); } diff --git a/homework/style.css b/homework/style.css index a8985a8a5..8642a772e 100644 --- a/homework/style.css +++ b/homework/style.css @@ -1,3 +1,142 @@ +#root { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; +} +#main { + display: flex; + flex-direction: row; + align-items: flex-start; +} +@media (max-width: 767px) { + body { + width: 768px; + } + #main { + flex-direction: column; + align-items: stretch; + /* margin-left: auto; + margin-right: auto; + margin-top: 0px; + font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;*/ + } + .right-div { + width: 100%; + } + .left-div { + width: 100%; + } +} .alert-error { - color: red; -} \ No newline at end of file + color: red; + text-align: center; + font-weight: bold; + font-size: 30px; +} +.h1 { + text-align: center; +} +.container { + display: flex; + flex-direction: row; + align-items: flex-start; + margin: 10px; +} + +.right-div { + color: green; + display: flex; + flex-direction: row; + justify-content: space-between; + flex-wrap: wrap; + margin: 10px; + flex-grow: 1; + height: 200px; + width: 200px; +} + +.left-div { + color: brown; + display: flex; + flex-wrap: wrap; + flex-direction: column; + flex-grow: 1; + + align-items: right; + margin: 10px; +} +header { + width: 800px; + height: 70px; + background-color: purple; + text-emphasis-color: white; + text-align: left; +} +.info-container { + text-align: left; + width: 40; + height: 30; +} +table { + table-layout: fixed; + color: rgb(0, 0, 0, 81%); +} + +table td { + vertical-align: top; +} +.repo-container, +.contributor-container { + background: white; + flex: 1; + margin-top: 15px; +} + +.repo-container { + margin-right: 5px; + padding: 16px; +} + +.left-cell { + font-weight: bold; + margin-right: 15px; +} +.repo-child { + font-size: 16px; +} + +.contributor-header { + font-size: 1rem; + padding: 8px 16px 4px 5px; + color: rgb(0, 0, 0, 54%); + margin-left: 15px; +} +.contributor-list { + list-style-type: none; + padding: 0; + margin: 0; +} +.image { + width: 50px; + height: 50px; + border-radius: 3px; + margin-right: 18px; + margin-bottom: 20px; +} +.contributor-badge { + font-size: 12px; + background-color: gray; + font-size: 12px; + padding: 2px 8px; + line-height: 1rem; + color: white; + border-radius: 4px; +} +.contributor-data { + flex: 1; + display: flex; + flex-direction: row; + justify-content: space-between; + align-content: center; +}