Spaces:
Running
Running
feat: add release notes section to download page
Browse files- Fetch releases from GitHub API
- Display last 3 releases with expandable view
- Parse markdown headers and lists
- src/pages/Download.jsx +149 -6
src/pages/Download.jsx
CHANGED
|
@@ -15,6 +15,8 @@ import DownloadIcon from '@mui/icons-material/Download';
|
|
| 15 |
import AppleIcon from '@mui/icons-material/Apple';
|
| 16 |
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
| 17 |
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
|
|
|
|
|
| 18 |
|
| 19 |
import Layout from '../components/Layout';
|
| 20 |
|
|
@@ -56,6 +58,7 @@ const PLATFORMS = {
|
|
| 56 |
|
| 57 |
// URL to fetch latest release info (using GitHub API for CORS support)
|
| 58 |
const GITHUB_RELEASES_API = 'https://api.github.com/repos/pollen-robotics/reachy-mini-desktop-app/releases/latest';
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
// Detect user's platform
|
|
@@ -194,8 +197,10 @@ function PlatformCard({ platformKey, url, isActive, onClick }) {
|
|
| 194 |
|
| 195 |
export default function Download() {
|
| 196 |
const [releaseData, setReleaseData] = useState(null);
|
|
|
|
| 197 |
const [detectedPlatform, setDetectedPlatform] = useState(null);
|
| 198 |
const [loading, setLoading] = useState(true);
|
|
|
|
| 199 |
|
| 200 |
const [error, setError] = useState(null);
|
| 201 |
|
|
@@ -203,11 +208,16 @@ export default function Download() {
|
|
| 203 |
setDetectedPlatform(detectPlatform());
|
| 204 |
|
| 205 |
// Fetch latest release info from GitHub API
|
| 206 |
-
async function
|
| 207 |
try {
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
|
| 212 |
// Transform GitHub API response to our format
|
| 213 |
const version = data.tag_name?.replace('v', '') || '';
|
|
@@ -251,6 +261,12 @@ export default function Download() {
|
|
| 251 |
} else {
|
| 252 |
setError('Failed to fetch release info');
|
| 253 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
} catch (err) {
|
| 255 |
console.error('Error fetching release:', err);
|
| 256 |
setError('Failed to fetch release info');
|
|
@@ -259,7 +275,7 @@ export default function Download() {
|
|
| 259 |
}
|
| 260 |
}
|
| 261 |
|
| 262 |
-
|
| 263 |
}, []);
|
| 264 |
|
| 265 |
if (loading) {
|
|
@@ -543,7 +559,7 @@ export default function Download() {
|
|
| 543 |
</Box>
|
| 544 |
|
| 545 |
{/* Requirements */}
|
| 546 |
-
<Box sx={{ textAlign: 'center' }}>
|
| 547 |
<Typography
|
| 548 |
variant="body2"
|
| 549 |
sx={{ color: 'rgba(255,255,255,0.4)', mb: 2 }}
|
|
@@ -565,6 +581,133 @@ export default function Download() {
|
|
| 565 |
View all releases on GitHub
|
| 566 |
</Button>
|
| 567 |
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 568 |
</Container>
|
| 569 |
</Box>
|
| 570 |
</Layout>
|
|
|
|
| 15 |
import AppleIcon from '@mui/icons-material/Apple';
|
| 16 |
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
| 17 |
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
| 18 |
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
| 19 |
+
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
| 20 |
|
| 21 |
import Layout from '../components/Layout';
|
| 22 |
|
|
|
|
| 58 |
|
| 59 |
// URL to fetch latest release info (using GitHub API for CORS support)
|
| 60 |
const GITHUB_RELEASES_API = 'https://api.github.com/repos/pollen-robotics/reachy-mini-desktop-app/releases/latest';
|
| 61 |
+
const GITHUB_RELEASES_LIST_API = 'https://api.github.com/repos/pollen-robotics/reachy-mini-desktop-app/releases?per_page=10';
|
| 62 |
|
| 63 |
|
| 64 |
// Detect user's platform
|
|
|
|
| 197 |
|
| 198 |
export default function Download() {
|
| 199 |
const [releaseData, setReleaseData] = useState(null);
|
| 200 |
+
const [allReleases, setAllReleases] = useState([]);
|
| 201 |
const [detectedPlatform, setDetectedPlatform] = useState(null);
|
| 202 |
const [loading, setLoading] = useState(true);
|
| 203 |
+
const [showAllReleases, setShowAllReleases] = useState(false);
|
| 204 |
|
| 205 |
const [error, setError] = useState(null);
|
| 206 |
|
|
|
|
| 208 |
setDetectedPlatform(detectPlatform());
|
| 209 |
|
| 210 |
// Fetch latest release info from GitHub API
|
| 211 |
+
async function fetchReleases() {
|
| 212 |
try {
|
| 213 |
+
// Fetch latest release for download buttons
|
| 214 |
+
const latestResponse = await fetch(GITHUB_RELEASES_API);
|
| 215 |
+
|
| 216 |
+
// Fetch all releases for changelog
|
| 217 |
+
const allResponse = await fetch(GITHUB_RELEASES_LIST_API);
|
| 218 |
+
|
| 219 |
+
if (latestResponse.ok) {
|
| 220 |
+
const data = await latestResponse.json();
|
| 221 |
|
| 222 |
// Transform GitHub API response to our format
|
| 223 |
const version = data.tag_name?.replace('v', '') || '';
|
|
|
|
| 261 |
} else {
|
| 262 |
setError('Failed to fetch release info');
|
| 263 |
}
|
| 264 |
+
|
| 265 |
+
// Set all releases for changelog
|
| 266 |
+
if (allResponse.ok) {
|
| 267 |
+
const releases = await allResponse.json();
|
| 268 |
+
setAllReleases(releases.filter(r => !r.draft));
|
| 269 |
+
}
|
| 270 |
} catch (err) {
|
| 271 |
console.error('Error fetching release:', err);
|
| 272 |
setError('Failed to fetch release info');
|
|
|
|
| 275 |
}
|
| 276 |
}
|
| 277 |
|
| 278 |
+
fetchReleases();
|
| 279 |
}, []);
|
| 280 |
|
| 281 |
if (loading) {
|
|
|
|
| 559 |
</Box>
|
| 560 |
|
| 561 |
{/* Requirements */}
|
| 562 |
+
<Box sx={{ textAlign: 'center', mb: 8 }}>
|
| 563 |
<Typography
|
| 564 |
variant="body2"
|
| 565 |
sx={{ color: 'rgba(255,255,255,0.4)', mb: 2 }}
|
|
|
|
| 581 |
View all releases on GitHub
|
| 582 |
</Button>
|
| 583 |
</Box>
|
| 584 |
+
|
| 585 |
+
{/* Release Notes */}
|
| 586 |
+
{allReleases.length > 0 && (
|
| 587 |
+
<Box
|
| 588 |
+
sx={{
|
| 589 |
+
background: 'rgba(255, 255, 255, 0.02)',
|
| 590 |
+
border: '1px solid rgba(255, 255, 255, 0.06)',
|
| 591 |
+
borderRadius: 4,
|
| 592 |
+
p: 4,
|
| 593 |
+
}}
|
| 594 |
+
>
|
| 595 |
+
<Typography
|
| 596 |
+
variant="h6"
|
| 597 |
+
sx={{ mb: 3, color: 'white', fontWeight: 600 }}
|
| 598 |
+
>
|
| 599 |
+
Release Notes
|
| 600 |
+
</Typography>
|
| 601 |
+
|
| 602 |
+
<Stack spacing={3}>
|
| 603 |
+
{(showAllReleases ? allReleases : allReleases.slice(0, 3)).map((release) => (
|
| 604 |
+
<Box
|
| 605 |
+
key={release.id}
|
| 606 |
+
sx={{
|
| 607 |
+
borderLeft: '2px solid rgba(255, 149, 0, 0.4)',
|
| 608 |
+
pl: 3,
|
| 609 |
+
py: 1,
|
| 610 |
+
}}
|
| 611 |
+
>
|
| 612 |
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 1 }}>
|
| 613 |
+
<Typography
|
| 614 |
+
variant="subtitle1"
|
| 615 |
+
sx={{ color: 'white', fontWeight: 600 }}
|
| 616 |
+
>
|
| 617 |
+
{release.name || release.tag_name}
|
| 618 |
+
</Typography>
|
| 619 |
+
<Chip
|
| 620 |
+
label={formatDate(release.published_at)}
|
| 621 |
+
size="small"
|
| 622 |
+
sx={{
|
| 623 |
+
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
| 624 |
+
color: 'rgba(255,255,255,0.5)',
|
| 625 |
+
fontSize: 11,
|
| 626 |
+
height: 22,
|
| 627 |
+
}}
|
| 628 |
+
/>
|
| 629 |
+
{release.prerelease && (
|
| 630 |
+
<Chip
|
| 631 |
+
label="Pre-release"
|
| 632 |
+
size="small"
|
| 633 |
+
sx={{
|
| 634 |
+
backgroundColor: 'rgba(255, 149, 0, 0.15)',
|
| 635 |
+
color: '#FF9500',
|
| 636 |
+
fontSize: 11,
|
| 637 |
+
height: 22,
|
| 638 |
+
}}
|
| 639 |
+
/>
|
| 640 |
+
)}
|
| 641 |
+
</Stack>
|
| 642 |
+
|
| 643 |
+
{release.body ? (
|
| 644 |
+
<Typography
|
| 645 |
+
variant="body2"
|
| 646 |
+
sx={{
|
| 647 |
+
color: 'rgba(255,255,255,0.6)',
|
| 648 |
+
whiteSpace: 'pre-line',
|
| 649 |
+
lineHeight: 1.7,
|
| 650 |
+
'& strong, & b': { color: 'rgba(255,255,255,0.8)' },
|
| 651 |
+
}}
|
| 652 |
+
>
|
| 653 |
+
{release.body.split('\n').map((line, i) => {
|
| 654 |
+
// Simple markdown parsing for headers and lists
|
| 655 |
+
if (line.startsWith('### ')) {
|
| 656 |
+
return (
|
| 657 |
+
<Box
|
| 658 |
+
key={i}
|
| 659 |
+
component="span"
|
| 660 |
+
sx={{
|
| 661 |
+
display: 'block',
|
| 662 |
+
fontWeight: 600,
|
| 663 |
+
color: 'rgba(255,255,255,0.8)',
|
| 664 |
+
mt: i > 0 ? 1.5 : 0,
|
| 665 |
+
mb: 0.5,
|
| 666 |
+
}}
|
| 667 |
+
>
|
| 668 |
+
{line.replace('### ', '')}
|
| 669 |
+
</Box>
|
| 670 |
+
);
|
| 671 |
+
}
|
| 672 |
+
if (line.startsWith('- ')) {
|
| 673 |
+
return (
|
| 674 |
+
<Box key={i} component="span" sx={{ display: 'block', pl: 2 }}>
|
| 675 |
+
• {line.replace('- ', '')}
|
| 676 |
+
</Box>
|
| 677 |
+
);
|
| 678 |
+
}
|
| 679 |
+
if (line.trim() === '') return null;
|
| 680 |
+
return <Box key={i} component="span" sx={{ display: 'block' }}>{line}</Box>;
|
| 681 |
+
})}
|
| 682 |
+
</Typography>
|
| 683 |
+
) : (
|
| 684 |
+
<Typography
|
| 685 |
+
variant="body2"
|
| 686 |
+
sx={{ color: 'rgba(255,255,255,0.4)', fontStyle: 'italic' }}
|
| 687 |
+
>
|
| 688 |
+
No release notes available
|
| 689 |
+
</Typography>
|
| 690 |
+
)}
|
| 691 |
+
</Box>
|
| 692 |
+
))}
|
| 693 |
+
</Stack>
|
| 694 |
+
|
| 695 |
+
{allReleases.length > 3 && (
|
| 696 |
+
<Button
|
| 697 |
+
variant="text"
|
| 698 |
+
onClick={() => setShowAllReleases(!showAllReleases)}
|
| 699 |
+
endIcon={showAllReleases ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
| 700 |
+
sx={{
|
| 701 |
+
mt: 3,
|
| 702 |
+
color: 'rgba(255,255,255,0.5)',
|
| 703 |
+
'&:hover': { color: 'white' },
|
| 704 |
+
}}
|
| 705 |
+
>
|
| 706 |
+
{showAllReleases ? 'Show less' : `Show ${allReleases.length - 3} more releases`}
|
| 707 |
+
</Button>
|
| 708 |
+
)}
|
| 709 |
+
</Box>
|
| 710 |
+
)}
|
| 711 |
</Container>
|
| 712 |
</Box>
|
| 713 |
</Layout>
|