mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-22 16:11:38 +03:00
Merge pull request #568 from kinode-dao/bp/autoupdatesfix
app_store: fix auto_updates
This commit is contained in:
commit
c91a8898b7
@ -184,6 +184,8 @@ interface downloads {
|
|||||||
auto-update(auto-update-request),
|
auto-update(auto-update-request),
|
||||||
/// Notify that a download is complete
|
/// Notify that a download is complete
|
||||||
download-complete(download-complete-request),
|
download-complete(download-complete-request),
|
||||||
|
/// Auto-update-download complete
|
||||||
|
auto-download-complete(auto-download-complete-request),
|
||||||
/// Get files for a package
|
/// Get files for a package
|
||||||
get-files(option<package-id>),
|
get-files(option<package-id>),
|
||||||
/// Remove a file
|
/// Remove a file
|
||||||
@ -243,6 +245,12 @@ interface downloads {
|
|||||||
err: option<download-error>,
|
err: option<download-error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request for an auto-download complete
|
||||||
|
record auto-download-complete-request {
|
||||||
|
download-info: download-complete-request,
|
||||||
|
manifest-hash: string,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a hash mismatch error
|
/// Represents a hash mismatch error
|
||||||
record hash-mismatch {
|
record hash-mismatch {
|
||||||
desired: string,
|
desired: string,
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
//! It delegates these responsibilities to the downloads and chain processes respectively.
|
//! It delegates these responsibilities to the downloads and chain processes respectively.
|
||||||
//!
|
//!
|
||||||
use crate::kinode::process::downloads::{
|
use crate::kinode::process::downloads::{
|
||||||
DownloadCompleteRequest, DownloadResponses, ProgressUpdate,
|
AutoDownloadCompleteRequest, DownloadCompleteRequest, DownloadResponses, ProgressUpdate,
|
||||||
};
|
};
|
||||||
use crate::kinode::process::main::{
|
use crate::kinode::process::main::{
|
||||||
ApisResponse, GetApiResponse, InstallPackageRequest, InstallResponse, LocalRequest,
|
ApisResponse, GetApiResponse, InstallPackageRequest, InstallResponse, LocalRequest,
|
||||||
@ -65,6 +65,7 @@ pub enum Req {
|
|||||||
LocalRequest(LocalRequest),
|
LocalRequest(LocalRequest),
|
||||||
Progress(ProgressUpdate),
|
Progress(ProgressUpdate),
|
||||||
DownloadComplete(DownloadCompleteRequest),
|
DownloadComplete(DownloadCompleteRequest),
|
||||||
|
AutoDownloadComplete(AutoDownloadCompleteRequest),
|
||||||
Http(http::server::HttpServerRequest),
|
Http(http::server::HttpServerRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +162,40 @@ fn handle_message(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Req::AutoDownloadComplete(req) => {
|
||||||
|
if !message.is_local(&our) {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"auto download complete from non-local node"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// auto_install case:
|
||||||
|
// the downloads process has given us the new package manifest's
|
||||||
|
// capability hashes, and the old package's capability hashes.
|
||||||
|
// we can use these to determine if the new package has the same
|
||||||
|
// capabilities as the old one, and if so, auto-install it.
|
||||||
|
|
||||||
|
let manifest_hash = req.manifest_hash;
|
||||||
|
let package_id = req.download_info.package_id;
|
||||||
|
let version_hash = req.download_info.version_hash;
|
||||||
|
|
||||||
|
if let Some(package) = state.packages.get(&package_id.clone().to_process_lib()) {
|
||||||
|
if package.manifest_hash == Some(manifest_hash) {
|
||||||
|
print_to_terminal(1, "auto_install:main, manifest_hash match");
|
||||||
|
if let Err(e) =
|
||||||
|
utils::install(&package_id, None, &version_hash, state, &our.node)
|
||||||
|
{
|
||||||
|
print_to_terminal(1, &format!("error auto_installing package: {e}"));
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"auto_installed update for package: {:?}",
|
||||||
|
&package_id.to_process_lib()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print_to_terminal(1, "auto_install:main, manifest_hash do not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Req::DownloadComplete(req) => {
|
Req::DownloadComplete(req) => {
|
||||||
if !message.is_local(&our) {
|
if !message.is_local(&our) {
|
||||||
return Err(anyhow::anyhow!("download complete from non-local node"));
|
return Err(anyhow::anyhow!("download complete from non-local node"));
|
||||||
@ -182,41 +217,6 @@ fn handle_message(
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// auto_install case:
|
|
||||||
// the downloads process has given us the new package manifest's
|
|
||||||
// capability hashes, and the old package's capability hashes.
|
|
||||||
// we can use these to determine if the new package has the same
|
|
||||||
// capabilities as the old one, and if so, auto-install it.
|
|
||||||
if let Some(context) = message.context() {
|
|
||||||
let manifest_hash = String::from_utf8(context.to_vec())?;
|
|
||||||
if let Some(package) =
|
|
||||||
state.packages.get(&req.package_id.clone().to_process_lib())
|
|
||||||
{
|
|
||||||
if package.manifest_hash == Some(manifest_hash) {
|
|
||||||
print_to_terminal(1, "auto_install:main, manifest_hash match");
|
|
||||||
if let Err(e) = utils::install(
|
|
||||||
&req.package_id,
|
|
||||||
None,
|
|
||||||
&req.version_hash,
|
|
||||||
state,
|
|
||||||
&our.node,
|
|
||||||
) {
|
|
||||||
print_to_terminal(
|
|
||||||
1,
|
|
||||||
&format!("error auto_installing package: {e}"),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"auto_installed update for package: {:?}",
|
|
||||||
&req.package_id.to_process_lib()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print_to_terminal(1, "auto_install:main, manifest_hash do not match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,9 +42,9 @@
|
|||||||
//! mechanism is implemented in the FT worker for improved modularity and performance.
|
//! mechanism is implemented in the FT worker for improved modularity and performance.
|
||||||
//!
|
//!
|
||||||
use crate::kinode::process::downloads::{
|
use crate::kinode::process::downloads::{
|
||||||
AutoUpdateRequest, DirEntry, DownloadCompleteRequest, DownloadError, DownloadRequests,
|
AutoDownloadCompleteRequest, AutoUpdateRequest, DirEntry, DownloadCompleteRequest,
|
||||||
DownloadResponses, Entry, FileEntry, HashMismatch, LocalDownloadRequest, RemoteDownloadRequest,
|
DownloadError, DownloadRequests, DownloadResponses, Entry, FileEntry, HashMismatch,
|
||||||
RemoveFileRequest,
|
LocalDownloadRequest, RemoteDownloadRequest, RemoveFileRequest,
|
||||||
};
|
};
|
||||||
use std::{collections::HashSet, io::Read, str::FromStr};
|
use std::{collections::HashSet, io::Read, str::FromStr};
|
||||||
|
|
||||||
@ -245,7 +245,7 @@ fn handle_message(
|
|||||||
// if we have a pending auto_install, forward that context to the main process.
|
// if we have a pending auto_install, forward that context to the main process.
|
||||||
// it will check if the caps_hashes match (no change in capabilities), and auto_install if it does.
|
// it will check if the caps_hashes match (no change in capabilities), and auto_install if it does.
|
||||||
|
|
||||||
let context = if auto_updates.remove(&(
|
let manifest_hash = if auto_updates.remove(&(
|
||||||
req.package_id.clone().to_process_lib(),
|
req.package_id.clone().to_process_lib(),
|
||||||
req.version_hash.clone(),
|
req.version_hash.clone(),
|
||||||
)) {
|
)) {
|
||||||
@ -253,7 +253,7 @@ fn handle_message(
|
|||||||
req.package_id.clone().to_process_lib(),
|
req.package_id.clone().to_process_lib(),
|
||||||
req.version_hash.clone(),
|
req.version_hash.clone(),
|
||||||
) {
|
) {
|
||||||
Ok(manifest_hash) => Some(manifest_hash.as_bytes().to_vec()),
|
Ok(manifest_hash) => Some(manifest_hash),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_to_terminal(
|
print_to_terminal(
|
||||||
1,
|
1,
|
||||||
@ -267,13 +267,26 @@ fn handle_message(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// pushed to UI via websockets
|
// pushed to UI via websockets
|
||||||
let mut request = Request::to(("our", "main", "app_store", "sys"))
|
Request::to(("our", "main", "app_store", "sys"))
|
||||||
.body(serde_json::to_vec(&req)?);
|
.body(serde_json::to_vec(&req)?)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
if let Some(ctx) = context {
|
// trigger auto-update install trigger to main:app_store:sys
|
||||||
request = request.context(ctx);
|
if let Some(manifest_hash) = manifest_hash {
|
||||||
|
let auto_download_complete_req = AutoDownloadCompleteRequest {
|
||||||
|
download_info: req.clone(),
|
||||||
|
manifest_hash,
|
||||||
|
};
|
||||||
|
print_to_terminal(
|
||||||
|
1,
|
||||||
|
&format!(
|
||||||
|
"auto_update download complete: triggering install on main:app_store:sys"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Request::to(("our", "main", "app_store", "sys"))
|
||||||
|
.body(serde_json::to_vec(&auto_download_complete_req)?)
|
||||||
|
.send()?;
|
||||||
}
|
}
|
||||||
request.send()?;
|
|
||||||
}
|
}
|
||||||
DownloadRequests::GetFiles(maybe_id) => {
|
DownloadRequests::GetFiles(maybe_id) => {
|
||||||
// if not local, throw to the boonies.
|
// if not local, throw to the boonies.
|
||||||
|
@ -6,7 +6,7 @@ interface PackageSelectorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PackageSelector: React.FC<PackageSelectorProps> = ({ onPackageSelect }) => {
|
const PackageSelector: React.FC<PackageSelectorProps> = ({ onPackageSelect }) => {
|
||||||
const { installed } = useAppsStore();
|
const { installed, fetchInstalled } = useAppsStore();
|
||||||
const [selectedPackage, setSelectedPackage] = useState<string>("");
|
const [selectedPackage, setSelectedPackage] = useState<string>("");
|
||||||
const [customPackage, setCustomPackage] = useState<string>("");
|
const [customPackage, setCustomPackage] = useState<string>("");
|
||||||
const [isCustomPackageSelected, setIsCustomPackageSelected] = useState(false);
|
const [isCustomPackageSelected, setIsCustomPackageSelected] = useState(false);
|
||||||
@ -18,6 +18,10 @@ const PackageSelector: React.FC<PackageSelectorProps> = ({ onPackageSelect }) =>
|
|||||||
}
|
}
|
||||||
}, [selectedPackage, onPackageSelect]);
|
}, [selectedPackage, onPackageSelect]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInstalled();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handlePackageChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handlePackageChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
if (value === "custom") {
|
if (value === "custom") {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { FaDownload, FaSpinner, FaChevronDown, FaChevronUp, FaRocket, FaTrash, FaPlay } from "react-icons/fa";
|
import { FaDownload, FaSpinner, FaChevronDown, FaChevronUp, FaRocket, FaTrash, FaPlay } from "react-icons/fa";
|
||||||
import useAppsStore from "../store";
|
import useAppsStore from "../store";
|
||||||
import { MirrorSelector } from '../components';
|
import { MirrorSelector } from '../components';
|
||||||
|
|
||||||
export default function DownloadPage() {
|
export default function DownloadPage() {
|
||||||
const navigate = useNavigate();
|
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const {
|
const {
|
||||||
listings,
|
listings,
|
||||||
@ -28,6 +27,9 @@ export default function DownloadPage() {
|
|||||||
const [isMirrorOnline, setIsMirrorOnline] = useState<boolean | null>(null);
|
const [isMirrorOnline, setIsMirrorOnline] = useState<boolean | null>(null);
|
||||||
const [showCapApproval, setShowCapApproval] = useState(false);
|
const [showCapApproval, setShowCapApproval] = useState(false);
|
||||||
const [manifest, setManifest] = useState<any>(null);
|
const [manifest, setManifest] = useState<any>(null);
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
|
const [isCheckingLaunch, setIsCheckingLaunch] = useState(false);
|
||||||
|
const [launchPath, setLaunchPath] = useState<string | null>(null);
|
||||||
|
|
||||||
const app = useMemo(() => listings[id || ""], [listings, id]);
|
const app = useMemo(() => listings[id || ""], [listings, id]);
|
||||||
const appDownloads = useMemo(() => downloads[id || ""] || [], [downloads, id]);
|
const appDownloads = useMemo(() => downloads[id || ""] || [], [downloads, id]);
|
||||||
@ -101,6 +103,36 @@ export default function DownloadPage() {
|
|||||||
return versionData ? installedApp.our_version_hash === versionData.hash : false;
|
return versionData ? installedApp.our_version_hash === versionData.hash : false;
|
||||||
}, [app, selectedVersion, installedApp, sortedVersions]);
|
}, [app, selectedVersion, installedApp, sortedVersions]);
|
||||||
|
|
||||||
|
const checkLaunchPath = useCallback(() => {
|
||||||
|
if (!app) return;
|
||||||
|
setIsCheckingLaunch(true);
|
||||||
|
const appId = `${app.package_id.package_name}:${app.package_id.publisher_node}`;
|
||||||
|
fetchHomepageApps().then(() => {
|
||||||
|
const path = getLaunchUrl(appId);
|
||||||
|
setLaunchPath(path);
|
||||||
|
setIsCheckingLaunch(false);
|
||||||
|
if (path) {
|
||||||
|
setIsInstalling(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [app, fetchHomepageApps, getLaunchUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInstalling) {
|
||||||
|
const checkInterval = setInterval(checkLaunchPath, 500);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
setIsInstalling(false);
|
||||||
|
setIsCheckingLaunch(false);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isInstalling, checkLaunchPath]);
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
if (!id || !selectedMirror || !app || !selectedVersion) return;
|
if (!id || !selectedMirror || !app || !selectedVersion) return;
|
||||||
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
||||||
@ -130,36 +162,87 @@ export default function DownloadPage() {
|
|||||||
}
|
}
|
||||||
}, [id, app, appDownloads]);
|
}, [id, app, appDownloads]);
|
||||||
|
|
||||||
const canDownload = useMemo(() => {
|
|
||||||
return selectedMirror && (isMirrorOnline === true || selectedMirror.startsWith('http')) && !isDownloading && !isDownloaded;
|
|
||||||
}, [selectedMirror, isMirrorOnline, isDownloading, isDownloaded]);
|
|
||||||
|
|
||||||
const confirmInstall = useCallback(() => {
|
const confirmInstall = useCallback(() => {
|
||||||
if (!id || !selectedVersion) return;
|
if (!id || !selectedVersion) return;
|
||||||
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
||||||
if (versionData) {
|
if (versionData) {
|
||||||
|
setIsInstalling(true);
|
||||||
|
setLaunchPath(null);
|
||||||
installApp(id, versionData.hash).then(() => {
|
installApp(id, versionData.hash).then(() => {
|
||||||
fetchData(id);
|
|
||||||
setShowCapApproval(false);
|
setShowCapApproval(false);
|
||||||
setManifest(null);
|
setManifest(null);
|
||||||
|
fetchData(id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [id, selectedVersion, sortedVersions, installApp, fetchData]);
|
}, [id, selectedVersion, sortedVersions, installApp, fetchData]);
|
||||||
|
|
||||||
const handleLaunch = useCallback(() => {
|
const handleLaunch = useCallback(() => {
|
||||||
if (app) {
|
if (launchPath) {
|
||||||
const launchUrl = getLaunchUrl(`${app.package_id.package_name}:${app.package_id.publisher_node}`);
|
window.location.href = launchPath;
|
||||||
if (launchUrl) {
|
|
||||||
window.location.href = launchUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [app, getLaunchUrl]);
|
}, [launchPath]);
|
||||||
|
|
||||||
const canLaunch = useMemo(() => {
|
const canLaunch = useMemo(() => {
|
||||||
if (!app) return false;
|
if (!app) return false;
|
||||||
return !!getLaunchUrl(`${app.package_id.package_name}:${app.package_id.publisher_node}`);
|
return !!getLaunchUrl(`${app.package_id.package_name}:${app.package_id.publisher_node}`);
|
||||||
}, [app, getLaunchUrl]);
|
}, [app, getLaunchUrl]);
|
||||||
|
|
||||||
|
const canDownload = useMemo(() => {
|
||||||
|
return selectedMirror && (isMirrorOnline === true || selectedMirror.startsWith('http')) && !isDownloading && !isDownloaded;
|
||||||
|
}, [selectedMirror, isMirrorOnline, isDownloading, isDownloaded]);
|
||||||
|
|
||||||
|
const renderActionButton = () => {
|
||||||
|
if (isCurrentVersionInstalled || launchPath) {
|
||||||
|
return (
|
||||||
|
<button className="action-button installed-button" disabled>
|
||||||
|
<FaRocket /> Installed
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInstalling || isCheckingLaunch) {
|
||||||
|
return (
|
||||||
|
<button className="action-button installing-button" disabled>
|
||||||
|
<FaSpinner className="fa-spin" /> Installing...
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloaded) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
||||||
|
if (versionData) {
|
||||||
|
handleInstall(versionData.version, versionData.hash);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="action-button install-button"
|
||||||
|
>
|
||||||
|
<FaRocket /> Install
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={!canDownload}
|
||||||
|
className="action-button download-button"
|
||||||
|
>
|
||||||
|
{isDownloading ? (
|
||||||
|
<>
|
||||||
|
<FaSpinner className="fa-spin" /> Downloading... {downloadProgress}%
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FaDownload /> Download
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return <div className="downloads-page"><h4>Loading app details...</h4></div>;
|
return <div className="downloads-page"><h4>Loading app details...</h4></div>;
|
||||||
}
|
}
|
||||||
@ -176,15 +259,22 @@ export default function DownloadPage() {
|
|||||||
<p className="app-id">{`${app.package_id.package_name}.${app.package_id.publisher_node}`}</p>
|
<p className="app-id">{`${app.package_id.package_name}.${app.package_id.publisher_node}`}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{installedApp && (
|
{launchPath ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleLaunch}
|
onClick={handleLaunch}
|
||||||
className="launch-button"
|
className="launch-button"
|
||||||
disabled={!canLaunch}
|
|
||||||
>
|
>
|
||||||
<FaPlay /> {canLaunch ? 'Launch' : 'No UI found for app'}
|
<FaPlay /> Launch
|
||||||
</button>
|
</button>
|
||||||
)}
|
) : isInstalling || isCheckingLaunch ? (
|
||||||
|
<button className="launch-button" disabled>
|
||||||
|
<FaSpinner className="fa-spin" /> Checking...
|
||||||
|
</button>
|
||||||
|
) : installedApp ? (
|
||||||
|
<button className="launch-button" disabled>
|
||||||
|
No UI found for app
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className="app-description">{app.metadata?.description}</p>
|
<p className="app-description">{app.metadata?.description}</p>
|
||||||
|
|
||||||
@ -207,39 +297,7 @@ export default function DownloadPage() {
|
|||||||
onMirrorSelect={handleMirrorSelect}
|
onMirrorSelect={handleMirrorSelect}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isCurrentVersionInstalled ? (
|
{renderActionButton()}
|
||||||
<button className="action-button installed-button" disabled>
|
|
||||||
<FaRocket /> Installed
|
|
||||||
</button>
|
|
||||||
) : isDownloaded ? (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const versionData = sortedVersions.find(v => v.version === selectedVersion);
|
|
||||||
if (versionData) {
|
|
||||||
handleInstall(versionData.version, versionData.hash);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="action-button install-button"
|
|
||||||
>
|
|
||||||
<FaRocket /> Install
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={handleDownload}
|
|
||||||
disabled={!canDownload}
|
|
||||||
className="action-button download-button"
|
|
||||||
>
|
|
||||||
{isDownloading ? (
|
|
||||||
<>
|
|
||||||
<FaSpinner className="fa-spin" /> Downloading... {downloadProgress}%
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<FaDownload /> Download
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="my-downloads">
|
<div className="my-downloads">
|
||||||
|
@ -12,7 +12,7 @@ const NAME_INVALID = "Package name must contain only valid characters (a-z, 0-9,
|
|||||||
|
|
||||||
export default function PublishPage() {
|
export default function PublishPage() {
|
||||||
const { openConnectModal } = useConnectModal();
|
const { openConnectModal } = useConnectModal();
|
||||||
const { ourApps, fetchOurApps, installed, downloads } = useAppsStore();
|
const { ourApps, fetchOurApps, downloads } = useAppsStore();
|
||||||
const publicClient = usePublicClient();
|
const publicClient = usePublicClient();
|
||||||
|
|
||||||
const { address, isConnected, isConnecting } = useAccount();
|
const { address, isConnected, isConnecting } = useAccount();
|
||||||
|
@ -218,12 +218,6 @@ const useAppsStore = create<AppsStore>()((set, get) => ({
|
|||||||
});
|
});
|
||||||
if (res.status === HTTP_STATUS.CREATED) {
|
if (res.status === HTTP_STATUS.CREATED) {
|
||||||
await get().fetchInstalled();
|
await get().fetchInstalled();
|
||||||
|
|
||||||
// hacky: a small delay (500ms) before fetching homepage apps
|
|
||||||
// to give the app time to add itself to the homepage
|
|
||||||
// might make sense to add more state and do retry logic instead.
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
await get().fetchHomepageApps();
|
await get().fetchHomepageApps();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user