From 875347bdb39abdf3178c3a5c1f2675c21c6d330f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cintia=20S=C3=A1nchez=20Garc=C3=ADa?= Date: Mon, 27 May 2024 11:50:22 +0200 Subject: [PATCH] Prepare web application for embed (#601) Signed-off-by: Cintia Sanchez Garcia --- web/public/static/demo-embed/index.html | 226 ++++++++++++++++++++++++ web/src/context/AppContextProvider.tsx | 113 ++++++++++-- web/src/data.tsx | 4 + web/src/demo-embed/index.html | 226 ++++++++++++++++++++++++ web/src/index.css | 25 +++ web/src/layout/common/Card.tsx | 47 +++-- web/src/layout/index.tsx | 7 +- web/src/layout/navigation/Navbar.tsx | 17 +- web/src/layout/navigation/Searchbar.tsx | 30 +++- web/src/layout/search/Filters.tsx | 10 +- web/src/layout/search/FiltersInLine.tsx | 22 ++- web/src/layout/search/Search.module.css | 4 + web/src/layout/search/index.tsx | 125 +++++++++---- 13 files changed, 775 insertions(+), 81 deletions(-) create mode 100644 web/public/static/demo-embed/index.html create mode 100644 web/src/demo-embed/index.html diff --git a/web/public/static/demo-embed/index.html b/web/public/static/demo-embed/index.html new file mode 100644 index 0000000..aaa61e1 --- /dev/null +++ b/web/public/static/demo-embed/index.html @@ -0,0 +1,226 @@ + + + + Clotributor embed + + + +
+ +
+
+
+
+
Title
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+
+
+
Contribute to the CNCF Ecosystem
+

+ Welcome! Are you interested in contributing to one of CNCF hosted projects? This repository should help you. It contains information and guidelines about contributions to CNCF projects. +

+

+ CNCF offers multiple ways to start contributing to the CNCF ecosystem, including either foundation-wide and project-wide opportunities. +

+

+ However, contributing to the CNCF ecosystem is not just about coding. There are many other ways to contribute to the CNCF ecosystem, including writing documentation, creating tutorials, bringing ideas to meetings, organizing meetups, and more. +

+
+
+
+ +
+
+
+ + diff --git a/web/src/context/AppContextProvider.tsx b/web/src/context/AppContextProvider.tsx index 1d8dcc6..46510d2 100644 --- a/web/src/context/AppContextProvider.tsx +++ b/web/src/context/AppContextProvider.tsx @@ -1,12 +1,14 @@ import { detectActiveThemeMode, useSystemThemeMode } from 'clo-ui'; -import { isNull } from 'lodash'; +import isNull from 'lodash/isNull'; import { createContext, Dispatch, useContext, useEffect, useReducer, useState } from 'react'; -import { Prefs, SortBy } from '../types'; +import { AVAILABLE_THEMES, DEFAULT_SORT_BY, DEFAULT_THEME, EMBED_PARAM, EMBED_SEARCH_LIMIT } from '../data'; +import { Prefs, SortBy, ThemePrefs } from '../types'; import lsStorage from '../utils/localStoragePreferences'; interface AppState { prefs: Prefs; + isEmbed: boolean; } interface Props { @@ -15,13 +17,15 @@ interface Props { const initialState: AppState = { prefs: lsStorage.getPrefs(), + isEmbed: false, }; type Action = - | { type: 'updateTheme'; theme: string } + | { type: 'updateTheme'; theme: string; isEmbed?: boolean } | { type: 'updateEffectiveTheme'; theme: string } | { type: 'updateLimit'; limit: number } - | { type: 'updateSort'; by: SortBy }; + | { type: 'updateSort'; by: SortBy } + | { type: 'updateEmbedStatus'; isEmbed: boolean; theme: ThemePrefs }; export const AppContext = createContext<{ ctx: AppState; @@ -32,8 +36,12 @@ export const AppContext = createContext<{ dispatch: () => null, }); -export function updateTheme(theme: string) { - return { type: 'updateTheme', theme }; +export function updateEmbedStatus(isEmbed: boolean, theme: ThemePrefs) { + return { type: 'updateEmbedStatus', isEmbed, theme }; +} + +export function updateTheme(theme: string, isEmbed?: boolean) { + return { type: 'updateTheme', theme, isEmbed }; } export function updateEffectiveTheme(theme: string) { @@ -48,17 +56,61 @@ export function updateSort(by: SortBy) { return { type: 'updateSort', by }; } -export function updateActiveStyleSheet(current: string) { +export function updateActiveStyleSheet(current: string, isEmbed?: boolean) { document.getElementsByTagName('html')[0].setAttribute('data-theme', current); document .querySelector(`meta[name='theme-color']`)! - .setAttribute('content', current === 'light' ? '#2a0552' : '#0f0e11'); + .setAttribute('content', current === 'light' ? (isEmbed ? '#343a40' : '#2a0552') : '#0f0e11'); } +const updateEmbedColorsTheme = () => { + const style = document.createElement('style'); + style.appendChild(document.createTextNode('')); + document.head.appendChild(style); + + const colorsList = [ + '--clo-primary: #31363F;', + '--clo-secondary: #343a40;', + '--clo-primary-50: rgba(49, 54, 63, 0.5);', + '--clo-primary-5: rgba(49, 54, 63, 0.05);', + '--clo-secondary-900: #1a1a1a;', + '--clo-secondary-50: rgba(52, 58, 64, 0.5);', + '--clo-secondary-15: rgba(52, 58, 64, 0.15);', + '--clo-secondary-5: rgba(52, 58, 64, 0.05);', + '--highlighted: #0175e4;', + ]; + + const darkColorsList = ['--highlighted: #343a40;']; + + style.sheet!.insertRule(`[data-theme='light'] { ${colorsList.join('')} }`, 0); + style.sheet!.insertRule(`[data-theme='dark'] { ${darkColorsList.join('')} }`, 0); +}; + export function appReducer(state: AppState, action: Action) { let prefs; let effective; + switch (action.type) { + case 'updateEmbedStatus': + if (action.isEmbed) { + updateEmbedColorsTheme(); + // Add style to html + document.getElementsByTagName('html')[0].classList.add('embed'); + } + + prefs = { + theme: { ...action.theme }, + search: { + limit: EMBED_SEARCH_LIMIT, + sortBy: DEFAULT_SORT_BY, + }, + }; + + return { + ...state, + isEmbed: action.isEmbed, + }; + case 'updateTheme': effective = action.theme === 'automatic' ? detectActiveThemeMode() : action.theme; prefs = { @@ -70,7 +122,7 @@ export function appReducer(state: AppState, action: Action) { }; lsStorage.setPrefs(prefs); - updateActiveStyleSheet(effective); + updateActiveStyleSheet(effective, action.isEmbed); return { ...state, prefs: prefs, @@ -130,19 +182,52 @@ function AppContextProvider(props: Props) { const activeProfilePrefs = lsStorage.getPrefs(); const [ctx, dispatch] = useReducer(appReducer, { prefs: activeProfilePrefs, + isEmbed: false, }); const [activeInitialTheme, setActiveInitialTheme] = useState(null); + const [isAutomatic, setIsAutomatic] = useState(ctx.prefs.theme.configured === 'automatic'); useEffect(() => { - const theme = - activeProfilePrefs.theme.configured === 'automatic' - ? detectActiveThemeMode() - : activeProfilePrefs.theme.configured || activeProfilePrefs.theme.effective; // Use effective theme if configured is undefined + let isEmbed = false; + let theme = detectActiveThemeMode() as string; + let configured = DEFAULT_THEME; + const search = new URLSearchParams(location.search); + if (search.has(EMBED_PARAM) && search.get(EMBED_PARAM) === 'true') { + isEmbed = true; + + if (search.has('theme') && AVAILABLE_THEMES.includes(search.get('theme')!)) { + configured = search.get('theme')!; + theme = search.get('theme')!; + if (search.get('theme')! === 'auto') { + configured = 'automatic'; + theme = detectActiveThemeMode(); + setIsAutomatic(true); + } + } else { + setIsAutomatic(true); + } + + dispatch({ + type: 'updateEmbedStatus', + isEmbed, + theme: { + effective: theme, + configured: configured, + }, + }); + } + + if (!isEmbed) { + theme = + activeProfilePrefs.theme.configured === 'automatic' + ? detectActiveThemeMode() + : activeProfilePrefs.theme.configured || activeProfilePrefs.theme.effective; // Use effective theme if configured is undefined + } updateActiveStyleSheet(theme); setActiveInitialTheme(theme); }, []); - useSystemThemeMode(ctx.prefs.theme.configured === 'automatic', dispatch); + useSystemThemeMode(isAutomatic, dispatch); if (isNull(activeInitialTheme)) return null; diff --git a/web/src/data.tsx b/web/src/data.tsx index 03759d5..8ce436b 100644 --- a/web/src/data.tsx +++ b/web/src/data.tsx @@ -1,7 +1,11 @@ import { FilterKind, SearchTipItem, SortBy } from './types'; export const DEFAULT_SORT_BY = SortBy.MostRecent; +export const DEFAULT_THEME = 'automatic'; export const DEFAULT_SEARCH_LIMIT = 20; +export const EMBED_SEARCH_LIMIT = 10; +export const EMBED_PARAM = 'embed'; +export const AVAILABLE_THEMES = ['light', 'dark', 'auto']; export const SORT_OPTIONS = [ { diff --git a/web/src/demo-embed/index.html b/web/src/demo-embed/index.html new file mode 100644 index 0000000..aaa61e1 --- /dev/null +++ b/web/src/demo-embed/index.html @@ -0,0 +1,226 @@ + + + + Clotributor embed + + + +
+ +
+
+
+
+
Title
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+
+
+
Contribute to the CNCF Ecosystem
+

+ Welcome! Are you interested in contributing to one of CNCF hosted projects? This repository should help you. It contains information and guidelines about contributions to CNCF projects. +

+

+ CNCF offers multiple ways to start contributing to the CNCF ecosystem, including either foundation-wide and project-wide opportunities. +

+

+ However, contributing to the CNCF ecosystem is not just about coding. There are many other ways to contribute to the CNCF ecosystem, including writing documentation, creating tutorials, bringing ideas to meetings, organizing meetups, and more. +

+
+
+
+ +
+
+
+ + diff --git a/web/src/index.css b/web/src/index.css index d2ad23b..61bde54 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -13,3 +13,28 @@ code { .fw-semibold { font-weight: 600 !important; } + +/* Embed */ +.embed #clo-wrapper { + height: 100%; + overflow: auto; + overscroll-behavior: contain; +} + +.embed::after { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; + border: 1px solid var(--clo-secondary); + pointer-events: none; +} + +@media screen and (min-width: 992px) { + .embed .container-lg { + max-width: 95% !important; + } +} diff --git a/web/src/layout/common/Card.tsx b/web/src/layout/common/Card.tsx index b06d009..a288dea 100644 --- a/web/src/layout/common/Card.tsx +++ b/web/src/layout/common/Card.tsx @@ -33,18 +33,29 @@ interface Props { const Card = (props: Props) => { const navigate = useNavigate(); const { ctx } = useContext(AppContext); + const isEmbed = ctx.isEmbed; const { effective } = ctx.prefs.theme; const [availableTopics, setAvailableTopics] = useState([]); const isMaintainersWantedAvailable: boolean = !isUndefined(props.issue.project.maintainers_wanted) && props.issue.project.maintainers_wanted.enabled; + const getExtraFilter = () => { + if (isEmbed) { + // Scroll to top on every change + document.getElementById('clo-wrapper')!.scrollTop = 0; + return { [FilterKind.Foundation]: [props.issue.project.foundation] }; + } else { + return undefined; + } + }; + const searchByText = (text: string) => { navigate({ pathname: '/search', search: prepareQueryString({ pageNumber: 1, ts_query_web: text.toLowerCase(), - filters: {}, + filters: { ...getExtraFilter() }, }), }); }; @@ -54,7 +65,7 @@ const Card = (props: Props) => { pathname: '/search', search: prepareQueryString({ pageNumber: 1, - filters: { [filter]: [value] }, + filters: { [filter]: [value], ...getExtraFilter() }, }), }); }; @@ -65,7 +76,7 @@ const Card = (props: Props) => { search: prepareQueryString({ pageNumber: 1, mentor_available: true, - filters: {}, + filters: { ...getExtraFilter() }, }), }); }; @@ -76,7 +87,7 @@ const Card = (props: Props) => { search: prepareQueryString({ pageNumber: 1, good_first_issue: true, - filters: {}, + filters: { ...getExtraFilter() }, }), }); }; @@ -141,11 +152,13 @@ const Card = (props: Props) => { className="d-none d-sm-flex me-2" /> )} - searchByFilter(FilterKind.Foundation, props.issue.project.foundation)} - /> + {!isEmbed && ( + searchByFilter(FilterKind.Foundation, props.issue.project.foundation)} + /> + )} @@ -203,13 +216,17 @@ const Card = (props: Props) => {
- searchByFilter(FilterKind.Foundation, props.issue.project.foundation)} - /> + {!isEmbed && ( + searchByFilter(FilterKind.Foundation, props.issue.project.foundation)} + /> + )} + {props.issue.project.maturity && ( - + )}
diff --git a/web/src/layout/index.tsx b/web/src/layout/index.tsx index 9f682f5..4a71d50 100644 --- a/web/src/layout/index.tsx +++ b/web/src/layout/index.tsx @@ -1,13 +1,16 @@ import { useScrollRestorationFix } from 'clo-ui'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; import { Outlet } from 'react-router-dom'; +import { AppContext } from '../context/AppContextProvider'; import styles from './Layout.module.css'; import Footer from './navigation/Footer'; import Navbar from './navigation/Navbar'; const Layout = () => { + const { ctx } = useContext(AppContext); const [invisibleFooter, setInvisibleFooter] = useState(false); + const isEmbed = ctx.isEmbed; useScrollRestorationFix(); @@ -17,7 +20,7 @@ const Layout = () => {
-