mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 11:53:31 +03:00
app_store UI: simplify css
This commit is contained in:
parent
59cfbaa59b
commit
9524bb3c5a
@ -566,7 +566,7 @@ fn serve_paths(
|
||||
));
|
||||
};
|
||||
|
||||
let downloads = Address::from_str("downloads@downloads:app_store:sys")?;
|
||||
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||
|
||||
match method {
|
||||
// start mirroring an app
|
||||
|
@ -8,7 +8,6 @@ import StorePage from "./pages/StorePage";
|
||||
import AppPage from "./pages/AppPage";
|
||||
import DownloadPage from "./pages/DownloadPage";
|
||||
import PublishPage from "./pages/PublishPage";
|
||||
import Testing from "./pages/Testing";
|
||||
import MyDownloadsPage from "./pages/MyDownloadsPage";
|
||||
|
||||
|
||||
@ -21,9 +20,7 @@ function App() {
|
||||
<div>
|
||||
<Router basename={BASE_URL}>
|
||||
<Header />
|
||||
<button onClick={() => window.location.href = `${BASE_URL}/testing`}>Go to Testing</button>
|
||||
<Routes>
|
||||
<Route path="/testing" element={<Testing />} />
|
||||
<Route path={STORE_PATH} element={<StorePage />} />
|
||||
<Route path={MY_DOWNLOADS_PATH} element={<MyDownloadsPage />} />
|
||||
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,56 +3,82 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { FaDownload, FaCheck, FaTimes, FaPlay } from "react-icons/fa";
|
||||
import useAppsStore from "../store";
|
||||
import { AppListing, PackageState } from "../types/Apps";
|
||||
import { compareVersions } from "../utils/compareVersions";
|
||||
|
||||
export default function AppPage() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { listings, installed, fetchListings, fetchInstalled } = useAppsStore();
|
||||
const { fetchListing, fetchInstalledApp, installApp } = useAppsStore();
|
||||
const [app, setApp] = useState<AppListing | null>(null);
|
||||
const [installedApp, setInstalledApp] = useState<PackageState | null>(null);
|
||||
const [currentVersion, setCurrentVersion] = useState<string | null>(null);
|
||||
const [latestVersion, setLatestVersion] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
await Promise.all([fetchListings(), fetchInstalled()]);
|
||||
if (!id) return;
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const foundApp = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === id) || null;
|
||||
setApp(foundApp);
|
||||
try {
|
||||
const [appData, installedAppData] = await Promise.all([
|
||||
fetchListing(id),
|
||||
fetchInstalledApp(id)
|
||||
]);
|
||||
|
||||
if (foundApp) {
|
||||
const foundInstalledApp = installed.find(i =>
|
||||
i.package_id.package_name === foundApp.package_id.package_name &&
|
||||
i.package_id.publisher_node === foundApp.package_id.publisher_node
|
||||
) || null;
|
||||
setInstalledApp(foundInstalledApp);
|
||||
setApp(appData);
|
||||
setInstalledApp(installedAppData);
|
||||
|
||||
if (foundApp.metadata?.properties?.code_hashes) {
|
||||
const versions = foundApp.metadata.properties.code_hashes;
|
||||
if (appData.metadata?.properties?.code_hashes) {
|
||||
const versions = appData.metadata.properties.code_hashes;
|
||||
if (versions.length > 0) {
|
||||
setLatestVersion(versions[versions.length - 1][0]);
|
||||
if (foundInstalledApp) {
|
||||
const installedVersion = versions.find(([_, hash]) => hash === foundInstalledApp.our_version_hash);
|
||||
const latestVer = versions.reduce((latest, current) =>
|
||||
compareVersions(current[0], latest[0]) > 0 ? current : latest
|
||||
)[0];
|
||||
setLatestVersion(latestVer);
|
||||
|
||||
if (installedAppData) {
|
||||
const installedVersion = versions.find(([_, hash]) => hash === installedAppData.our_version_hash);
|
||||
if (installedVersion) {
|
||||
setCurrentVersion(installedVersion[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Failed to load app details. Please try again.");
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [id, fetchListings, fetchInstalled]);
|
||||
}, [id, fetchListing, fetchInstalledApp]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
if (!app) {
|
||||
return <div className="app-page"><h4>App details not found for {id}</h4></div>;
|
||||
}
|
||||
|
||||
const handleDownload = () => {
|
||||
navigate(`/download/${id}`);
|
||||
};
|
||||
|
||||
const handleLaunch = () => {
|
||||
// Implement launch functionality
|
||||
console.log("Launching app:", app?.package_id.package_name);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="app-page"><h4>Loading app details...</h4></div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="app-page"><h4>{error}</h4></div>;
|
||||
}
|
||||
|
||||
if (!app) {
|
||||
return <div className="app-page"><h4>App details not found for {id}</h4></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="app-page">
|
||||
<div className="app-header">
|
||||
@ -89,7 +115,7 @@ export default function AppPage() {
|
||||
<FaDownload /> Download
|
||||
</button>
|
||||
{installedApp && (
|
||||
<button className="primary">
|
||||
<button onClick={handleLaunch} className="primary">
|
||||
<FaPlay /> Launch
|
||||
</button>
|
||||
)}
|
||||
|
@ -2,14 +2,14 @@ import React, { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FaDownload, FaCheck, FaSpinner, FaRocket, FaChevronDown, FaChevronUp, FaExclamationTriangle } from "react-icons/fa";
|
||||
import useAppsStore from "../store";
|
||||
import { AppListing, DownloadItem, MirrorCheckFile, PackageManifest } from "../types/Apps";
|
||||
import { DownloadItem, PackageManifest, AppListing } from "../types/Apps";
|
||||
|
||||
export default function DownloadPage() {
|
||||
const { id } = useParams();
|
||||
const { listings, fetchListings, fetchDownloadsForApp, downloadApp, installApp, checkMirror, fetchInstalled, installed, getCaps, approveCaps } = useAppsStore();
|
||||
const [downloads, setDownloads] = useState<DownloadItem[]>([]);
|
||||
const [selectedMirror, setSelectedMirror] = useState<string | null>(null);
|
||||
const [mirrorStatuses, setMirrorStatuses] = useState<{ [mirror: string]: MirrorCheckFile | null }>({});
|
||||
const [mirrorStatuses, setMirrorStatuses] = useState<{ [mirror: string]: { status: 'unchecked' | 'checking' | 'online' | 'offline' } }>({});
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -19,8 +19,10 @@ export default function DownloadPage() {
|
||||
const [manifest, setManifest] = useState<PackageManifest | null>(null);
|
||||
const [showCapApproval, setShowCapApproval] = useState(false);
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [showManualMirror, setShowManualMirror] = useState(false);
|
||||
const [manualMirror, setManualMirror] = useState("");
|
||||
|
||||
const app = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === id);
|
||||
const app = listings[id as string];
|
||||
|
||||
useEffect(() => {
|
||||
fetchListings();
|
||||
@ -32,20 +34,30 @@ export default function DownloadPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (app) {
|
||||
checkMirrors();
|
||||
initializeMirrors();
|
||||
}
|
||||
}, [app]);
|
||||
|
||||
const checkMirrors = async () => {
|
||||
const initializeMirrors = () => {
|
||||
if (!app) return;
|
||||
const mirrors = [app.package_id.publisher_node, ...(app.metadata?.properties?.mirrors || [])];
|
||||
const statuses: { [mirror: string]: MirrorCheckFile | null } = {};
|
||||
for (const mirror of mirrors) {
|
||||
const initialStatuses: { [mirror: string]: { status: 'unchecked' | 'checking' | 'online' | 'offline' } } = {};
|
||||
mirrors.forEach(mirror => {
|
||||
initialStatuses[mirror] = { status: 'unchecked' };
|
||||
});
|
||||
setMirrorStatuses(initialStatuses);
|
||||
setSelectedMirror(app.package_id.publisher_node);
|
||||
mirrors.forEach(checkMirrorStatus);
|
||||
};
|
||||
|
||||
const checkMirrorStatus = async (mirror: string) => {
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: { status: 'checking' } }));
|
||||
try {
|
||||
const status = await checkMirror(mirror);
|
||||
statuses[mirror] = status;
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: { status: status.is_online ? 'online' : 'offline' } }));
|
||||
} catch (error) {
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: { status: 'offline' } }));
|
||||
}
|
||||
setMirrorStatuses(statuses);
|
||||
setSelectedMirror(statuses[app.package_id.publisher_node]?.is_online ? app.package_id.publisher_node : mirrors.find(m => statuses[m]?.is_online) || null);
|
||||
};
|
||||
|
||||
const handleDownload = async (version: string) => {
|
||||
@ -115,25 +127,64 @@ export default function DownloadPage() {
|
||||
<div className="mirror-selection">
|
||||
<h3>Select Mirror</h3>
|
||||
<select
|
||||
value={selectedMirror || ''}
|
||||
onChange={(e) => setSelectedMirror(e.target.value)}
|
||||
value={selectedMirror === manualMirror ? 'manual' : selectedMirror || ''}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === 'manual') {
|
||||
setShowManualMirror(true);
|
||||
} else {
|
||||
setSelectedMirror(e.target.value);
|
||||
setShowManualMirror(false);
|
||||
setManualMirror('');
|
||||
}
|
||||
}}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
<option value="" disabled>Select Mirror</option>
|
||||
{Object.entries(mirrorStatuses).map(([mirror, status]) => (
|
||||
<option key={mirror} value={mirror} disabled={!status?.is_online}>
|
||||
{mirror} {status?.is_online ? '(Online)' : '(Offline)'}
|
||||
{Object.entries(mirrorStatuses).map(([mirror, { status }]) => (
|
||||
<option key={mirror} value={mirror} disabled={status === 'offline'}>
|
||||
{mirror} {status === 'checking' ? '(Checking...)' : status === 'online' ? '(Online)' : status === 'offline' ? '(Offline)' : ''}
|
||||
</option>
|
||||
))}
|
||||
<option value="manual">Manual Mirror (Advanced)</option>
|
||||
</select>
|
||||
{(showManualMirror || selectedMirror === manualMirror) && (
|
||||
<div className="manual-mirror-input">
|
||||
<input
|
||||
type="text"
|
||||
value={manualMirror}
|
||||
onChange={(e) => setManualMirror(e.target.value)}
|
||||
placeholder="Enter mirror node"
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (manualMirror) {
|
||||
setSelectedMirror(manualMirror);
|
||||
setShowManualMirror(true);
|
||||
}
|
||||
}}
|
||||
disabled={!manualMirror}
|
||||
>
|
||||
Set Manual Mirror
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedMirror(app.package_id.publisher_node);
|
||||
setShowManualMirror(false);
|
||||
setManualMirror('');
|
||||
}}
|
||||
>
|
||||
Reset to Default Mirror
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<h3>Available Versions</h3>
|
||||
<table className="downloads-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@ -147,12 +198,11 @@ export default function DownloadPage() {
|
||||
return false;
|
||||
});
|
||||
const isDownloaded = !!download;
|
||||
const isInstalled = installed.some(i => i.package_id.package_name === app.package_id.package_name && i.our_version_hash === version);
|
||||
const isInstalled = installed[id as string]?.our_version_hash === hash;
|
||||
|
||||
return (
|
||||
<tr key={version}>
|
||||
<td>{version}</td>
|
||||
<td>{download?.File?.size ? `${(download.File.size / 1024 / 1024).toFixed(2)} MB` : 'N/A'}</td>
|
||||
<td>
|
||||
{isInstalled ? 'Installed' : isDownloaded ? 'Downloaded' : 'Not downloaded'}
|
||||
</td>
|
||||
@ -160,7 +210,7 @@ export default function DownloadPage() {
|
||||
{!isDownloaded && (
|
||||
<button
|
||||
onClick={() => handleDownload(version)}
|
||||
disabled={!selectedMirror || isDownloading}
|
||||
disabled={!selectedMirror || isDownloading || selectedMirror === 'manual'}
|
||||
className="download-button"
|
||||
>
|
||||
{isDownloading ? <FaSpinner className="fa-spin" /> : <FaDownload />} Download
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FaFolder, FaFile, FaChevronLeft, FaSync, FaRocket, FaSpinner, FaCheck } from "react-icons/fa";
|
||||
import useAppsStore from "../store";
|
||||
import { DownloadItem, PackageManifest } from "../types/Apps";
|
||||
import { DownloadItem, PackageManifest, PackageState } from "../types/Apps";
|
||||
|
||||
export default function MyDownloadsPage() {
|
||||
const { fetchDownloads, fetchDownloadsForApp, startMirroring, stopMirroring, installApp, getCaps, approveCaps, fetchInstalled, installed } = useAppsStore();
|
||||
@ -19,13 +19,18 @@ export default function MyDownloadsPage() {
|
||||
}, [currentPath]);
|
||||
|
||||
const loadItems = async () => {
|
||||
let downloads: DownloadItem[];
|
||||
if (currentPath.length === 0) {
|
||||
downloads = await fetchDownloads();
|
||||
} else {
|
||||
downloads = await fetchDownloadsForApp(currentPath.join(':'));
|
||||
try {
|
||||
let downloads: DownloadItem[];
|
||||
if (currentPath.length === 0) {
|
||||
downloads = await fetchDownloads();
|
||||
} else {
|
||||
downloads = await fetchDownloadsForApp(currentPath.join(':'));
|
||||
}
|
||||
setItems(downloads);
|
||||
} catch (error) {
|
||||
console.error("Error loading items:", error);
|
||||
setError(`Error loading items: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
setItems(downloads);
|
||||
};
|
||||
|
||||
const navigateToItem = (item: DownloadItem) => {
|
||||
@ -89,6 +94,11 @@ export default function MyDownloadsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const isAppInstalled = (name: string): boolean => {
|
||||
const packageName = name.replace('.zip', '');
|
||||
return Object.values(installed).some(app => app.package_id.package_name === packageName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="downloads-page">
|
||||
<h2>Downloads</h2>
|
||||
@ -115,7 +125,7 @@ export default function MyDownloadsPage() {
|
||||
{items.map((item, index) => {
|
||||
const isFile = !!item.File;
|
||||
const name = isFile ? item.File!.name : item.Dir!.name;
|
||||
const isInstalled = isFile && installed.some(i => i.package_id.package_name === name.replace('.zip', ''));
|
||||
const isInstalled = isFile && isAppInstalled(name);
|
||||
return (
|
||||
<tr key={index} onClick={() => navigateToItem(item)} className={isFile ? 'file' : 'directory'}>
|
||||
<td>
|
||||
|
@ -8,7 +8,6 @@ import { kinohash } from '../utils/kinohash';
|
||||
import useAppsStore from "../store";
|
||||
|
||||
export default function PublishPage() {
|
||||
const { state } = useLocation();
|
||||
const { openConnectModal } = useConnectModal();
|
||||
const { ourApps, fetchOurApps } = useAppsStore();
|
||||
const publicClient = usePublicClient();
|
||||
@ -83,7 +82,6 @@ export default function PublishPage() {
|
||||
[tba, owner, _data] = data as [string, string, string];
|
||||
isUpdate = false; // It's a new package, but we might have a publisher TBA
|
||||
currentTBA = (tba && tba !== '0x') ? tba as `0x${string}` : null;
|
||||
console.log('NEWcurrenttba, isupdate: ', currentTBA, isUpdate)
|
||||
}
|
||||
|
||||
let metadata = metadataHash;
|
||||
@ -252,10 +250,10 @@ export default function PublishPage() {
|
||||
|
||||
<div className="my-packages">
|
||||
<h2>Packages You Own</h2>
|
||||
{ourApps.length > 0 ? (
|
||||
{Object.keys(ourApps).length > 0 ? (
|
||||
<ul>
|
||||
{ourApps.map((app) => (
|
||||
<li key={`${app.package_id.package_name}:{app.package_id.publisher_node}`}>
|
||||
{Object.values(ourApps).map((app) => (
|
||||
<li key={`${app.package_id.package_name}:${app.package_id.publisher_node}`}>
|
||||
<Link to={`/app/${app.package_id.package_name}:${app.package_id.publisher_node}`} className="app-name">
|
||||
{app.metadata?.name || app.package_id.package_name}
|
||||
</Link>
|
||||
|
@ -11,12 +11,10 @@ export default function StorePage() {
|
||||
fetchListings();
|
||||
}, [fetchListings]);
|
||||
|
||||
const filteredApps = Array.isArray(listings)
|
||||
? listings.filter((app) =>
|
||||
app.package_id.package_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
const filteredApps = Object.values(listings).filter((app) =>
|
||||
app.package_id.package_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="store-page">
|
||||
@ -29,9 +27,9 @@ export default function StorePage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="app-list">
|
||||
{!Array.isArray(listings) ? (
|
||||
{Object.keys(listings).length === 0 ? (
|
||||
<p>Loading...</p>
|
||||
) : listings.length === 0 ? (
|
||||
) : filteredApps.length === 0 ? (
|
||||
<p>No apps available.</p>
|
||||
) : (
|
||||
<table>
|
||||
@ -55,7 +53,6 @@ export default function StorePage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ... rest of the code remains the same
|
||||
interface AppRowProps {
|
||||
app: AppListing;
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import useAppsStore from '../store'
|
||||
|
||||
const Testing: React.FC = () => {
|
||||
const {
|
||||
fetchListings,
|
||||
fetchInstalled,
|
||||
fetchDownloads,
|
||||
fetchOurApps,
|
||||
fetchDownloadsForApp,
|
||||
listings,
|
||||
installed,
|
||||
downloads,
|
||||
ourApps
|
||||
} = useAppsStore()
|
||||
const [result, setResult] = useState<any>(null)
|
||||
const [appId, setAppId] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
fetchListings()
|
||||
fetchInstalled()
|
||||
fetchDownloads()
|
||||
fetchOurApps()
|
||||
}, [])
|
||||
|
||||
const handleAction = async (action: () => Promise<void>, key: string) => {
|
||||
console.log('in handleAction')
|
||||
try {
|
||||
await action()
|
||||
setResult(JSON.stringify(useAppsStore.getState()[key], null, 2))
|
||||
} catch (error) {
|
||||
setResult(`Error: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadsForApp = async () => {
|
||||
try {
|
||||
const data = await fetchDownloadsForApp(appId)
|
||||
setResult(JSON.stringify(data, null, 2))
|
||||
} catch (error) {
|
||||
setResult(`Error: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Testing Page</h1>
|
||||
<div>
|
||||
<button onClick={() => handleAction(fetchListings, 'listings')}>Refresh Listings</button>
|
||||
<button onClick={() => handleAction(fetchInstalled, 'installed')}>Refresh Installed Apps</button>
|
||||
{/* <button onClick={() => handleAction(fetchDownloads, 'downloads')}>Refresh Downloads</button> */}
|
||||
<button onClick={() => handleAction(fetchOurApps, 'ourApps')}>Refresh Our Apps</button>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={appId}
|
||||
onChange={(e) => setAppId(e.target.value)}
|
||||
placeholder="Enter App ID"
|
||||
/>
|
||||
<button onClick={handleDownloadsForApp}>
|
||||
Get Downloads for App
|
||||
</button>
|
||||
</div>
|
||||
<pre>{result}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Testing
|
@ -8,8 +8,8 @@ import { WEBSOCKET_URL } from '../utils/ws'
|
||||
const BASE_URL = '/main:app_store:sys'
|
||||
|
||||
interface AppsStore {
|
||||
listings: AppListing[]
|
||||
installed: PackageState[]
|
||||
listings: Record<string, AppListing>
|
||||
installed: Record<string, PackageState>
|
||||
downloads: Record<string, DownloadItem[]>
|
||||
ourApps: AppListing[]
|
||||
ws: KinodeClientApi
|
||||
@ -18,9 +18,10 @@ interface AppsStore {
|
||||
fetchListings: () => Promise<void>
|
||||
fetchListing: (id: string) => Promise<AppListing>
|
||||
fetchInstalled: () => Promise<void>
|
||||
fetchDownloads: () => Promise<DownloadItem[]>;
|
||||
fetchOurApps: () => Promise<void>;
|
||||
fetchDownloadsForApp: (id: string) => Promise<DownloadItem[]>;
|
||||
fetchInstalledApp: (id: string) => Promise<PackageState | null>
|
||||
fetchDownloads: () => Promise<DownloadItem[]>
|
||||
fetchOurApps: () => Promise<void>
|
||||
fetchDownloadsForApp: (id: string) => Promise<DownloadItem[]>
|
||||
checkMirror: (node: string) => Promise<MirrorCheckFile>
|
||||
|
||||
installApp: (id: string, version_hash: string) => Promise<void>
|
||||
@ -42,8 +43,8 @@ const appId = (id: string): PackageId => {
|
||||
const useAppsStore = create<AppsStore>()(
|
||||
persist(
|
||||
(set, get): AppsStore => ({
|
||||
listings: [],
|
||||
installed: [],
|
||||
listings: {},
|
||||
installed: {},
|
||||
downloads: {},
|
||||
ourApps: [],
|
||||
activeDownloads: {},
|
||||
@ -77,31 +78,51 @@ const useAppsStore = create<AppsStore>()(
|
||||
fetchListings: async () => {
|
||||
const res = await fetch(`${BASE_URL}/apps`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data = await res.json()
|
||||
set({ listings: data || [] })
|
||||
const data: AppListing[] = await res.json()
|
||||
const listingsMap = data.reduce((acc, listing) => {
|
||||
acc[`${listing.package_id.package_name}:${listing.package_id.publisher_node}`] = listing
|
||||
return acc
|
||||
}, {} as Record<string, AppListing>)
|
||||
set({ listings: listingsMap })
|
||||
}
|
||||
},
|
||||
|
||||
fetchListing: async (id: string) => {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const listing = await res.json()
|
||||
const listing: AppListing = await res.json()
|
||||
set((state) => ({
|
||||
listings: state.listings.map(l => l.package_id === appId(id) ? listing : l)
|
||||
listings: { ...state.listings, [id]: listing }
|
||||
}))
|
||||
return listing
|
||||
}
|
||||
throw new Error(`Failed to get listing for app: ${id}`)
|
||||
throw new Error(`Failed to fetch listing for app: ${id}`)
|
||||
},
|
||||
|
||||
fetchInstalled: async () => {
|
||||
const res = await fetch(`${BASE_URL}/installed`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const installed = await res.json()
|
||||
set({ installed })
|
||||
const data: PackageState[] = await res.json()
|
||||
const installedMap = data.reduce((acc, pkg) => {
|
||||
acc[`${pkg.package_id.package_name}:${pkg.package_id.publisher_node}`] = pkg
|
||||
return acc
|
||||
}, {} as Record<string, PackageState>)
|
||||
set({ installed: installedMap })
|
||||
}
|
||||
},
|
||||
|
||||
fetchInstalledApp: async (id: string) => {
|
||||
const res = await fetch(`${BASE_URL}/installed/${id}`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const installedApp: PackageState = await res.json()
|
||||
set((state) => ({
|
||||
installed: { ...state.installed, [id]: installedApp }
|
||||
}))
|
||||
return installedApp
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
fetchDownloads: async () => {
|
||||
const res = await fetch(`${BASE_URL}/downloads`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
@ -114,17 +135,16 @@ const useAppsStore = create<AppsStore>()(
|
||||
|
||||
fetchOurApps: async () => {
|
||||
const res = await fetch(`${BASE_URL}/ourapps`)
|
||||
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data = await res.json()
|
||||
set({ ourApps: data || [] })
|
||||
const data: AppListing[] = await res.json()
|
||||
set({ ourApps: data })
|
||||
}
|
||||
},
|
||||
|
||||
fetchDownloadsForApp: async (id: string) => {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads = await res.json()
|
||||
const downloads: DownloadItem[] = await res.json()
|
||||
set((state) => ({
|
||||
downloads: { ...state.downloads, [id]: downloads }
|
||||
}))
|
||||
@ -136,7 +156,7 @@ const useAppsStore = create<AppsStore>()(
|
||||
checkMirror: async (node: string) => {
|
||||
const res = await fetch(`${BASE_URL}/mirrorcheck/${node}`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json()
|
||||
return await res.json() as MirrorCheckFile
|
||||
}
|
||||
throw new Error(`Failed to check mirror status for node: ${node}`)
|
||||
},
|
||||
@ -173,7 +193,7 @@ const useAppsStore = create<AppsStore>()(
|
||||
getCaps: async (id: string) => {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/caps`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json()
|
||||
return await res.json() as PackageManifest
|
||||
}
|
||||
throw new Error(`Failed to get caps for app: ${id}`)
|
||||
},
|
||||
|
12
kinode/packages/app_store/ui/src/utils/compareVersions.ts
Normal file
12
kinode/packages/app_store/ui/src/utils/compareVersions.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Helper function to compare version strings
|
||||
export const compareVersions = (v1: string, v2: string) => {
|
||||
const parts1 = v1.split('.').map(Number);
|
||||
const parts2 = v2.split('.').map(Number);
|
||||
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||
const part1 = parts1[i] || 0;
|
||||
const part2 = parts2[i] || 0;
|
||||
if (part1 > part2) return 1;
|
||||
if (part1 < part2) return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
Loading…
Reference in New Issue
Block a user