From 58f7b1be7c1ce366eabae3b172de732f0122776b Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 25 Oct 2021 16:22:49 +0200 Subject: chore: print the project preview & details on click On small viewport, hide the project details and display it when the user clicks on toolbar button. So I adjust the toolbar z-index and the preview height. --- htdocs/src/js/app.js | 257 +++++++++++++++++++++++++++++------ htdocs/src/scss/base/_helpers.scss | 2 +- htdocs/src/scss/base/_spacings.scss | 10 ++ htdocs/src/scss/layout/_main.scss | 2 +- htdocs/src/scss/layout/_toolbar.scss | 1 + 5 files changed, 229 insertions(+), 43 deletions(-) (limited to 'htdocs') diff --git a/htdocs/src/js/app.js b/htdocs/src/js/app.js index 9a45242..636eb20 100644 --- a/htdocs/src/js/app.js +++ b/htdocs/src/js/app.js @@ -2,52 +2,76 @@ import projects from './config/projects'; import { isSmallVw, isStyleJsExists } from './utilities/helpers'; /** - * Hide the header and footer. - * @param {HTMLElement} header - The header element. - * @param {HTMLElement} footer - The footer element. - */ -function hideHeaderFooter(header, footer) { - header.classList.remove('slide-in--left'); - footer.classList.remove('slide-in--left'); - header.classList.add('slide-out--left'); - footer.classList.add('slide-out--left'); + * Change the element classes to hide it with a slide out left animation. + * @param {HTMLElement} el - The HTMLElement to hide. + */ +function hideToLeft(el) { + el?.classList.remove('slide-in--left'); + el?.classList.add('slide-out--left'); setTimeout(() => { - header.classList.add('hide'); - footer.classList.add('hide'); + el?.classList.add('hide'); }, 800); } /** - * Show the header and footer. - * @param {HTMLElement} header - The header element. - * @param {HTMLElement} footer - The footer element. + * Change the element classes to show it with a slide in left animation. + * @param {HTMLElement} el - The HTMLElement to show. */ -function showHeaderFooter(header, footer) { - header.classList.remove('slide-out--left'); - footer.classList.remove('slide-out--left'); - header.classList.remove('hide'); - footer.classList.remove('hide'); - header.classList.add('slide-in--left'); - footer.classList.add('slide-in--left'); +function showFromLeft(el) { + el?.classList.remove('slide-out--left'); + el?.classList.remove('hide'); + el?.classList.add('slide-in--left'); } /** - * Handle header and footer visibility. - * @returns {void} + * Show/hide header and footer with slide animation (left). */ function toggleHeaderFooter() { const header = document.querySelector('header'); const footer = document.querySelector('footer'); + const elements = [header, footer]; - if (!isSmallVw()) { - showHeaderFooter(header, footer); - return; - } + elements.forEach((el) => { + if (el.classList.contains('hide')) { + showFromLeft(el); + } else { + hideToLeft(el); + } + }); +} + +/** + * Change the element classes to hide it with a slide out bottom animation. + * @param {HTMLElement} el - The HTMLElement to hide. + */ +function hideToBottom(el) { + el?.classList.remove('slide-in--up'); + el?.classList.add('slide-out--bottom'); + setTimeout(() => { + el?.classList.add('hide'); + }, 800); +} + +/** + * Change the element classes to show it with a slide in up animation. + * @param {HTMLElement} el - The HTMLElement to show. + */ +function showFromBottom(el) { + el?.classList.remove('slide-out--bottom'); + el?.classList.remove('hide'); + el?.classList.add('slide-in--up'); +} + +/** + * Show/hide project details with slide animation (bottom). + */ +function toggleProjectDetails() { + const details = document.querySelector('.project-details'); - if (header.classList.contains('hide')) { - showHeaderFooter(header, footer); + if (details.classList.contains('hide')) { + showFromBottom(details); } else { - hideHeaderFooter(header, footer); + hideToBottom(details); } } @@ -60,30 +84,176 @@ function listenMenuBtn() { } /** - * Display or hide the toolbar depending on the current viewport width. + * Update the visibility of some DOM elements depending on viewport. */ -function toggleToolbar() { +function updateView() { + const header = document.querySelector('header'); + const footer = document.querySelector('footer'); const toolbar = document.querySelector('.toolbar'); + const details = document.querySelector('.project-details'); + if (isSmallVw()) { - toolbar.style.display = ''; + header.classList.add('hide'); + footer.classList.add('hide'); + toolbar.classList.remove('hide'); + details?.classList.add('hide'); + details?.classList.remove('fade-in'); } else { - toolbar.style.display = 'none'; + showFromLeft(header); + showFromLeft(footer); + toolbar.classList.add('hide'); + details?.classList.remove('hide'); + details?.classList.add('fade-in'); } } /** - * Change the visibility of some DOM elements. + * Update view when the window size changes. */ -function updateView() { - toggleToolbar(); - toggleHeaderFooter(); +function listenWindowSize() { + window.addEventListener('resize', updateView); } /** - * Update view when the window size changes. + * Retrieve a project by id. + * @param {Integer} id - The project id. + * @returns {Object} The current project. */ -function listenWindowSize() { - window.addEventListener('resize', updateView); +function getCurrentProject(id) { + return projects.find((project) => project.id === id); +} + +/** + * Get a list item for the given repo. + * @param {String} name - The repository name. + * @param {String} url - The repository URL. + * @returns {HTMLElement} A list item. + */ +function getRepoItem(name, url) { + const item = document.createElement('li'); + const link = document.createElement('a'); + const span = document.createElement('span'); + span.classList.add('screen-reader-text'); + span.textContent = name; + link.classList.add('list__link', `list__link--${name.toLocaleLowerCase()}`); + link.href = url; + link.appendChild(span); + item.classList.add('list__item'); + item.appendChild(link); + return item; +} + +/** + * Get the repos list wrapped inside ul element and the title. + * @param {Object[]} repos - An array of repo with name and URL. + * @returns {[title, list]} An array of HTMLElements for title and list. + */ +function getRepos(repos) { + if (repos.length === 0) return []; + + const wrapper = document.createElement('div'); + const title = document.createElement('h3'); + const list = document.createElement('ul'); + const items = repos.map((repo) => getRepoItem(repo.name, repo.url)); + title.classList.add('project-details__title'); + title.textContent = 'Repositories:'; + list.classList.add('list', 'list--repos'); + list.append(...items); + wrapper.append(title, list); + return [title, list]; +} + +/** + * Get the technologies list wrapped inside ul element and the title. + * @param {String[]} technologies - An array of technology names. + * @returns {[title, list]} An array of HTMLElements for title and list. + */ +function getTechs(technologies) { + if (technologies.length === 0) return []; + + const title = document.createElement('h3'); + title.classList.add('project-details__title'); + title.textContent = 'Technologies:'; + const list = document.createElement('ul'); + const items = technologies.map((technology) => { + const item = document.createElement('li'); + item.textContent = technology; + return item; + }); + list.classList.add('list', 'list--tech'); + list.append(...items); + return [title, list]; +} + +/** + * Retrieve the project details. + * @param {Object} project - The project. + * @returns {HTMLElement} The project details wrapped in a div. + */ +function getProjectDetails(project) { + const details = document.createElement('div'); + const title = document.createElement('h2'); + const techList = project?.technologies ? getTechs(project.technologies) : []; + const reposList = getRepos(project.repo); + let description; + + if (project.description) { + description = document.createElement('p'); + description.textContent = project.description; + } else { + description = ''; + } + + title.classList.add('project-details__title'); + title.textContent = `About ${project.name}`; + details.classList.add('project-details'); + details.replaceChildren(title, description, ...techList, ...reposList); + + return details; +} + +/** + * Get an iframe for the given path/url. + * @param {String} src - The path/url to use as source. + * @returns {HTMLElement} The iframe. + */ +function getIframe(src) { + const iframe = document.createElement('iframe'); + iframe.src = src; + return iframe; +} + +/** + * Retrieve the project preview. + * @param {String} projectPath - The project path. + * @returns {HTMLElement} The project preview wrapped in a div. + */ +function getProjectPreview(projectPath) { + const preview = document.createElement('div'); + const iframe = getIframe(projectPath); + preview.classList.add('project-preview', 'fade-in'); + preview.replaceChildren(iframe); + return preview; +} + +/** + * Display the targeted project. + * @param {String} id - The project id. + * @param {String} href - The project URL. + */ +function showProject(id, href) { + const currentProject = getCurrentProject(id); + const main = document.querySelector('.main'); + const details = getProjectDetails(currentProject); + const preview = getProjectPreview(currentProject.path); + const detailsBtn = document.querySelector('.btn--details'); + + if (isSmallVw()) details.classList.add('hide'); + + detailsBtn.textContent = `About ${currentProject.name}`; + detailsBtn.addEventListener('click', toggleProjectDetails); + window.history.pushState({}, currentProject.name, href); + main.replaceChildren(preview, details); } /** @@ -99,6 +269,11 @@ function getProjectsNavItem(id, name) { link.href = id; link.id = id; link.textContent = name; + link.addEventListener('click', (e) => { + e.preventDefault(); + showProject(id, e.target.href); + if (isSmallVw()) toggleHeaderFooter(); + }); item.classList.add('nav__item'); item.appendChild(link); return item; diff --git a/htdocs/src/scss/base/_helpers.scss b/htdocs/src/scss/base/_helpers.scss index 2e85a51..d6a9233 100644 --- a/htdocs/src/scss/base/_helpers.scss +++ b/htdocs/src/scss/base/_helpers.scss @@ -2,7 +2,7 @@ @use "../abstracts/mixins" as mix; .hide { - display: none; + display: none !important; } /* Text meant only for screen readers. */ diff --git a/htdocs/src/scss/base/_spacings.scss b/htdocs/src/scss/base/_spacings.scss index 9106c48..f7ff3c0 100644 --- a/htdocs/src/scss/base/_spacings.scss +++ b/htdocs/src/scss/base/_spacings.scss @@ -15,3 +15,13 @@ toolbar-height: fun.convert-px(60), ) ); + +@include mix.media("screen") { + @include mix.dimensions("lg") { + @include mix.set-vars( + ( + toolbar-height: fun.convert-px(0), + ) + ); + } +} diff --git a/htdocs/src/scss/layout/_main.scss b/htdocs/src/scss/layout/_main.scss index 9adb18d..8118d9b 100644 --- a/htdocs/src/scss/layout/_main.scss +++ b/htdocs/src/scss/layout/_main.scss @@ -35,7 +35,7 @@ .project-preview { background: fun.get-var(color-bg); - flex: 1; + flex: 0 1 calc(100% - #{fun.get-var(toolbar-height)}); width: 100%; @include mix.media("screen") { diff --git a/htdocs/src/scss/layout/_toolbar.scss b/htdocs/src/scss/layout/_toolbar.scss index 585b3e6..c96bd1d 100644 --- a/htdocs/src/scss/layout/_toolbar.scss +++ b/htdocs/src/scss/layout/_toolbar.scss @@ -13,6 +13,7 @@ left: 0; position: absolute; right: 0; + z-index: 2; & > &__options { background: fun.get-var(color-primary); -- cgit v1.2.3