app_store: widget change and more ui

This commit is contained in:
bitful-pannul 2024-07-29 14:02:13 +03:00
parent aa4c114a17
commit 7edcf31ddd
8 changed files with 144 additions and 193 deletions

View File

@ -144,7 +144,7 @@ fn make_widget() -> String {
if (app.metadata) {
const a = document.createElement('a');
a.className = 'app';
a.href = `/main:app_store:sys/apps/${app.package}:${app.publisher}`
a.href = `/main:app_store:sys/app/${app.package}:${app.publisher}`
a.target = '_blank';
a.rel = 'noopener noreferrer';
const iconLetter = app.metadata_hash.replace('0x', '')[0].toUpperCase();

View File

@ -2,9 +2,8 @@ import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import StorePage from "./pages/StorePage";
import MyAppsPage from "./pages/MyAppsPage";
import AppPage from "./pages/AppPage";
import { APP_DETAILS_PATH, MY_APPS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
import PublishPage from "./pages/PublishPage";
import Header from "./components/Header";
@ -19,7 +18,6 @@ function App() {
<Header />
<Routes>
<Route path={STORE_PATH} element={<StorePage />} />
<Route path={MY_APPS_PATH} element={<MyAppsPage />} />
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
<Route path={PUBLISH_PATH} element={<PublishPage />} />
</Routes>

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { STORE_PATH, MY_APPS_PATH, PUBLISH_PATH } from '../constants/path';
import { STORE_PATH, PUBLISH_PATH } from '../constants/path';
import { ConnectButton } from '@rainbow-me/rainbowkit';
const Header: React.FC = () => {
@ -11,7 +11,6 @@ const Header: React.FC = () => {
<div className="header-left">
<nav>
<Link to={STORE_PATH} className={location.pathname === STORE_PATH ? 'active' : ''}>Home</Link>
<Link to={MY_APPS_PATH} className={location.pathname === MY_APPS_PATH ? 'active' : ''}>My Apps</Link>
<Link to={PUBLISH_PATH} className={location.pathname === PUBLISH_PATH ? 'active' : ''}>Publish</Link>
</nav>
</div>

View File

@ -1,4 +1,3 @@
export const MY_APPS_PATH = '/my-apps';
export const STORE_PATH = '/';
export const PUBLISH_PATH = '/publish';
export const APP_DETAILS_PATH = '/app';

View File

@ -106,85 +106,89 @@
color: light-dark(var(--gray), var(--off-white));
}
/* ... (previous styles) ... */
/* App Page Styles */
.app-page {
padding: 2rem;
max-width: 800px;
margin: 0 auto;
}
.app-details-card {
background-color: light-dark(var(--white), var(--off-black));
border-radius: 8px;
padding: 2rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.app-divider {
border: none;
border-top: 1px solid var(--gray);
margin: 1rem 0;
}
.app-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
.app-description {
margin-bottom: 1rem;
color: light-dark(var(--gray), var(--off-white));
}
.app-info-item {
text-align: center;
.app-details {
display: flex;
justify-content: space-between;
gap: 2rem;
}
.app-info-top {
font-weight: bold;
margin-bottom: 0.5rem;
.app-info {
flex: 1;
}
.app-info-middle {
font-size: 1.5rem;
margin-bottom: 0.5rem;
.app-details-list {
list-style-type: none;
padding: 0;
}
.app-info-bottom {
.app-details-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid light-dark(var(--gray), var(--maroon));
}
.app-details-list li:last-child {
border-bottom: none;
}
.status-icon {
font-size: 1rem;
}
.status-icon.installed,
.status-icon.mirroring,
.status-icon.auto-update {
color: var(--orange);
}
.status-icon.not-installed,
.status-icon.not-mirroring,
.status-icon.no-auto-update {
color: var(--ansi-red);
}
.app-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.app-actions button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
color: var(--gray);
}
.app-screenshots {
display: flex;
overflow-x: auto;
gap: 1rem;
margin-bottom: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
.app-screenshot {
max-width: 200px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.app-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.app-actions button,
.app-actions .button {
flex: 1;
min-width: 120px;
}
.version-number {
font-family: monospace;
background-color: light-dark(var(--tan), var(--maroon));
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
/* ... (previous styles) ... */
/* My Apps Page Styles */
.my-apps-page {
padding: 2rem;
@ -258,8 +262,6 @@
color: light-dark(var(--gray), var(--off-white));
}
/* ... (previous styles) ... */
/* Publish Page Styles */
.publish-page {
padding: 2rem;
@ -361,8 +363,6 @@
background-color: #c62828;
}
/* ... (previous styles) ... */
/* Store Page Styles */
.store-page {
padding: 2rem;
@ -371,13 +371,15 @@
.store-header {
display: flex;
justify-content: space-between;
align-items: center;
align-items: stretch;
margin-bottom: 2rem;
gap: 1rem;
}
.search-bar {
flex-grow: 1;
margin-right: 1rem;
display: flex;
align-items: stretch;
}
.search-bar input {
@ -386,6 +388,27 @@
font-size: 1rem;
border: 1px solid var(--gray);
border-radius: 4px;
height: 38px;
}
.filter-button,
.store-header button {
height: 38px;
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
align-self: stretch;
}
/* Add these new styles */
.store-header>* {
margin: 0;
}
.store-header button {
flex-shrink: 0;
}
.app-list table {
@ -430,7 +453,7 @@
}
.status.installed {
background-color: var(--green);
background-color: var(--off-black);
color: var(--white);
}

View File

@ -1,148 +1,72 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { FaGlobe, FaPeopleGroup, FaCode, FaLink, FaCaretDown } from "react-icons/fa6";
import { AppInfo } from "../types/Apps";
import React from "react";
import { useParams } from "react-router-dom";
import { FaDownload, FaSync, FaTrash, FaMagnet, FaCog, FaCheck, FaTimes } from "react-icons/fa";
import useAppsStore from "../store";
import { PUBLISH_PATH } from "../constants/path";
import { FaCheckCircle, FaTimesCircle } from "react-icons/fa";
import { appId } from "../utils/app";
export default function AppPage() {
const { getApp, installApp, updateApp, uninstallApp, setMirroring, setAutoUpdate } = useAppsStore();
const navigate = useNavigate();
const { installApp, updateApp, uninstallApp, setMirroring, setAutoUpdate, apps } = useAppsStore();
const { id } = useParams();
const [app, setApp] = useState<AppInfo | undefined>(undefined);
const app = apps.find(a => appId(a) === id);
useEffect(() => {
if (id) {
getApp(id).then(setApp).catch(console.error);
}
}, [id, getApp]);
if (!app) {
return <div className="app-page"><h4>App details not found for {id}</h4></div>;
}
const handleInstall = () => app && installApp(app);
const handleUpdate = () => app && updateApp(app);
const handleUninstall = () => app && uninstallApp(app);
const handleMirror = () => app && setMirroring(app, !app.state?.mirroring);
const handleAutoUpdate = () => app && setAutoUpdate(app, !app.state?.auto_update);
const handlePublish = () => navigate(PUBLISH_PATH, { state: { app } });
if (!app) {
return <div className="app-page"><h4>App details not found for {id}</h4></div>;
}
const version = app.metadata?.properties?.current_version || "Unknown";
const versions = Object.entries(app.metadata?.properties?.code_hashes || {});
const hash = app.state?.our_version || (versions[(versions.length || 1) - 1] || ["", ""])[1];
const mirrors = app.metadata?.properties?.mirrors || [];
const tbaLink = `https://optimistic.etherscan.io/address/${app.tba}`;
return (
<div className="app-page">
<div className="app-details-card">
<h2>{app.metadata?.name || app.package}</h2>
<p>{app.metadata?.description || "No description available"}</p>
<hr className="app-divider" />
<div className="app-info-grid">
<div className="app-info-item">
<FaPeopleGroup size={36} />
<span>Developer</span>
<span>{app.publisher}</span>
</div>
<div className="app-info-item">
<FaCode size={36} />
<span>Version</span>
<span>{version}</span>
<span className="hash">{`${hash.slice(0, 5)}...${hash.slice(-5)}`}</span>
</div>
<div className="app-info-item">
<FaGlobe size={36} />
<span>Mirrors</span>
<span>{mirrors.length}</span>
<MirrorsDropdown mirrors={mirrors} />
</div>
<div className="app-info-item">
<FaLink size={36} />
<span>TBA</span>
<a href={tbaLink} target="_blank" rel="noopener noreferrer">
{`${app.package}.${app.publisher}`}
</a>
</div>
<div className="app-info-item">
<span>Installed</span>
{app.installed ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
</div>
{app.state && (
<>
<div className="app-info-item">
<span>Mirrored From</span>
<span>{app.state.mirrored_from || "N/A"}</span>
</div>
<div className="app-info-item">
<span>Capabilities Approved</span>
{app.state.caps_approved ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
</div>
<div className="app-info-item">
<span>Mirroring</span>
{app.state.mirroring ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
</div>
<div className="app-info-item">
<span>Auto Update</span>
{app.state.auto_update ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
</div>
<div className="app-info-item">
<span>Verified</span>
{app.state.verified ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
</div>
</>
)}
<section className="app-page">
<h2>{app.metadata?.name || app.package}</h2>
<p className="app-description">{app.metadata?.description || "No description available"}</p>
<div className="app-details">
<div className="app-info">
<ul className="app-details-list">
<li><span>Version:</span> <span>{app.metadata?.properties?.current_version || "Unknown"}</span></li>
<li><span>Developer:</span> <span>{app.publisher}</span></li>
<li><span>Mirrors:</span> <span>{app.metadata?.properties?.mirrors?.length || 0}</span></li>
<li>
<span>Installed:</span>
{app.installed ? <FaCheck className="status-icon installed" /> : <FaTimes className="status-icon not-installed" />}
</li>
<li>
<span>Mirroring:</span>
{app.state?.mirroring ? <FaCheck className="status-icon mirroring" /> : <FaTimes className="status-icon not-mirroring" />}
</li>
<li>
<span>Auto-Update:</span>
{app.state?.auto_update ? <FaCheck className="status-icon auto-update" /> : <FaTimes className="status-icon no-auto-update" />}
</li>
</ul>
</div>
{app.metadata?.properties?.screenshots && (
<div className="app-screenshots">
{app.metadata.properties.screenshots.map((screenshot, index) => (
<img key={index} src={screenshot} alt={`Screenshot ${index + 1}`} className="app-screenshot" />
))}
</div>
)}
<div className="app-actions">
{app.installed ? (
<>
<button onClick={handleUpdate}>Update</button>
<button onClick={handleUninstall}>Uninstall</button>
<button onClick={handleMirror}>
{app.state?.mirroring ? "Stop Mirroring" : "Start Mirroring"}
</button>
<button onClick={handleAutoUpdate}>
{app.state?.auto_update ? "Disable Auto-Update" : "Enable Auto-Update"}
</button>
{app.state?.mirroring && (
<button onClick={handlePublish}>Publish</button>
)}
<button onClick={handleUpdate} className="secondary"><FaSync /> Update</button>
<button onClick={handleUninstall} className="secondary"><FaTrash /> Uninstall</button>
</>
) : (
<button onClick={handleInstall}>Install</button>
<button onClick={handleInstall}><FaDownload /> Install</button>
)}
<button onClick={handleMirror} className="secondary">
<FaMagnet /> {app.state?.mirroring ? "Stop Mirroring" : "Start Mirroring"}
</button>
<button onClick={handleAutoUpdate} className="secondary">
<FaCog /> {app.state?.auto_update ? "Disable Auto-Update" : "Enable Auto-Update"}
</button>
</div>
</div>
</div>
);
}
const MirrorsDropdown = ({ mirrors }: { mirrors: string[] }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="mirrors-dropdown">
<button onClick={() => setIsOpen(!isOpen)} className="mirrors-dropdown-toggle">
Mirrors <FaCaretDown />
</button>
{isOpen && (
<ul className="mirrors-list">
{mirrors.map((mirror, index) => (
<li key={index}>{mirror}</li>
{app.metadata?.properties?.screenshots && (
<div className="app-screenshots">
{app.metadata.properties.screenshots.map((screenshot, index) => (
<img key={index} src={screenshot} alt={`Screenshot ${index + 1}`} className="app-screenshot" />
))}
</ul>
</div>
)}
</div>
</section>
);
};
}

View File

@ -39,7 +39,7 @@ export default function PublishPage() {
useEffect(() => {
setMyPublishedApps(
apps.filter((app) => app.owner?.toLowerCase() === address?.toLowerCase())
apps.filter((app) => app.publisher?.toLowerCase() === window.our.node?.toLowerCase())
);
}, [apps, address])

View File

@ -3,20 +3,22 @@ import useAppsStore from "../store";
import { AppInfo } from "../types/Apps";
import { appId } from '../utils/app'
import { Link } from "react-router-dom";
import { FaGlobe, FaPeopleGroup, FaCode } from "react-icons/fa6";
import { FaGlobe, FaPeopleGroup, FaCode, FaFilter } from "react-icons/fa6";
export default function StorePage() {
const { apps, getApps, rebuildIndex } = useAppsStore();
const [searchQuery, setSearchQuery] = useState<string>("");
const [isRebuildingIndex, setIsRebuildingIndex] = useState(false);
const [showMyApps, setShowMyApps] = useState(false);
useEffect(() => {
getApps();
}, [getApps]);
const filteredApps = apps.filter((app) =>
app.package.toLowerCase().includes(searchQuery.toLowerCase()) ||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())
(app.package.toLowerCase().includes(searchQuery.toLowerCase()) ||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())) &&
(!showMyApps || app.installed)
);
const handleRebuildIndex = async () => {
@ -44,6 +46,12 @@ export default function StorePage() {
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<button
className={`filter-button ${showMyApps ? 'active' : ''}`}
onClick={() => setShowMyApps(!showMyApps)}
>
<FaFilter /> {showMyApps ? 'All Apps' : 'My Apps'}
</button>
<button onClick={handleRebuildIndex} disabled={isRebuildingIndex}>
{isRebuildingIndex ? "Rebuilding..." : "Rebuild Index"}
</button>