mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 11:53:31 +03:00
app_store UI: downloads page
This commit is contained in:
parent
71d6ad486c
commit
29d30d7c32
@ -2,12 +2,14 @@ import React from "react";
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
|
||||
import Header from "./components/Header";
|
||||
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
import { APP_DETAILS_PATH, DOWNLOAD_PATH, MY_DOWNLOADS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
|
||||
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";
|
||||
|
||||
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
@ -23,8 +25,10 @@ function App() {
|
||||
<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 />} />
|
||||
<Route path={PUBLISH_PATH} element={<PublishPage />} />
|
||||
<Route path={`${DOWNLOAD_PATH}/:id`} element={<DownloadPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div >
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { STORE_PATH, PUBLISH_PATH } from '../constants/path';
|
||||
import { STORE_PATH, PUBLISH_PATH, MY_DOWNLOADS_PATH } from '../constants/path';
|
||||
import { ConnectButton } from '@rainbow-me/rainbowkit';
|
||||
import { FaHome } from "react-icons/fa";
|
||||
|
||||
@ -14,6 +14,7 @@ const Header: React.FC = () => {
|
||||
</button>
|
||||
<Link to={STORE_PATH} className={location.pathname === STORE_PATH ? 'active' : ''}>Apps</Link>
|
||||
<Link to={PUBLISH_PATH} className={location.pathname === PUBLISH_PATH ? 'active' : ''}>Publish</Link>
|
||||
<Link to={MY_DOWNLOADS_PATH} className={location.pathname === MY_DOWNLOADS_PATH ? 'active' : ''}>My Downloads</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const STORE_PATH = '/';
|
||||
export const PUBLISH_PATH = '/publish';
|
||||
export const APP_DETAILS_PATH = '/app';
|
||||
export const DOWNLOAD_PATH = '/download';
|
||||
export const MY_DOWNLOADS_PATH = '/my-downloads';
|
||||
|
@ -858,3 +858,90 @@ button svg,
|
||||
margin: 0;
|
||||
color: light-dark(var(--off-black), var(--off-white));
|
||||
}
|
||||
|
||||
.downloads-page {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.app-title h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-id {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.app-description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-selection {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mirror-selection select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.downloads-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.downloads-table th,
|
||||
.downloads-table td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.downloads-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.download-button,
|
||||
.install-button {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-section h3 {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.json-display {
|
||||
background-color: #f2f2f2;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
@ -1,55 +1,49 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
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";
|
||||
|
||||
export default function AppPage() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { listings, installed, fetchListings, fetchInstalled } = 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);
|
||||
|
||||
useEffect(() => {
|
||||
fetchListings();
|
||||
fetchInstalled();
|
||||
}, [fetchListings, fetchInstalled]);
|
||||
const loadData = async () => {
|
||||
await Promise.all([fetchListings(), fetchInstalled()]);
|
||||
|
||||
const app = useMemo(() => {
|
||||
const foundApp = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === id) || null;
|
||||
console.log("Found app:", foundApp);
|
||||
return foundApp;
|
||||
}, [listings, id]);
|
||||
const foundApp = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === id) || null;
|
||||
setApp(foundApp);
|
||||
|
||||
const installedApp = useMemo(() => {
|
||||
if (!app) return null;
|
||||
const foundInstalledApp = installed.find(i =>
|
||||
i.package_id.package_name === app.package_id.package_name &&
|
||||
i.package_id.publisher_node === app.package_id.publisher_node
|
||||
) || null;
|
||||
console.log("Found installed app:", foundInstalledApp);
|
||||
return foundInstalledApp;
|
||||
}, [app, installed]);
|
||||
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);
|
||||
|
||||
const { currentVersion, latestVersion } = useMemo(() => {
|
||||
let current: string | null = null;
|
||||
let latest: string | null = null;
|
||||
if (app?.metadata?.properties?.code_hashes) {
|
||||
console.log("Code hashes:", app.metadata.properties.code_hashes);
|
||||
const versions = app.metadata.properties.code_hashes;
|
||||
if (versions.length > 0) {
|
||||
latest = versions[versions.length - 1][0];
|
||||
if (installedApp) {
|
||||
const installedVersion = versions.find(([_, hash]) => hash === installedApp.our_version_hash);
|
||||
if (installedVersion) {
|
||||
current = installedVersion[0];
|
||||
if (foundApp.metadata?.properties?.code_hashes) {
|
||||
const versions = foundApp.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);
|
||||
if (installedVersion) {
|
||||
setCurrentVersion(installedVersion[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("No code hashes found in app metadata");
|
||||
}
|
||||
console.log("Current version:", current, "Latest version:", latest);
|
||||
return { currentVersion: current, latestVersion: latest };
|
||||
}, [app, installedApp]);
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [id, fetchListings, fetchInstalled, listings, installed]);
|
||||
|
||||
if (!app) {
|
||||
return <div className="app-page"><h4>App details not found for {id}</h4></div>;
|
||||
|
212
kinode/packages/app_store/ui/src/pages/DownloadPage.tsx
Normal file
212
kinode/packages/app_store/ui/src/pages/DownloadPage.tsx
Normal file
@ -0,0 +1,212 @@
|
||||
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";
|
||||
|
||||
export default function DownloadPage() {
|
||||
const { id } = useParams();
|
||||
const { listings, fetchListings, fetchDownloadsForApp, downloadApp, installApp, checkMirror, fetchInstalled, installed, getCaps } = useAppsStore();
|
||||
const [downloads, setDownloads] = useState<DownloadItem[]>([]);
|
||||
const [selectedMirror, setSelectedMirror] = useState<string | null>(null);
|
||||
const [mirrorStatuses, setMirrorStatuses] = useState<{ [mirror: string]: MirrorCheckFile | null }>({});
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showMetadata, setShowMetadata] = useState(false);
|
||||
const [showManifest, setShowManifest] = useState(false);
|
||||
const [showCaps, setShowCaps] = useState(false);
|
||||
const [manifest, setManifest] = useState<PackageManifest | null>(null);
|
||||
|
||||
const app = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === id);
|
||||
|
||||
useEffect(() => {
|
||||
fetchListings();
|
||||
fetchInstalled();
|
||||
if (id) {
|
||||
fetchDownloadsForApp(id).then(setDownloads);
|
||||
}
|
||||
}, [id, fetchListings, fetchDownloadsForApp, fetchInstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (app) {
|
||||
checkMirrors();
|
||||
}
|
||||
}, [app]);
|
||||
|
||||
const checkMirrors = async () => {
|
||||
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 status = await checkMirror(mirror);
|
||||
statuses[mirror] = status;
|
||||
}
|
||||
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) => {
|
||||
if (!app || !selectedMirror) return;
|
||||
setIsDownloading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await downloadApp(`${app.package_id.package_name}:${app.package_id.publisher_node}`, selectedMirror, version);
|
||||
const updatedDownloads = await fetchDownloadsForApp(id!);
|
||||
setDownloads(updatedDownloads);
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
setError(`Download failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstall = async (version: string) => {
|
||||
if (!app) return;
|
||||
setIsInstalling(true);
|
||||
setError(null);
|
||||
try {
|
||||
await installApp(`${app.package_id.package_name}:${app.package_id.publisher_node}`, version);
|
||||
fetchInstalled();
|
||||
} catch (error) {
|
||||
console.error('Installation failed:', error);
|
||||
setError(`Installation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!app) {
|
||||
return <div className="downloads-page"><h4>App details not found for {id}</h4></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="downloads-page">
|
||||
<div className="app-header">
|
||||
{app.metadata?.image && (
|
||||
<img src={app.metadata.image} alt={app.metadata?.name || app.package_id.package_name} className="app-icon" />
|
||||
)}
|
||||
<div className="app-title">
|
||||
<h2>{app.metadata?.name || app.package_id.package_name}</h2>
|
||||
<p className="app-id">{`${app.package_id.package_name}.${app.package_id.publisher_node}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-description">{app.metadata?.description || "No description available"}</div>
|
||||
|
||||
<div className="mirror-selection">
|
||||
<h3>Select Mirror</h3>
|
||||
<select
|
||||
value={selectedMirror || ''}
|
||||
onChange={(e) => setSelectedMirror(e.target.value)}
|
||||
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)'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h3>Available Versions</h3>
|
||||
<table className="downloads-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{app.metadata?.properties?.code_hashes.map(([version, hash]) => {
|
||||
const download = downloads.find(d => d.name === `${hash}.zip`);
|
||||
const isDownloaded = !!download;
|
||||
const isInstalled = installed.some(i => i.package_id.package_name === app.package_id.package_name && i.our_version_hash === version);
|
||||
|
||||
return (
|
||||
<tr key={version}>
|
||||
<td>{version}</td>
|
||||
<td>{download?.size ? `${(download.size / 1024 / 1024).toFixed(2)} MB` : 'N/A'}</td>
|
||||
<td>
|
||||
{isInstalled ? 'Installed' : isDownloaded ? 'Downloaded' : 'Not downloaded'}
|
||||
</td>
|
||||
<td>
|
||||
{!isDownloaded && (
|
||||
<button
|
||||
onClick={() => handleDownload(version)}
|
||||
disabled={!selectedMirror || isDownloading}
|
||||
className="download-button"
|
||||
>
|
||||
{isDownloading ? <FaSpinner className="fa-spin" /> : <FaDownload />} Download
|
||||
</button>
|
||||
)}
|
||||
{isDownloaded && !isInstalled && (
|
||||
<button
|
||||
onClick={() => handleInstall(version)}
|
||||
disabled={isInstalling}
|
||||
className="install-button"
|
||||
>
|
||||
{isInstalling ? <FaSpinner className="fa-spin" /> : <FaRocket />} Install
|
||||
</button>
|
||||
)}
|
||||
{isInstalled && (
|
||||
<FaCheck className="installed" />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{error && (
|
||||
<div className="error-message">
|
||||
<FaExclamationTriangle /> {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="app-details">
|
||||
<div className="detail-section">
|
||||
<h3 onClick={() => setShowMetadata(!showMetadata)}>
|
||||
Metadata {showMetadata ? <FaChevronUp /> : <FaChevronDown />}
|
||||
</h3>
|
||||
{showMetadata && (
|
||||
<pre className="json-display">
|
||||
{JSON.stringify(app.metadata, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{manifest && (
|
||||
<div className="detail-section">
|
||||
<h3 onClick={() => setShowManifest(!showManifest)}>
|
||||
Manifest {showManifest ? <FaChevronUp /> : <FaChevronDown />}
|
||||
</h3>
|
||||
{showManifest && (
|
||||
<pre className="json-display">
|
||||
{JSON.stringify(manifest, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{manifest && manifest.request_capabilities && (
|
||||
<div className="detail-section">
|
||||
<h3 onClick={() => setShowCaps(!showCaps)}>
|
||||
Capabilities {showCaps ? <FaChevronUp /> : <FaChevronDown />}
|
||||
</h3>
|
||||
{showCaps && (
|
||||
<pre className="json-display">
|
||||
{JSON.stringify(manifest.request_capabilities, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
91
kinode/packages/app_store/ui/src/pages/MyDownloadsPage.tsx
Normal file
91
kinode/packages/app_store/ui/src/pages/MyDownloadsPage.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FaFolder, FaFile, FaChevronLeft } from "react-icons/fa";
|
||||
import useAppsStore from "../store";
|
||||
import { DownloadItem } from "../types/Apps";
|
||||
|
||||
export default function MyDownloadsPage() {
|
||||
const { fetchDownloads, fetchDownloadsForApp, listings } = useAppsStore();
|
||||
const [currentPath, setCurrentPath] = useState<string[]>([]);
|
||||
const [items, setItems] = useState<DownloadItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
loadItems();
|
||||
}, [currentPath]);
|
||||
|
||||
const loadItems = async () => {
|
||||
let downloads: DownloadItem[];
|
||||
if (currentPath.length === 0) {
|
||||
downloads = await fetchDownloads();
|
||||
} else {
|
||||
downloads = await fetchDownloadsForApp(currentPath.join(':'));
|
||||
}
|
||||
setItems(downloads);
|
||||
};
|
||||
|
||||
const navigateToItem = (item: DownloadItem) => {
|
||||
if (!item.is_file) {
|
||||
setCurrentPath([...currentPath, item.name]);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateUp = () => {
|
||||
setCurrentPath(currentPath.slice(0, -1));
|
||||
};
|
||||
|
||||
const getItemVersion = (name: string): string | undefined => {
|
||||
if (currentPath.length === 0) {
|
||||
return undefined; // No version for top-level directories
|
||||
}
|
||||
|
||||
const appId = currentPath.join(':');
|
||||
const app = listings.find(a => `${a.package_id.package_name}:${a.package_id.publisher_node}` === appId);
|
||||
|
||||
if (app && app.metadata?.properties?.code_hashes) {
|
||||
const matchingHash = app.metadata.properties.code_hashes.find(([_, hash]) => `${hash}.zip` === name);
|
||||
return matchingHash ? matchingHash[0] : undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="downloads-page">
|
||||
<h2>Downloads</h2>
|
||||
<div className="file-explorer">
|
||||
<div className="path-navigation">
|
||||
{currentPath.length > 0 && (
|
||||
<button onClick={navigateUp} className="navigate-up">
|
||||
<FaChevronLeft /> Back
|
||||
</button>
|
||||
)}
|
||||
<span className="current-path">/{currentPath.join('/')}</span>
|
||||
</div>
|
||||
<table className="downloads-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
<th>Version</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
const version = getItemVersion(item.name);
|
||||
return (
|
||||
<tr key={index} onClick={() => navigateToItem(item)} className={item.is_file ? 'file' : 'directory'}>
|
||||
<td>
|
||||
{item.is_file ? <FaFile /> : <FaFolder />} {item.name}
|
||||
</td>
|
||||
<td>{item.is_file ? 'File' : 'Directory'}</td>
|
||||
<td>{item.size ? `${(item.size / 1024).toFixed(2)} KB` : '-'}</td>
|
||||
<td>{version || '-'}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -11,10 +11,12 @@ export default function StorePage() {
|
||||
fetchListings();
|
||||
}, [fetchListings]);
|
||||
|
||||
const filteredApps = listings?.filter((app) =>
|
||||
app.package_id.package_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
) || [];
|
||||
const filteredApps = Array.isArray(listings)
|
||||
? listings.filter((app) =>
|
||||
app.package_id.package_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
app.metadata?.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="store-page">
|
||||
@ -27,7 +29,7 @@ export default function StorePage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="app-list">
|
||||
{listings === undefined ? (
|
||||
{!Array.isArray(listings) ? (
|
||||
<p>Loading...</p>
|
||||
) : listings.length === 0 ? (
|
||||
<p>No apps available.</p>
|
||||
@ -53,6 +55,7 @@ export default function StorePage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ... rest of the code remains the same
|
||||
interface AppRowProps {
|
||||
app: AppListing;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ const Testing: React.FC = () => {
|
||||
<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(fetchDownloads, 'downloads')}>Refresh Downloads</button> */}
|
||||
<button onClick={() => handleAction(fetchOurApps, 'ourApps')}>Refresh Our Apps</button>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { PackageState, AppListing, MirrorCheckFile, PackageManifest, Download, PackageId } from '../types/Apps'
|
||||
import { PackageState, AppListing, MirrorCheckFile, PackageManifest, DownloadItem, PackageId } from '../types/Apps'
|
||||
import { HTTP_STATUS } from '../constants/http'
|
||||
import KinodeClientApi from "@kinode/client-api"
|
||||
import { WEBSOCKET_URL } from '../utils/ws'
|
||||
@ -10,7 +10,7 @@ const BASE_URL = '/main:app_store:sys'
|
||||
interface AppsStore {
|
||||
listings: AppListing[]
|
||||
installed: PackageState[]
|
||||
downloads: Record<string, Download[]>
|
||||
downloads: Record<string, DownloadItem[]>
|
||||
ourApps: AppListing[]
|
||||
ws: KinodeClientApi
|
||||
activeDownloads: Record<string, [number, number]>
|
||||
@ -18,11 +18,11 @@ interface AppsStore {
|
||||
fetchListings: () => Promise<void>
|
||||
fetchListing: (id: string) => Promise<AppListing>
|
||||
fetchInstalled: () => Promise<void>
|
||||
fetchDownloads: () => Promise<void>
|
||||
fetchOurApps: () => Promise<void>
|
||||
fetchDownloadsForApp: (id: string) => Promise<Download[]>
|
||||
|
||||
fetchDownloads: () => Promise<DownloadItem[]>;
|
||||
fetchOurApps: () => Promise<void>;
|
||||
fetchDownloadsForApp: (id: string) => Promise<DownloadItem[]>;
|
||||
checkMirror: (node: string) => Promise<MirrorCheckFile>
|
||||
|
||||
installApp: (id: string, version_hash: string) => Promise<void>
|
||||
uninstallApp: (id: string) => Promise<void>
|
||||
downloadApp: (id: string, version_hash: string, downloadFrom: string) => Promise<void>
|
||||
@ -106,8 +106,10 @@ const useAppsStore = create<AppsStore>()(
|
||||
const res = await fetch(`${BASE_URL}/downloads`)
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads = await res.json()
|
||||
set({ downloads })
|
||||
set({ downloads: { root: downloads } })
|
||||
return downloads
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
fetchOurApps: async () => {
|
||||
|
@ -12,7 +12,7 @@ export interface AppListing {
|
||||
auto_update: boolean
|
||||
}
|
||||
|
||||
export interface Download {
|
||||
export interface DownloadItem {
|
||||
name: string,
|
||||
is_file: boolean,
|
||||
size?: number
|
||||
|
Loading…
Reference in New Issue
Block a user