Skip to content
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

[Feat] :Adding a move to top icon #607

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
513 changes: 513 additions & 0 deletions d

Large diffs are not rendered by default.

67 changes: 18 additions & 49 deletions src/components/CodeEmbed/frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ interface CodeBundle {
scripts?: string[];
}

/*
* Wraps the given code in a p5.js setup function if it doesn't already have one.
*/
// Function to wrap the sketch code
const wrapSketch = (sketchCode?: string) => {
if (sketchCode !== "" && !sketchCode?.includes("setup")) {
if (sketchCode && !sketchCode.includes("setup")) {
return `
function setup() {
createCanvas(100, 100);
Expand All @@ -24,12 +22,9 @@ const wrapSketch = (sketchCode?: string) => {
return sketchCode;
};

/*
* Wraps the given code in a html document for display.
* Single object argument, all properties optional:
*/
const wrapInMarkup = (code: CodeBundle) =>
`<!DOCTYPE html>
// Function to wrap code in HTML markup
const wrapInMarkup = (code: CodeBundle) => `
<!DOCTYPE html>
<meta charset="utf8" />
<base href="${code.base || "/assets/"}" />
<style type='text/css'>
Expand All @@ -45,11 +40,9 @@ ${code.css || ""}
${(code.scripts ? [cdnLibraryUrl, ...code.scripts] : []).map((src) => `<script type="text/javascript" src="${src}"></script>`).join('\n')}
<body>${code.htmlBody || ""}</body>
<script id="code" type="text/javascript">${wrapSketch(code.js) || ""}</script>
${(code.scripts?.length ?? 0) > 0 ? '' : `
${(code.scripts?.length ?? 0) === 0 ? `
<script type="text/javascript">
// Listen for p5.min.js text content and include in iframe's head as script
window.addEventListener("message", event => {
// Include check to prevent p5.min.js from being loaded twice
const scriptExists = !!document.getElementById("p5ScriptTagInIframe");
if (!scriptExists && event.data?.sender === '${cdnLibraryUrl}') {
const p5ScriptElement = document.createElement('script');
Expand All @@ -58,11 +51,12 @@ ${(code.scripts?.length ?? 0) > 0 ? '' : `
p5ScriptElement.textContent = event.data.message;
document.head.appendChild(p5ScriptElement);
}
})
});
</script>
`}
` : ''}
`.replace(/\u00A0/g, " ");

// Props interface for CodeFrame
export interface CodeFrameProps {
jsCode: string;
cssCode?: string;
Expand All @@ -74,22 +68,13 @@ export interface CodeFrameProps {
scripts?: string[];
}

/*
* Component that uses an iframe to run code with the p5 library included.
*
*/
// CodeFrame component
export const CodeFrame = (props: CodeFrameProps) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const p5ScriptTag = document.getElementById(
"p5ScriptTag",
) as HTMLScriptElement;
const p5ScriptTag = document.getElementById("p5ScriptTag") as HTMLScriptElement;

// For performance, set the iframe to display:none when
// not visible on the page. This will stop the browser
// from running `draw` every frame, which helps performance
// on pages with multiple sketches, and causes sketch
// animations only to start when they become visible.
// Handle visibility of the iframe
useLayoutEffect(() => {
if (!containerRef.current) return;
const { IntersectionObserver } = window;
Expand All @@ -98,10 +83,9 @@ export const CodeFrame = (props: CodeFrameProps) => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!iframeRef.current) return;
if (entry.isIntersecting) {
if (entry.isIntersecting && iframeRef.current) {
iframeRef.current.style.removeProperty("display");
} else {
} else if (iframeRef.current) {
iframeRef.current.style.display = "none";
}
});
Expand All @@ -113,28 +97,14 @@ export const CodeFrame = (props: CodeFrameProps) => {
return () => observer.disconnect();
}, []);

// Fetch p5.js script and post message
useEffect(() => {
(async () => {
if (!p5ScriptTag || !iframeRef.current) return;

/*
* Uses postMessage to receive the text content of p5.min.js, to be included
* in a script so p5.js functions can be called.
*
* Rather than including the script with <script src=p5LibraryUrl>, this had
* to be done because caching this resource with the service worker or browser
* cache, so the cached version could be used by an iframe isn't currently
* supported on all major browsers.
* It would instead, cause multiple downloads of p5.min.js on page load for
* each example, and on re-running a CodeFrame.
*
* See https://github.com/w3c/ServiceWorker/issues/765.
*/
try {
const p5ScriptText = await fetch(p5ScriptTag.src).then((res) =>
res.text(),
);
iframeRef.current.contentWindow?.postMessage(
const p5ScriptText = await fetch(p5ScriptTag.src).then((res) => res.text());
iframeRef.current?.contentWindow?.postMessage(
{
sender: cdnLibraryUrl,
message: p5ScriptText,
Expand All @@ -143,10 +113,9 @@ export const CodeFrame = (props: CodeFrameProps) => {
);
} catch (e) {
console.error(`Error loading ${p5ScriptTag.src}`);
return;
}
})();
}, [props.jsCode]);
}, [p5ScriptTag, props.jsCode]);

return (
<div
Expand Down
29 changes: 12 additions & 17 deletions src/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Dropdown = ({
setIsOpen(!isOpen);
};

// Handle blur event, focus moving away from open dropdown
// Handle blur event, focus moving away from open dropdown
const handleBlur = () => {
setTimeout(() => {
if (
Expand All @@ -55,14 +55,14 @@ export const Dropdown = ({
}, 0);
};

// Handle option selection
// Handle option selection
const handleOptionClick = (option: DropdownOption) => {
if (variant === "dropdown") {
setSelected(option.value);
setIsOpen(false);
}

// With a radio variant, multiple options can be selected
// With a radio variant, multiple options can be selected
if (variant === "radio" && Array.isArray(selected)) {
const newSelected = selected.includes(option.value)
? selected.filter((value) => value !== option.value)
Expand Down Expand Up @@ -102,15 +102,20 @@ export const Dropdown = ({
}
}, [isOpen]);

// Determine if an option is selected
// Determine if an option is selected
const isSelected = (option: DropdownOption) => {
if (variant === "dropdown") {
return selected === option.value || selected === option.id;
}
return selected.includes(option.value);
};
// Render the collapsed dropdown button
const checkmarkSVG = (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="m 16.371092,6.6210937 c 0.513245,0.5203285 0.507123,1.3583198 -0.01367,1.8710938 L 9.8535158,14.896484 c -0.5147559,0.506589 -1.3407128,0.506589 -1.8554687,0 L 3.6425783,10.607422 C 3.1217848,10.094649 3.1156616,9.2566574 3.6289063,8.7363283 4.1416797,8.2155349 4.9796709,8.2094117 5.5,8.7226563 L 8.9257813,12.097656 14.5,6.6074219 c 0.519236,-0.5121001 1.35949,-0.5058647 1.871092,0.013672 z M 10,20 C 15.5228,20 20,15.5228 20,10 20,4.47715 15.5228,0 10,0 4.47715,0 0,4.47715 0,10 0,15.5228 4.47715,20 10,20 Z" fill="currentColor" />
</svg>
);

// Render the collapsed dropdown button
const renderCollapsedDropdown = () => (
<button
className={styles.selected}
Expand All @@ -132,23 +137,13 @@ export const Dropdown = ({
</div>
</button>
);

// Render the expanded dropdown options
const renderExpandedDropdown = () => (
<ul className={styles.options} role="listbox" tabIndex={-1}>
{options.map((option, index) => (
<li
key={option.value}
className={styles.option}
role="option"
aria-selected={isSelected(option)}
>
<li key={option.value} className={styles.option} role="option" aria-selected={isSelected(option)}>
<div className={styles.icon}>
<Icon
kind={
isSelected(option) ? "option-selected" : "option-unselected"
}
/>
{isSelected(option) ? checkmarkSVG : <Icon kind="option-unselected" />}
</div>
<button
onClick={() => handleOptionClick(option)}
Expand Down
47 changes: 46 additions & 1 deletion src/components/Footer/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,52 @@ const bannerEntry = await getEntry("banner", currentLocale);
</ul>
</div>
</div>

<!-- Clean and Simple "Move to Top" Button -->
<button id="moveToTop" title="Go to top" class="move-to-top">
</button>
</footer>

{bannerEntry && !bannerEntry.data.hidden && (<Banner entry={bannerEntry} />)}

<style></style>
<script>
// Show the button when the user scrolls 100px down from the top
window.onscroll = function() {
const moveToTopButton = document.getElementById("moveToTop");
if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) {
moveToTopButton.style.display = "block";
} else {
moveToTopButton.style.display = "none";
}
};

// Scroll smoothly to the top when the button is clicked
document.getElementById("moveToTop").addEventListener("click", function() {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
</script>

<style>
.move-to-top {
position: fixed;
bottom: 60px; /* Slightly higher than before */
right: 20px;
background-color: #000; /* Solid black background */
color: #fff; /* White text */
border: none; /* Remove borders */
padding: 10px 15px; /* Comfortable padding */
border-radius: 50px; /* Rounded button */
font-size: 24px; /* Larger text for visibility */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Subtle shadow for depth */
cursor: pointer; /* Pointer on hover */
display: none; /* Hidden by default */
transition: background-color 0.3s ease, transform 0.3s ease;
z-index: 1000; /* Ensure it appears above other content */
}

.move-to-top:hover {
background-color: #333; /* Darken on hover */
transform: translateY(-2px); /* Slight upward shift on hover */
}
</style>
12 changes: 6 additions & 6 deletions src/components/SearchProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "preact/hooks";
import { useEffect, useState, useCallback } from "preact/hooks";
import Fuse, { type FuseResult } from "fuse.js";
import SearchResults from "../SearchResults";
import { defaultLocale } from "@/src/i18n/const";
Expand Down Expand Up @@ -29,8 +29,8 @@ const SearchProvider = ({
const [searchTerm, setSearchTerm] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);

// Flattens the search index data
const flattenData = (data: FuseResult<SearchResult>) => {
// Memoize flattenData function
const flattenData = useCallback((data: FuseResult<SearchResult>) => {
const flatData: SearchResult[] = [];
let flatId = 0;
Object.entries(data).forEach(([category, entries]) => {
Expand All @@ -53,7 +53,7 @@ const SearchProvider = ({
});

return flatData;
};
}, [currentLocale]); // Include currentLocale in dependencies

// Read the search term from query params on first load
useEffect(() => {
Expand Down Expand Up @@ -86,7 +86,7 @@ const SearchProvider = ({
fetch(`/search-indices/${currentLocale}.json`)
.then((response) => response.json())
.then((data) => {
flatData = flattenData(data);
flatData = flattenData(data); // Use memoized function

const fuseOptions = {
includeScore: true,
Expand All @@ -110,7 +110,7 @@ const SearchProvider = ({
.catch((error) =>
console.error("Error fetching or indexing data:", error),
);
}, [searchTerm, currentLocale]);
}, [searchTerm, currentLocale, flattenData]); // Added flattenData to dependencies

const handleSearchTermChange = (term?: string) => {
if (term !== undefined) {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/_utils-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export const rewriteRelativeLink = (url: string): string => {
!updatedUrl.endsWith('/') &&
!/(\.\w+)$/.exec(updatedUrl) &&
!updatedUrl.includes('?') &&
!/#([\w\-]+)$/.exec(updatedUrl)
!/#([\w-]+)$/.exec(updatedUrl) // Removed the unnecessary escape character
) {
updatedUrl += '/';
}

return updatedUrl;
};
};
4 changes: 2 additions & 2 deletions src/scripts/parsers/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const parseLibraryReference =

// Fix p5.sound classes
for (const key in soundData.classes) {
const newName = 'p5.' + soundData.classes[key].name;
const newName = `p5.${ soundData.classes[key].name}`;
const updated = {
...soundData.classes[key],
name: newName,
Expand All @@ -49,7 +49,7 @@ export const parseLibraryReference =
delete soundData.classes[key];
}
for (const item of soundData.classitems) {
item.class = 'p5.' + item.class;
item.class = `p5.${ item.class}`;
}

const combined = await combineYuidocData(
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let latestRelease = p5Version;
// If the latest release is a version number (e.g. 1.10.0) without a 'v'
// prefix, add the v prefix
if (/^\d+\.\d+\.\d+$/.exec(latestRelease)) {
latestRelease = 'v' + latestRelease;
latestRelease = `v${ latestRelease}`;
}

export const p5RepoUrl = "https://github.com/processing/p5.js.git";
Expand Down
Loading