Skip to content

Commit d1a4f16

Browse files
authored
Merge pull request #10 from NetLogo/intro_redesign
Intro redesign
2 parents e9fa75b + 6303bea commit d1a4f16

18 files changed

+816
-465
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## NetLogo Website Frontend
22
This is the frontend repository of the new NetLogo website.
3-
Currently, the demo is deployed at https://netlogo.github.io/foundation-website-frontend/
3+
Currently, the demo is deployed at https://netlogo.github.io/
44
The backend is hosted at https://backend.netlogo.org/
55

66
### Architecture

astro.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import react from '@astrojs/react';
44
export default defineConfig({
55
integrations: [react()],
66
site: 'https://netlogo.github.io/',
7-
base: '/foundation-website-frontend'
7+
base: '/'
88
});

src/assets/NetLogoOrgLogo.svg

Lines changed: 10 additions & 0 deletions
Loading

src/assets/intro-turtles.svg

Lines changed: 26 additions & 0 deletions
Loading

src/assets/logo-text.svg

Lines changed: 4 additions & 0 deletions
Loading

src/components/index/body.tsx

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useRef } from "react";
22
import { Announcement } from "../layout/announcement";
3-
import { Intro } from "./intro";
3+
import { Intro } from "./introduction";
44
import { WhyNetLogo } from "./why-netlogo";
55
import { GetNetLogo } from "./get-netlogo";
66
import { Community } from "./community";
@@ -27,7 +27,7 @@ function Body({
2727
competitions,
2828
publications,
2929
communityContent,
30-
siteData
30+
siteData,
3131
}: BodyProps) {
3232
const {
3333
introduction,
@@ -36,38 +36,25 @@ function Body({
3636
get_netlogo,
3737
featured_partners,
3838
community,
39-
announcement
39+
announcement,
4040
} = siteData;
4141

4242
const [showAnnouncement, setShowAnnouncement] = useState(!!announcement);
43-
44-
const getNetLogoSection = useRef<HTMLDivElement | null>(null);
45-
4643
return (
4744
<div className="body">
48-
49-
{showAnnouncement && (
50-
45+
{showAnnouncement && (
5146
<Announcement
5247
announcement={announcement}
5348
setShowAnnouncement={setShowAnnouncement}
5449
/>
5550
)}
5651

57-
<Intro
58-
intro_data={introduction}
59-
intro_splash_data={intro_splash}
60-
/>
52+
<Intro intro_data={introduction} intro_splash_data={intro_splash} />
6153
<WhyNetLogo page_data={why_netlogo} />
6254
<GetNetLogo page_data={get_netlogo} />
6355
<Community communityPosts={communityContent} page_data={community} />
6456
<FeaturedPartners featured_partners={featured_partners} />
6557

66-
{/* <News upcomingEvents={upcomingEvents}
67-
upcomingWorkshops={upcomingWorkshops}
68-
competitions={competitions}
69-
publications={publications}/> */}
70-
7158
<MailingList />
7259
</div>
7360
);

src/components/index/intro-splash.tsx

Lines changed: 205 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,230 @@
1-
import { useState, useEffect, type Dispatch } from "react";
2-
import { DemoDisplay } from "./demo-display";
1+
import React, { useState, useMemo, useEffect } from "react";
2+
import ReactMarkdown from "react-markdown";
33
import "./styles/intro-splash.css";
4-
import type { IntroSplashEntry } from "../../utils/api";
4+
import type { IntroSplashEntry, FeaturedItem } from "../../utils/api";
55

6+
interface WordPair {
7+
word: string;
8+
url: string;
9+
}
10+
11+
interface ImagePair {
12+
word: string;
13+
image: {
14+
id: string;
15+
};
16+
}
17+
18+
// Props interfaces
619
interface IntroSplashProps {
720
page_data: IntroSplashEntry[];
821
}
922

10-
interface IntroTabProps {
23+
interface LinksColumnProps {
1124
title: string;
12-
tab_number: number;
13-
current_tab: number;
14-
icon_key: string;
15-
click_handler: (tab: number) => void;
25+
link_entries: WordPair[];
1626
}
1727

18-
const IntroTab = ({
19-
title,
20-
tab_number,
21-
icon_key,
22-
current_tab,
23-
click_handler,
24-
}: IntroTabProps) => {
25-
const backend_url = import.meta.env.PUBLIC_BACKEND_URL;
28+
interface ImagesColumnProps {
29+
title: string;
30+
image_entries: ImagePair[];
31+
}
32+
33+
// Constants
34+
const backend_url = import.meta.env.PUBLIC_BACKEND_URL;
35+
36+
// Helper functions
37+
const handleLinkClick = (url: string) => {
38+
const fullUrl =
39+
url.startsWith("http://") || url.startsWith("https://")
40+
? url
41+
: `https://${url}`;
42+
43+
window.open(fullUrl, "_blank");
44+
};
45+
46+
const createImageURL = (imageId: string) => {
47+
return `${backend_url}/assets/${imageId}`;
48+
};
49+
50+
// Sub-components
51+
const ImagesColumn = ({ title, image_entries }: ImagesColumnProps) => {
52+
const images_object = image_entries?.reduce(
53+
(acc: any, entry: ImagePair) => {
54+
acc[entry.image.id] = entry.word;
55+
return acc;
56+
},
57+
{}
58+
);
59+
60+
const [currentImageId, setCurrentImageId] = useState<string>(
61+
image_entries[0]?.image.id
62+
);
63+
2664
return (
27-
<div className={`${current_tab === tab_number ? "current-tab" : ""}`}>
28-
<div
29-
className={`intro-anim-option `}
30-
onClick={() => click_handler(tab_number)}
31-
style={{
32-
animationDelay: "1500ms",
33-
animationTimingFunction: "ease-out",
34-
animationDuration: "300ms",
35-
}}
36-
>
37-
<div className="intro-anim-text-icon">
38-
<div
39-
className={`intro-anim-icon ${current_tab === tab_number ? "current-tab-icon" : ""}`}
40-
>
41-
<img
42-
src={`${backend_url}/assets/${icon_key}`}
43-
alt="Visual Icon"
44-
/>
45-
</div>
46-
<span
47-
className={`intro-anim-text ${current_tab === tab_number ? "current-tab-text" : ""}`}
48-
>
49-
{title}
50-
</span>
65+
<div className="image-column-container">
66+
<img
67+
className="current-column-image"
68+
src={createImageURL(currentImageId)}
69+
alt={images_object[currentImageId]}
70+
/>
71+
<div className="column-container">
72+
<div className="column-title">{title}</div>
73+
74+
<div className="column-card">
75+
{image_entries?.map((pair, index) => (
76+
<div key={index} className="column-entry">
77+
<a
78+
className="entry-text"
79+
onClick={(e) => {
80+
e.preventDefault();
81+
setCurrentImageId(pair.image.id);
82+
}}
83+
>
84+
{pair.word}
85+
</a>
86+
</div>
87+
))}
88+
<div className="column-footer">and many more...</div>
5189
</div>
5290
</div>
5391
</div>
5492
);
5593
};
5694

57-
const IntroAnimation = ({ page_data }: IntroSplashProps) => {
58-
const [currentTab, setCurrentTab] = useState(0);
95+
const LinksColumn = ({ title, link_entries }: LinksColumnProps) => {
96+
return (
97+
<div className="column-container">
98+
<div className="column-title">{title}</div>
99+
100+
<div className="column-card">
101+
{link_entries?.map((entry, index) => (
102+
<div key={index} className="column-entry">
103+
<a
104+
href={entry.url}
105+
target="_blank"
106+
className="entry-text"
107+
onClick={(e) => {
108+
e.preventDefault();
109+
handleLinkClick(entry.url);
110+
}}
111+
>
112+
{entry.word}
113+
</a>
114+
</div>
115+
))}
116+
<div className="column-footer">and many more...</div>
117+
</div>
118+
</div>
119+
);
120+
};
121+
122+
// Main component
123+
const IntroSplash = ({ page_data }: IntroSplashProps) => {
124+
// State
125+
const [introData, setIntroData] = useState<IntroSplashEntry[]>(page_data);
126+
const [currentTab, setCurrentTab] = useState(introData[0]?.title || "");
127+
128+
// Process learn more links
129+
useEffect(() => {
130+
setIntroData((prev) => {
131+
prev.forEach((item: IntroSplashEntry) => {
132+
if (item.learn_more_link) {
133+
const url = item.learn_more_link;
134+
const fullUrl =
135+
url.startsWith("http://") || url.startsWith("https://")
136+
? url
137+
: `https://${url}`;
138+
139+
item.description += ` [Learn more →](${fullUrl})`;
140+
}
141+
});
142+
143+
return [...prev];
144+
});
145+
}, []);
146+
147+
// Memoized values
148+
const currentTabData = useMemo(() => {
149+
return introData.find((tab) => tab.title === currentTab);
150+
}, [currentTab, introData]);
151+
152+
const FeaturedItems: FeaturedItem[] | undefined =
153+
currentTabData?.featured_items;
154+
155+
// Check if all items are images (type 1)
156+
const allItemsAreImages = useMemo(() => {
157+
if (!FeaturedItems || FeaturedItems.length === 0) return false;
158+
return FeaturedItems.every((item) => Number(item?.type) === 1);
159+
}, [FeaturedItems]);
160+
161+
// Render functions
162+
const renderFeaturedItem = (item: FeaturedItem, index: number) => {
163+
const itemType = Number(item?.type);
59164

60-
const handleTabClick = (tab: number) => {
61-
setCurrentTab(tab);
165+
switch (itemType) {
166+
case 1:
167+
return (
168+
<img
169+
className={`featured-image ${allItemsAreImages ? "uniform-height" : ""}`}
170+
src={createImageURL(item?.image?.id || "")}
171+
alt={currentTabData?.title}
172+
/>
173+
);
174+
case 2:
175+
return (
176+
<LinksColumn
177+
key={index}
178+
title={item?.word_column_title || ""}
179+
link_entries={item?.column_words || []}
180+
/>
181+
);
182+
case 3:
183+
return (
184+
<ImagesColumn
185+
key={index}
186+
title={item?.image_column_title || ""}
187+
image_entries={item?.column_images || []}
188+
/>
189+
);
190+
default:
191+
return null;
192+
}
62193
};
63194

64195
return (
65-
<div className="intro-anim-cont">
66-
<div className="intro-anim-options">
67-
{page_data.map((entry, index) => (
68-
<IntroTab
69-
key={entry.id}
70-
title={entry.title}
71-
tab_number={index}
72-
icon_key={entry.icon.id}
73-
current_tab={currentTab}
74-
click_handler={handleTabClick}
75-
/>
76-
))}
196+
<div className="splash-section">
197+
<div className="splash-content">
198+
<div className="category-buttons">
199+
{page_data.map((tab, index) => (
200+
<button
201+
key={index}
202+
className={`category-button ${currentTab === tab.title ? "active" : ""}`}
203+
onClick={() => setCurrentTab(tab.title)}
204+
>
205+
{tab.title}
206+
</button>
207+
))}
208+
</div>
209+
210+
<div
211+
className={`featured-item-container ${allItemsAreImages ? "all-images" : ""}`}
212+
>
213+
{FeaturedItems?.map((item, index) => (
214+
<React.Fragment key={index}>
215+
{renderFeaturedItem(item, index)}
216+
</React.Fragment>
217+
))}
218+
</div>
219+
220+
<div className="featured-item-description">
221+
<span className="inline-markdown">
222+
<ReactMarkdown>{currentTabData?.description || ""}</ReactMarkdown>
223+
</span>
224+
</div>
77225
</div>
78-
<DemoDisplay
79-
demo={page_data[currentTab]}
80-
currentTab={currentTab}
81-
isLastTab={currentTab == page_data.length - 1}
82-
/>
83226
</div>
84227
);
85228
};
86229

87-
export { IntroAnimation };
230+
export { IntroSplash };

0 commit comments

Comments
 (0)