Merge pull request #345 from kinode-dao/tm/appstore-flows

repair some missing appstore flows
This commit is contained in:
doria 2024-05-15 12:16:25 +09:00 committed by GitHub
commit 1bb7b5a86f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 198 additions and 144 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,8 +14,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/main:app_store:sys/assets/index-BxGs27ah.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-umttRNrr.css">
<script type="module" crossorigin src="/main:app_store:sys/assets/index-34EVhDFF.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-cJEV35Fc.css">
</head>
<body>

View File

@ -5,6 +5,7 @@ import DownloadButton from "./DownloadButton";
import InstallButton from "./InstallButton";
import LaunchButton from "./LaunchButton";
import { FaCheck } from "react-icons/fa6";
import classNames from "classnames";
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
@ -12,7 +13,6 @@ interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
}
export default function ActionButton({ app, isIcon = false, ...props }: ActionButtonProps) {
const [incrementNumber, setIncrementNumber] = useState(0);
const { installed, downloaded, updatable } = useMemo(() => {
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
@ -29,7 +29,7 @@ export default function ActionButton({ app, isIcon = false, ...props }: ActionBu
downloaded,
updatable,
};
}, [app, incrementNumber]);
}, [app]);
const [launchPath, setLaunchPath] = useState('');
@ -44,25 +44,33 @@ export default function ActionButton({ app, isIcon = false, ...props }: ActionBu
}
}
})
}, [app, incrementNumber])
}, [app])
return (
<>
{(installed && launchPath)
? <LaunchButton app={app} {...props} isIcon={isIcon} launchPath={launchPath} />
: (installed && updatable)
? <UpdateButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
? <UpdateButton app={app} {...props} isIcon={isIcon} />
: !downloaded
? <DownloadButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
? <DownloadButton app={app} {...props} isIcon={isIcon} />
: !installed
? <InstallButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
? <InstallButton app={app} {...props} isIcon={isIcon} />
: isIcon
? <button
className="pointer-events none icon clear absolute top-0 right-0"
>
<FaCheck />
</button>
: <div>Installed</div>}
: <></>
// <button
// onClick={() => { }}
// {...props as any}
// className={classNames("clear pointer-events-none", props.className)}
// >
// Installed
// </button>
}
</>
);
}

View File

@ -8,14 +8,16 @@ import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { APP_DETAILS_PATH } from "../constants/path";
import MoreActions from "./MoreActions";
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large";
showMoreActions?: boolean;
}
export default function AppEntry({ app, size = "medium", overrideImageSize, ...props }: AppEntryProps) {
export default function AppEntry({ app, size = "medium", overrideImageSize, showMoreActions, ...props }: AppEntryProps) {
const isMobile = isMobileCheck()
const navigate = useNavigate()
@ -27,17 +29,26 @@ export default function AppEntry({ app, size = "medium", overrideImageSize, ...p
'flex-wrap gap-2': isMobile,
'flex-col relative': size !== 'large'
})}
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
onClick={() => {
if (!showMoreActions) {
navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)
}
}}
>
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
<ActionButton
app={app}
isIcon={size !== 'large'}
isIcon={!showMoreActions && size !== 'large'}
className={classNames({
'absolute top-0 right-0': size !== 'large',
'absolute': size !== 'large',
'top-2 right-2': size !== 'large' && showMoreActions,
'top-0 right-0': size !== 'large' && !showMoreActions,
'bg-orange text-lg min-w-1/5': size === 'large',
'ml-auto': size === 'large' && isMobile
})} />
{showMoreActions && <div className="absolute bottom-2 right-2">
<MoreActions app={app} />
</div>}
</div>
);
}

View File

@ -47,15 +47,15 @@ export default function AppHeader({
alt="app icon"
className={classNames('object-cover', {
'rounded': !imageSize,
'rounded-lg': imageSize === 'small',
'rounded-xl': imageSize === 'medium',
'rounded-md': imageSize === 'small',
'rounded-lg': imageSize === 'medium',
'rounded-2xl': imageSize === 'large',
'h-32': imageSize === 'large' || imageSize === 'small',
'h-20': imageSize === 'medium',
})}
/>
: <ColorDot
num={app.metadata_hash}
num={app.metadata_hash || app.state?.our_version?.toString() || ''}
dotSize={imageSize}
/>}
<div className={classNames("flex flex-col", {

View File

@ -15,7 +15,7 @@ const ColorDot: React.FC<ColorDotProps> = ({
}) => {
const isMobile = isMobileCheck()
num = (num || '').replace(/(0x|\.)/g, '')
num = num ? num : '';
while (num.length < 6) {
num = '0' + num

View File

@ -5,17 +5,15 @@ import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { FaDownload } from "react-icons/fa6";
interface DownloadButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function DownloadButton({ app, isIcon = false, callback, ...props }: DownloadButtonProps) {
const { downloadApp, getCaps, getMyApp } =
export default function DownloadButton({ app, isIcon = false, ...props }: DownloadButtonProps) {
const { downloadApp, getCaps, getMyApp, getMyApps } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
@ -50,7 +48,7 @@ export default function DownloadButton({ app, isIcon = false, callback, ...props
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
getMyApps();
})
.catch(console.log);
}, 2000);

View File

@ -12,6 +12,7 @@ export default function Dropdown({ ...props }: DropdownProps) {
{...props}
unmountOnClose={true}
className={classNames("relative", props.className)}
direction='left'
menuButton={<MenuButton className="small">
<FaEllipsisH className='-mb-1' />
</MenuButton>}

View File

@ -10,11 +10,10 @@ import { FaI } from "react-icons/fa6";
interface InstallButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function InstallButton({ app, isIcon = false, callback, ...props }: InstallButtonProps) {
const { installApp, getCaps, getMyApp } =
export default function InstallButton({ app, isIcon = false, ...props }: InstallButtonProps) {
const { installApp, getCaps, getMyApp, getMyApps } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
@ -40,7 +39,7 @@ export default function InstallButton({ app, isIcon = false, callback, ...props
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
getMyApps();
})
.catch(console.log);
}, 2000);

View File

@ -23,7 +23,7 @@ export default function MoreActions({ app, className }: MoreActionsProps) {
return (
<Dropdown className={className}>
<div className="flex flex-col backdrop-blur-lg p-2 rounded-lg relative z-10">
<div className="flex flex-col backdrop-blur-lg bg-black/10 p-2 rounded-lg relative z-10">
{app.metadata?.description && (
<button
className="my-1 whitespace-nowrap clear"

View File

@ -9,11 +9,10 @@ import classNames from "classnames";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void
}
export default function UpdateButton({ app, callback, isIcon = false, ...props }: UpdateButtonProps) {
const { updateApp, getCaps, getMyApp } =
export default function UpdateButton({ app, isIcon = false, ...props }: UpdateButtonProps) {
const { updateApp, getCaps, getMyApp, getMyApps } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
@ -40,7 +39,7 @@ export default function UpdateButton({ app, callback, isIcon = false, ...props }
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
getMyApps();
})
.catch(console.log);
}, 2000);

View File

@ -37,7 +37,7 @@ export default function AppPage() {
.catch(console.error);
}
}
}, [params.id]);
}, [params.id, myApps, listedApps]);
const goToPublish = useCallback(() => {
navigate(PUBLISH_PATH, { state: { app } });
@ -92,9 +92,9 @@ export default function AppPage() {
]
return (
<div className={classNames("flex flex-col w-full h-screen",
<div className={classNames("flex flex-col w-full p-2",
{
'gap-4 p-2 max-w-screen': isMobile,
'gap-4 max-w-screen': isMobile,
'gap-8 max-w-[900px]': !isMobile,
})}
>

View File

@ -10,9 +10,11 @@ import { useNavigate } from "react-router-dom";
import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames";
export default function MyAppsPage() { // eslint-disable-line
const { myApps, getMyApps } = useAppsStore()
const { myApps, getMyApps, } = useAppsStore()
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState<string>("");
@ -52,8 +54,15 @@ export default function MyAppsPage() { // eslint-disable-line
}
}, [myApps]);
const isMobile = isMobileCheck()
console.log({ myApps })
return (
<div className="flex flex-col w-full max-w-[900px]">
<div className={classNames("flex flex-col w-full h-screen p-2",
{
'gap-4 max-w-screen': isMobile,
'gap-8 max-w-[900px]': !isMobile,
})}>
<HomeButton />
<SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="flex justify-between items-center mt-2">
@ -64,15 +73,39 @@ export default function MyAppsPage() { // eslint-disable-line
</button>
</div>
<div className="flex flex-col card gap-2 mt-2">
<h4>Downloaded</h4>
{(displayedApps.downloaded || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Installed</h4>
{(displayedApps.installed || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Local</h4>
{(displayedApps.local || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>System</h4>
{(displayedApps.system || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<div className={classNames("flex flex-col card gap-2 mt-2",
{
'max-h-[80vh] overflow-y-scroll overflow-x-visible': !isMobile,
})}
style={{
scrollbarWidth: 'thin',
scrollbarColor: '#FFF5D9 transparent',
}}
>
{displayedApps.downloaded.length > 0 && <h4>Downloaded</h4>}
{(displayedApps.downloaded || []).map((app) => <AppEntry
key={appId(app)}
app={app}
showMoreActions
/>)}
{displayedApps.installed.length > 0 && <h4>Installed</h4>}
{(displayedApps.installed || []).map((app) => <AppEntry
key={appId(app)}
app={app}
showMoreActions
/>)}
{displayedApps.local.length > 0 && <h4>Local</h4>}
{(displayedApps.local || []).map((app) => <AppEntry
key={appId(app)}
app={app}
showMoreActions
/>)}
{displayedApps.system.length > 0 && <h4>System</h4>}
{(displayedApps.system || []).map((app) => <AppEntry
key={appId(app)}
app={app}
showMoreActions
/>)}
</div>
</div>
);

View File

@ -126,8 +126,8 @@ export default function StorePage() {
const isMobile = isMobileCheck()
return (
<div className={classNames("flex flex-col w-full max-h-screen", {
'gap-4 max-w-screen p-2': isMobile,
<div className={classNames("flex flex-col w-full max-h-screen p-2", {
'gap-4 max-w-screen': isMobile,
'gap-6 max-w-[900px]': !isMobile
})}>
{!isMobile && <HomeButton />}
@ -173,7 +173,8 @@ export default function StorePage() {
</select>
</div>
{!searchQuery ? <div className={classNames("flex flex-col", {
'grow overflow-y-auto gap-4 items-center px-2': isMobile
'gap-4': !isMobile,
'grow overflow-y-auto gap-2 items-center px-2': isMobile
})}>
<h2>Top apps this week...</h2>
<div className={classNames("flex gap-2", {
@ -195,7 +196,7 @@ export default function StorePage() {
<div className={classNames("flex gap-2", {
'flex-col': isMobile
})}>
{displayedApps.slice(0, 6).map((app) => (
{displayedApps.slice(0, 5).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size={isMobile ? 'medium' : 'small'}

View File

@ -47,12 +47,16 @@ const useAppsStore = create<AppsStore>()(
searchResults: [] as AppInfo[],
query: '',
getMyApps: async () => {
const listedApps = await get().getListedApps()
const res = await fetch(`${BASE_URL}/apps`)
const apps = await res.json() as AppInfo[]
const myApps = apps.reduce((acc, app) => {
const appType = getAppType(app)
if (listedApps.find(lapp => lapp.metadata_hash === app.metadata_hash)) {
console.log({ listedappmatch: app })
}
acc[appType].push(app)
return acc
}, {