From 74e769ba3be76b53a9f7f3f5620d78d4b1e014b8 Mon Sep 17 00:00:00 2001 From: bitful-pannul Date: Wed, 18 Sep 2024 16:38:46 +0300 Subject: [PATCH 1/2] app_store UI: fix launch buttons --- .../app_store/ui/src/pages/AppPage.tsx | 34 ++++++++++++++----- .../app_store/ui/src/pages/DownloadPage.tsx | 25 +++++++++++--- .../packages/app_store/ui/src/store/index.ts | 28 ++++++++++++++- .../packages/app_store/ui/src/types/Apps.ts | 13 +++++++ 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/kinode/packages/app_store/ui/src/pages/AppPage.tsx b/kinode/packages/app_store/ui/src/pages/AppPage.tsx index b7e6a9bc..44ce3f32 100644 --- a/kinode/packages/app_store/ui/src/pages/AppPage.tsx +++ b/kinode/packages/app_store/ui/src/pages/AppPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback, useMemo } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { FaDownload, FaCheck, FaTimes, FaPlay, FaSpinner, FaTrash, FaSync } from "react-icons/fa"; import useAppsStore from "../store"; @@ -8,7 +8,7 @@ import { compareVersions } from "../utils/compareVersions"; export default function AppPage() { const { id } = useParams(); const navigate = useNavigate(); - const { fetchListing, fetchInstalledApp, uninstallApp, setAutoUpdate } = useAppsStore(); + const { fetchListing, fetchInstalledApp, uninstallApp, setAutoUpdate, getLaunchUrl, fetchHomepageApps } = useAppsStore(); const [app, setApp] = useState(null); const [installedApp, setInstalledApp] = useState(null); const [currentVersion, setCurrentVersion] = useState(null); @@ -57,6 +57,21 @@ export default function AppPage() { } }, [id, fetchListing, fetchInstalledApp]); + + const handleLaunch = useCallback(() => { + if (app) { + const launchUrl = getLaunchUrl(`${app.package_id.package_name}:${app.package_id.publisher_node}`); + if (launchUrl) { + window.location.href = launchUrl; + } + } + }, [app, getLaunchUrl]); + + const canLaunch = useMemo(() => { + if (!app) return false; + return !!getLaunchUrl(`${app.package_id.package_name}:${app.package_id.publisher_node}`); + }, [app, getLaunchUrl]); + const handleUninstall = async () => { if (!app) return; setIsUninstalling(true); @@ -88,16 +103,13 @@ export default function AppPage() { useEffect(() => { loadData(); - }, [loadData]); + fetchHomepageApps(); + }, [loadData, fetchHomepageApps]); const handleDownload = () => { navigate(`/download/${id}`); }; - const handleLaunch = () => { - window.location.href = `/${app?.package_id.package_name}:${app?.package_id.package_name}:${app?.package_id.publisher_node}/`; - }; - if (isLoading) { return

Loading app details...

; } @@ -150,8 +162,12 @@ export default function AppPage() {
{installedApp && ( <> - )}
diff --git a/kinode/packages/app_store/ui/src/store/index.ts b/kinode/packages/app_store/ui/src/store/index.ts index 350ef82c..8fdf1ca8 100644 --- a/kinode/packages/app_store/ui/src/store/index.ts +++ b/kinode/packages/app_store/ui/src/store/index.ts @@ -1,6 +1,6 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' -import { PackageState, AppListing, MirrorCheckFile, PackageManifest, DownloadItem } from '../types/Apps' +import { PackageState, AppListing, MirrorCheckFile, PackageManifest, DownloadItem, HomepageApp } from '../types/Apps' import { HTTP_STATUS } from '../constants/http' import KinodeClientApi from "@kinode/client-api" import { WEBSOCKET_URL } from '../utils/ws' @@ -13,6 +13,7 @@ interface AppsStore { downloads: Record ourApps: AppListing[] ws: KinodeClientApi + homepageApps: HomepageApp[] activeDownloads: Record fetchData: (id: string) => Promise @@ -25,6 +26,9 @@ interface AppsStore { fetchDownloadsForApp: (id: string) => Promise checkMirror: (node: string) => Promise + fetchHomepageApps: () => Promise + getLaunchUrl: (id: string) => string | null + installApp: (id: string, version_hash: string) => Promise uninstallApp: (id: string) => Promise downloadApp: (id: string, version_hash: string, downloadFrom: string) => Promise @@ -49,6 +53,8 @@ const useAppsStore = create()( downloads: {}, ourApps: [], activeDownloads: {}, + homepageApps: [], + fetchData: async (id: string) => { if (!id) return; @@ -174,6 +180,26 @@ const useAppsStore = create()( return []; }, + fetchHomepageApps: async () => { + try { + const res = await fetch('/apps'); + if (res.status === HTTP_STATUS.OK) { + const data: HomepageApp[] = await res.json(); + set({ homepageApps: data }); + } + } catch (error) { + console.error("Error fetching homepage apps:", error); + } + }, + + getLaunchUrl: (id: string) => { + const app = get().homepageApps.find(app => `${app.package}:${app.publisher}` === id); + if (app && app.path) { + return app.path; + } + return null; + }, + checkMirror: async (node: string) => { try { const res = await fetch(`${BASE_URL}/mirrorcheck/${node}`); diff --git a/kinode/packages/app_store/ui/src/types/Apps.ts b/kinode/packages/app_store/ui/src/types/Apps.ts index a9698d07..a7f5b0f0 100644 --- a/kinode/packages/app_store/ui/src/types/Apps.ts +++ b/kinode/packages/app_store/ui/src/types/Apps.ts @@ -70,3 +70,16 @@ export interface PackageManifest { grant_capabilities: any[] public: boolean } + +export interface HomepageApp { + id: string; + process: string; + package: string; + publisher: string; + path?: string; + label: string; + base64_icon?: string; + widget?: string; + order: number; + favorite: boolean; +} From 6959939665af6feaaaf2d20d5e3949e5808a1593 Mon Sep 17 00:00:00 2001 From: bitful-pannul Date: Wed, 18 Sep 2024 17:30:11 +0300 Subject: [PATCH 2/2] app_store UI: small adjustments, simplify css --- kinode/packages/app_store/ui/src/index.css | 356 ++++-------------- .../app_store/ui/src/pages/DownloadPage.tsx | 35 +- 2 files changed, 102 insertions(+), 289 deletions(-) diff --git a/kinode/packages/app_store/ui/src/index.css b/kinode/packages/app_store/ui/src/index.css index 8005aefc..8ef50f76 100644 --- a/kinode/packages/app_store/ui/src/index.css +++ b/kinode/packages/app_store/ui/src/index.css @@ -83,6 +83,7 @@ form { .form-group { display: flex; flex-direction: column; + margin-bottom: 1rem; } label { @@ -94,6 +95,8 @@ select { padding: 0.5rem; border: 1px solid var(--gray); border-radius: var(--border-radius); + background-color: light-dark(var(--white), var(--tasteful-dark)); + color: light-dark(var(--off-black), var(--off-white)); } /* Buttons */ @@ -201,6 +204,12 @@ td { flex-direction: column; } +.app-title-container { + display: flex; + align-items: center; + gap: 1rem; +} + .app-id { font-size: 0.9rem; color: var(--gray); @@ -228,277 +237,124 @@ td { margin-top: 0.25rem; } -.form-group { - margin-bottom: 1rem; -} - -/* App Page */ -.app-page { - background-color: light-dark(var(--off-white), var(--off-black)); - border-radius: var(--border-radius); - padding: 1rem; - margin-bottom: 1rem; -} - -.app-description { - margin-bottom: 1rem; -} - -.app-info { - background-color: light-dark(var(--tan), var(--tasteful-dark)); - border-radius: var(--border-radius); - padding: 1rem; - margin-bottom: 1rem; -} - -.app-actions { - display: flex; - gap: 1rem; - margin-bottom: 1rem; -} - -.app-screenshots { - margin-top: 1rem; -} - -.screenshot-container { - display: flex; - gap: 1rem; - overflow-x: auto; -} - -.app-screenshot { - max-width: 200px; - height: auto; -} - -/* Store Page */ -.store-page { - background-color: light-dark(var(--off-white), var(--off-black)); - border-radius: var(--border-radius); - padding: 1rem; -} - -.store-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; -} - -.app-list { - background-color: light-dark(var(--tan), var(--tasteful-dark)); - border-radius: var(--border-radius); - padding: 1rem; -} - -/* Publish Page */ -.publish-page { - background-color: light-dark(var(--off-white), var(--off-black)); - border-radius: var(--border-radius); - padding: 1rem; -} - -.publish-form { - max-width: 500px; -} - -.my-packages { - margin-top: 2rem; -} - -.my-packages ul { - list-style-type: none; - padding: 0; -} - -.my-packages li { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.5rem 0; - border-bottom: 1px solid var(--gray); -} - -/* Download Page */ +/* App Page and Download Page shared styles */ +.app-page, .downloads-page { background-color: light-dark(var(--white), var(--maroon)); - border-radius: 8px; + border-radius: var(--border-radius); padding: 2rem; - margin-bottom: 2rem; + width: 100%; } .app-header { display: flex; align-items: center; gap: 1rem; - margin-bottom: 1rem; + margin-bottom: 1.5rem; } -.app-header h2 { - margin: 0; +.app-description { + margin-bottom: 2rem; + line-height: 1.6; } -.launch-button { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - font-size: 14px; - background-color: var(--orange); - color: var(--white); - border: none; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; -} - -.launch-button:hover { - background-color: var(--dark-orange); -} - -.version-selector { - margin-bottom: 1rem; -} - -.version-selector select { - width: 100%; - padding: 0.75rem; - border: 2px solid var(--orange); - border-radius: 4px; - background-color: light-dark(var(--white), var(--tasteful-dark)); - color: light-dark(var(--off-black), var(--off-white)); - transition: all 0.3s ease; -} - -.version-selector select:focus { - outline: none; - border-color: var(--dark-orange); - box-shadow: 0 0 0 3px rgba(255, 79, 0, 0.2); +.app-info { + background-color: light-dark(var(--tan), var(--tasteful-dark)); + border-radius: var(--border-radius); + padding: 1.5rem; + margin-bottom: 2rem; } +/* Download Page specific styles */ .download-section { display: flex; flex-direction: column; gap: 1rem; - margin-bottom: 1rem; + margin-bottom: 2rem; + max-width: 20rem; } -.download-button, -.install-button, -.installed-button { +.version-selector, +.mirror-selector select { + width: 100%; + padding: 0.5em; + border: 1px solid var(--gray); + border-radius: var(--border-radius); + background-color: light-dark(var(--white), var(--tasteful-dark)); + color: light-dark(var(--off-black), var(--off-white)); +} + +/* Action Buttons */ +.action-button, +.primary, +.secondary { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; - padding: 0.5rem 1rem; - font-size: 16px; - font-weight: bold; + padding: 0.75em 1em; + font-size: 1rem; border: none; - border-radius: 4px; + border-radius: var(--border-radius); cursor: pointer; - transition: background-color 0.3s ease; + transition: background-color 0.3s ease, color 0.3s ease; } -.download-button { +.primary { background-color: var(--orange); color: var(--white); } -.download-button:hover:not(:disabled) { +.primary:hover:not(:disabled) { background-color: var(--dark-orange); -} - -.install-button { - background-color: var(--blue); color: var(--white); } -.install-button:hover { - background-color: color-mix(in srgb, var(--blue) 80%, black); +.secondary { + background-color: light-dark(var(--off-white), var(--off-black)); + color: var(--orange); + border: 2px solid var(--orange); } -.installed-button { - background-color: var(--gray); +.secondary:hover:not(:disabled) { + background-color: var(--orange); color: var(--white); - cursor: not-allowed; } -.download-button:disabled, -.install-button:disabled, -.installed-button:disabled { +.action-button:disabled, +.primary:disabled, +.secondary:disabled { opacity: 0.5; cursor: not-allowed; } -.my-downloads { - margin-top: 1rem; -} - -.my-downloads>button { +/* App actions */ +.app-actions { display: flex; - align-items: center; - gap: 0.5rem; - background-color: transparent; - color: var(--orange); - border: 2px solid var(--orange); - padding: 0.5rem 1rem; - cursor: pointer; - transition: background-color 0.3s ease, color 0.3s ease; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 2rem; } -.my-downloads>button:hover { - background-color: var(--orange); - color: var(--white); +/* Screenshots */ +.app-screenshots { + margin-top: 2rem; } -.my-downloads table { - width: 100%; - border-collapse: collapse; - margin-top: 1rem; -} - -.my-downloads th, -.my-downloads td { - padding: 0.5rem; - text-align: left; - border-bottom: 1px solid var(--gray); -} - -.my-downloads td button { - margin-right: 0.5rem; -} - -.app-details { - margin-top: 1rem; -} - -.app-details>button { +.screenshot-container { display: flex; - align-items: center; - gap: 0.5rem; - background-color: transparent; - color: var(--orange); - border: 2px solid var(--orange); - padding: 0.5rem 1rem; - cursor: pointer; - transition: background-color 0.3s ease, color 0.3s ease; -} - -.app-details>button:hover { - background-color: var(--orange); - color: var(--white); -} - -.app-details pre { - background-color: light-dark(var(--tan), var(--tasteful-dark)); - color: light-dark(var(--off-black), var(--off-white)); - padding: 1rem; - border-radius: 4px; + gap: 1rem; overflow-x: auto; - margin-top: 1rem; + padding-bottom: 1rem; } +.app-screenshot { + max-width: 200px; + height: auto; + border-radius: var(--border-radius); +} + +/* Capabilities approval popup */ .cap-approval-popup { position: fixed; top: 0; @@ -538,73 +394,23 @@ td { margin-top: 1rem; } -/* My Downloads Page */ -.my-downloads-page { - background-color: light-dark(var(--off-white), var(--off-black)); - border-radius: var(--border-radius); - padding: 1rem; -} +/* Responsive adjustments */ +@media (max-width: 48em) { + + .app-page, + .downloads-page { + padding: 1.5rem; + } -/* Responsive Design */ -@media (max-width: 768px) { .app-actions { flex-direction: column; } - .my-packages li { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; + .download-section { + max-width: 100%; } } -.cap-approval-popup { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.cap-approval-content { - background-color: white; - padding: 20px; - border-radius: 8px; - max-width: 80%; - max-height: 80%; - overflow-y: auto; -} - -.download-progress { - display: flex; - align-items: center; - gap: 10px; -} - -.progress-bar { - width: 100px; - height: 10px; - background-color: #e0e0e0; - border-radius: 5px; - overflow: hidden; -} - -.progress-bar-fill { - height: 100%; - background-color: #4caf50; - transition: width 0.3s ease-in-out; -} - -.progress-text { - min-width: 50px; - text-align: right; -} - @keyframes spin { 0% { transform: rotate(0deg); diff --git a/kinode/packages/app_store/ui/src/pages/DownloadPage.tsx b/kinode/packages/app_store/ui/src/pages/DownloadPage.tsx index 9bd263d9..cd90c6be 100644 --- a/kinode/packages/app_store/ui/src/pages/DownloadPage.tsx +++ b/kinode/packages/app_store/ui/src/pages/DownloadPage.tsx @@ -167,7 +167,15 @@ export default function DownloadPage() { return (
-

{app.metadata?.name || app.package_id.package_name}

+
+ {app.metadata?.image && ( + {app.metadata?.name + )} +
+

{app.metadata?.name || app.package_id.package_name}

+

{`${app.package_id.package_name}.${app.package_id.publisher_node}`}

+
+
{installedApp && (
-

{app.metadata?.description}

+

{app.metadata?.description}

-
+
-
-
+ {isCurrentVersionInstalled ? ( - ) : isDownloaded ? ( @@ -211,7 +219,7 @@ export default function DownloadPage() { handleInstall(versionData.version, versionData.hash); } }} - className="install-button" + className="action-button install-button" > Install @@ -219,12 +227,11 @@ export default function DownloadPage() {