Skip to content

Add blogs page #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 4, 2025
14 changes: 14 additions & 0 deletions content/_publications/2024-08-29-Topology.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ tags:
- Helmi
- Quantum
type: 'Blog'
filters:
Skill level:
- name: "Advanced"
value: false
- name: "Beginner"
value: true
Type:
- name: "Blog"
value: false
- name: "Instructions"
value: true
- name: "News"
value: false
Theme: "Technical"
---

*As quantum computing continues to advance, the architecture of quantum processing units (QPUs) is becoming increasingly critical to the performance of quantum circuits, the backbone of all quantum algorithms. A key factor influencing the design of these circuits is qubit connectivity—how qubits are arranged and interact within a QPU. Different quantum platforms exhibit unique connectivity patterns that directly impact how quantum circuits are implemented and optimized.*
Expand Down
Binary file added content/assets/images/LUMI.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion content/pages/publications.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
layout: page
title: Posts and publications
subtitle: Blog posts, publications, and other material of interest
react_source_files:
- blogs.js
---

{% include post-list.html posts=site.publications %}
{%- include react/root.html id='blogs' -%}
18 changes: 16 additions & 2 deletions content/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,21 @@
"title": "{{ publication.title }}",
"url": "{{ publication.url | relative_url }}",
"date": "{{ publication.date | date: '%-d.%-m.%Y' }}",
"teaser": "{{publication.header.teaser | relative_url}}"
"teaser": "{{publication.header.teaser | relative_url}}",
"filters": {
{%- for category in publication.filters %}
{%- if category[0] == "Theme" -%}
"Theme": "{{ category[1] }}"
{%- else -%}
"{{ category[0] }}": {
{%- for option in category[1] %}
"{{ option.name }}": {{ option.value | jsonify }}{%- unless forloop.last -%},{%- endunless -%}
{%- endfor %}
}
{%- endif -%}
{%- unless forloop.last -%},{%- endunless -%}
{%- endfor %}
}
}{%- unless forloop.last == true -%},{%- endunless -%}
{% endfor %}
]
Expand Down Expand Up @@ -39,7 +53,7 @@
{%- endif -%}
{%- unless forloop.last -%},{%- endunless -%}
{%- endfor %}
}
}
}{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
]
Expand Down
2 changes: 1 addition & 1 deletion src/components/Banner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Banner = ({ title }) => {
<div className='justify-start landscape:justify-start sm:justify-start md:justify-start bg-[url(/assets/images/FiQCI-banner.jpg)] bg-center w-full h-[250px] flex flex-row items-center'>
<div className='mx-8 lg:mx-[100px]'>
<div className='bg-[#0D2B53] w-fit font-bold text-white leading-tight'>
<h1 className='text-5xl px-5 py-4 sm:text-4xl sm:px-6 sm:py-5 md:text-4xl md:px-10 md:py-7'>{title}</h1>
<h1 className='text-4xl px-5 py-4 sm:text-4xl sm:px-6 sm:py-5 md:text-4xl md:px-10 md:py-7'>{title}</h1>
</div>
</div>
</div>
Expand Down
15 changes: 9 additions & 6 deletions src/components/BlogCards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import { CCard, CIcon, CCardContent } from '@cscfi/csc-ui-react';
import { mdiArrowRight } from '@mdi/js';

const BlogCardComponent = props => {
const type = props?.filters?.Type ? Object.entries(props?.filters?.Type)?.filter(field => field[1])[0][0] : "News";
return (
<CCard className="flex flex-auto flex-col flex-wrap border border-gray-200 rounded-none shadow-md overflow-hidden hover:shadow-lg p-0 m-0 w-full"> {/* Adjusted card width */}
<CCard className="flex flex-auto flex-col flex-wrap border border-gray-200 rounded-none shadow-md overflow-hidden hover:shadow-lg p-0 m-0 w-full"> {/* Adjusted card width */}
<img src={props.teaser} alt="Logo" className="w-full h-28 scale-125 object-cover m-0 p-0" /> {/* Reduced image height */}
<CCardContent className="flex flex-col border-none m-0">
<div>
<a
href={props.url}
href={props.url.split(".")[0]}
className="text-md text-black-500 hover:underline font-bold"
>
{props.title}
{props.title.length >= 89 ? props.title.slice(0, 90) + "..." : props.title}
</a>
<p className="text-sm text-gray-500 pb-2 pt-1">
{props.type} | {props.date}
{ type } | {props.date}
</p>
</div>
</CCardContent>
Expand All @@ -26,7 +27,7 @@ const BlogCardComponent = props => {



export const BlogCard = () => {
const BlogCard = () => {
return (
<div className="mx-8 lg:mx-[100px] py-6">
<div className="flex items-center justify-between mb-6">
Expand All @@ -37,7 +38,7 @@ export const BlogCard = () => {
{ SITE.publications.slice(-5).reverse().map(blog => <BlogCardComponent {...blog} />) }
</div>
<div className="mt-4">
<a
<a
href="#"
className="text-sky-800 hover:underline font-bold"
>
Expand All @@ -50,3 +51,5 @@ export const BlogCard = () => {
</div>
);
};

export { BlogCard, BlogCardComponent }
236 changes: 236 additions & 0 deletions src/components/Blogs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import React, { useState, useEffect, useCallback } from 'react';
import '@cscfi/csc-ui-react/css/theme.css';
import {
CPagination, CCheckbox, CSelect, CButton, CModal, CCard,
CCardTitle, CCardContent, CCardActions
} from '@cscfi/csc-ui-react';
import { BlogCardComponent } from './BlogCards';

const BlogFilters = ({ filters, handleFilterChange }) => {
const handleCheckboxChange = useCallback((category, option) => {
handleFilterChange({
...filters,
[category]: { ...filters[category], [option]: !filters[category][option] } //toggle the state
});
}, [filters, handleFilterChange]);

const handleChangeTheme = useCallback(selectedTheme => {
handleFilterChange({ ...filters, Theme: selectedTheme.detail || '' }); //update theme or set to ''
}, [filters, handleFilterChange]);

return (
<div className='flex flex-col gap-2'>
{Object.entries(filters).slice(0, -1).map(([category, options]) => ( //slice(0,-1) to exclude Theme
<FilterCategory
key={category}
category={category}
options={options}
handleCheckboxChange={handleCheckboxChange}
/>
))}
<FilterTheme
selectedTheme={filters.Theme}
handleChangeTheme={handleChangeTheme}
/>
</div>
);
};

//Checkbox filters
const FilterCategory = ({ category, options, handleCheckboxChange }) => (
<div>
<h3 className='font-bold'>{category}</h3>
{Object.keys(options).map(option => ( //generate a chekcbox for each filter category
<CCheckbox
hideDetails={true}
key={option}
checked={options[option]}
onChangeValue={() => handleCheckboxChange(category, option)}
>
<p className='text-sm'>{option}</p>
</CCheckbox>
))}
</div>
);

//Theme filter
const FilterTheme = ({ selectedTheme, handleChangeTheme }) => (
<div>
<p className='font-bold'>Theme</p>
<CSelect
hideDetails={true}
className='py-2'
clearable
value={selectedTheme}
items={[
{ name: 'Hybrid QC+HPC computing', value: 'hybrid QC+HPC computing' },
{ name: 'Programming', value: 'programming' },
{ name: 'Algorithm', value: 'algorithm' },
{ name: 'Technical', value: 'Technical' },
]}
placeholder='Choose a theme'
onChangeValue={handleChangeTheme}
/>
</div>
);

//Modal filter for mobile
const FilterModal = ({ isModalOpen, setIsModalOpen, filters, handleFilterChange }) => {
return (
<CModal
key={isModalOpen ? 'open' : 'closed'}
style={{ overflow: 'scroll' }}
className='overflow-scroll'
value={isModalOpen}
dismissable
onChangeValue={event => setIsModalOpen(event.detail)}
>
<CCard style={{ overflow: 'scroll' }} className='overflow-scroll max-h-[80vh]'>
<CCardTitle>Filters</CCardTitle>
<CCardContent>
<BlogFilters filters={filters} handleFilterChange={handleFilterChange} />
</CCardContent>
<CCardActions justify='end'>
<CButton onClick={() => setIsModalOpen(false)} text>Close</CButton>
</CCardActions>
</CCard>
</CModal>
);
};

//List blogs in a grid with pagination
const BlogsList = ({ title, blogs, paginationOptions, handlePageChange, showFilters, onOpenDialog }) => (
<div>
<div className='flex flex-row justify-between'>
<h2 className='text-3xl font-bold'>{title}</h2>
{showFilters && //to not show the button on every EventsList instance
<CButton
className='flex items-center py-2 lg:hidden'
onClick={() => onOpenDialog()}
>
Filters
</CButton>
}
</div>
{blogs.length ? (
<>
<div className='grid grid-cols-1 py-6 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-6'>
{blogs.slice(
(paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage,
(paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage + paginationOptions.itemsPerPage
).map(blog => (
<BlogCardComponent key={blog.id} {...blog} />
))}
</div>
<CPagination
value={paginationOptions}
hideDetails
onChangeValue={handlePageChange}
control
/>
</>
) : (
<p className='pt-6 pb-8'>No {title.toLowerCase()}.</p>
)}
</div>
);

//Full blogs component
export const Blogs = () => {
const blogs_dict = SITE.publications; //get blogs
const [isModalOpen, setIsModalOpen] = useState(false); //modal control
const [filters, setFilters] = useState({
"Skill level": { "Advanced": false, "Beginner": false },
"Type": { "Blog": false, "Instructions": false, "News": false },
"Theme": "",
}); //filter state

const [options, setOptions] = useState({
itemCount: blogs_dict.length,
itemsPerPage: 8,
currentPage: 1,
pageSizes: [5, 10, 15, 25, 50]
}); //pagination control

const [filteredBlogs, setFilteredBlogs] = useState(blogs_dict);

useEffect(() => {
document.body.classList.add("min-w-fit");
}, []);

useEffect(() => {
document.body.style.overflow = isModalOpen ? 'hidden' : 'visible';
return () => {
document.body.style.overflow = 'visible';
};
}, [isModalOpen]);

useEffect(() => { //set filteredBlogs everytime filters changes
const applyFilters = (blog) => {
if (filters.Theme && blog?.filters?.Theme !== filters.Theme) {
return false;
}
// For every other filter category...
return Object.entries(filters).every(([category, options]) => {
// Skip the "Theme" category here
if (category === "Theme") return true;

// Create an array of only the options that are checked (active)
const activeOptions = Object.entries(options).filter(([_, checked]) => checked);

// If no options are active in this category, do not filter out the event:
if (activeOptions.length === 0) return true;

// Otherwise, require that at least one active option is true in the event:
return activeOptions.some(([option]) => blog?.filters?.[category]?.[option]);
});
};

//apply filter
const filtered = blogs_dict.filter(applyFilters);
setFilteredBlogs(filtered);

//also update item count in pagination options
setOptions(prev => ({ ...prev, itemCount: filtered.length }));
}, [filters]);

const onOpenDialog = () => { //modal control
setIsModalOpen(true);
};

const handlePageChange = (setOptions) => (blog) => {
// blog.detail.currentPage should be the new page number.
setOptions(prev => ({ ...prev, currentPage: blog.detail.currentPage }));
};

const handleFilterChange = (newFilters) => {
setFilters(newFilters);
setOptions(prev => ({ ...prev, currentPage: 1 }));
};

return (
<div className='flex flex-col items-top mb-2'>
<div className='mt-8 mx-8 lg:mx-[100px] flex lg:grid grid-cols-5 gap-8'>
<div className='hidden lg:block lg:sticky lg:top-16 lg:self-start z-10'>
<BlogFilters filters={filters} handleFilterChange={handleFilterChange} />
</div>
<div className='md:py-0 col-span-4'>
<BlogsList
title='Blogs'
blogs={[...filteredBlogs].reverse()}
paginationOptions={options}
handlePageChange={handlePageChange(setOptions)}
showFilters={true}
onOpenDialog={onOpenDialog}
/>
</div>
</div>
<FilterModal
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
filters={filters}
handleFilterChange={handleFilterChange}
/>
</div>
);
};
Loading