aboutsummaryrefslogtreecommitdiffstats
path: root/htdocs
diff options
context:
space:
mode:
Diffstat (limited to 'htdocs')
-rw-r--r--htdocs/src/js/app.js257
-rw-r--r--htdocs/src/scss/base/_helpers.scss2
-rw-r--r--htdocs/src/scss/base/_spacings.scss10
-rw-r--r--htdocs/src/scss/layout/_main.scss2
-rw-r--r--htdocs/src/scss/layout/_toolbar.scss1
5 files changed, 229 insertions, 43 deletions
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);