Merge pull request #418 from kinode-dao/tm/app-store-mobile-ui

app store mobile ui
This commit is contained in:
doria 2024-06-21 23:00:14 +09:00 committed by GitHub
commit cf1811fa18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 109 additions and 89 deletions

View File

@ -60,6 +60,7 @@ pub fn init_frontend(our: &Address) {
fn make_widget() -> String { fn make_widget() -> String {
return r#"<html> return r#"<html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -84,7 +85,7 @@ fn make_widget() -> String {
gap: 0.5rem; gap: 0.5rem;
align-items: center; align-items: center;
backdrop-filter: saturate(1.25); backdrop-filter: saturate(1.25);
border-radius: 0.75rem; border-radius: 1rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
@ -98,7 +99,7 @@ fn make_widget() -> String {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
align-items: stretch; align-items: stretch;
border-radius: 0.75rem; border-radius: 0.5rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
cursor: pointer; cursor: pointer;

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 httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> 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-I7H8IZID.js"></script> <script type="module" crossorigin src="/main:app_store:sys/assets/index-B8jjW9yS.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-fGthT1qI.css"> <link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-zU7UyELC.css">
</head> </head>
<body> <body>

View File

@ -37,12 +37,13 @@ export default function AppEntry({ app, size = "medium", overrideImageSize, show
}} }}
> >
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} /> <AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
<div className={classNames("flex items-center", { <div className={classNames("flex", {
'items-center': size !== 'large',
'items-start': size === 'large',
'absolute': size !== 'large', 'absolute': size !== 'large',
'top-2 right-2': size !== 'large' && showMoreActions, 'top-2 right-2': size !== 'large' && showMoreActions,
'top-0 right-0': size !== 'large' && !showMoreActions, 'top-0 right-0': size !== 'large' && !showMoreActions,
'ml-auto': size === 'large' && isMobile, 'ml-auto': size === 'large' && isMobile,
'min-w-1/5': size === 'large'
})}> })}>
<ActionButton <ActionButton
app={app} app={app}
@ -54,7 +55,13 @@ export default function AppEntry({ app, size = "medium", overrideImageSize, show
'w-full': size === 'large' 'w-full': size === 'large'
})} })}
/> />
{showMoreActions && <MoreActions app={app} className="self-stretch" />} {showMoreActions && <MoreActions
app={app}
className={classNames("self-stretch", {
'self-start': size === 'large',
})}
/>}
</div> </div>
</div> </div>
); );

View File

@ -13,8 +13,8 @@ export default function Dropdown({ ...props }: DropdownProps) {
unmountOnClose={true} unmountOnClose={true}
className={classNames("relative", props.className)} className={classNames("relative", props.className)}
direction='left' direction='left'
menuButton={<MenuButton className="small"> menuButton={<MenuButton>
<FaEllipsisH className='-mb-1' /> <FaEllipsisH className='mb-[3px]' />
</MenuButton>} </MenuButton>}
> >
{props.children} {props.children}

View File

@ -1,6 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import React, { MouseEvent } from 'react' import React, { MouseEvent } from 'react'
import { FaX } from 'react-icons/fa6' import { FaX } from 'react-icons/fa6'
import { isMobileCheck } from '../utils/dimensions'
export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> { export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
show: boolean show: boolean
@ -25,10 +26,17 @@ const Modal: React.FC<ModalProps> = ({
return null return null
} }
const isMobile = isMobileCheck()
return ( return (
<div <div
className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`, className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] isMobile-${isMobile}`,
{ show } {
isMobile,
show,
'min-w-[30em]': !isMobile,
'min-w-[75vw]': isMobile,
}
)} )}
onClick={hide} onClick={hide}
> >

View File

@ -17,13 +17,15 @@ export default function MoreActions({ app, className }: MoreActionsProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const downloaded = Boolean(app.state); const downloaded = Boolean(app.state);
const menuClass = "flex flex-col bg-black p-2 rounded-lg relative z-10 border border-orange -mr-[1px]"
if (!downloaded) { if (!downloaded) {
if (!app.metadata) return <></>; if (!app.metadata) return <></>;
return ( return (
<Dropdown className={className}> <Dropdown className={className}>
<div className="flex flex-col backdrop-blur-lg bg-black/10 p-2 rounded-lg relative z-10"> <div className={menuClass}>
{app.metadata?.description && ( {app.metadata?.description && (
<button <button
className="my-1 whitespace-nowrap clear" className="my-1 whitespace-nowrap clear"
@ -48,7 +50,7 @@ export default function MoreActions({ app, className }: MoreActionsProps) {
return ( return (
<Dropdown className={className}> <Dropdown className={className}>
<div className="flex flex-col p-2 rounded-lg backdrop-blur-lg relative z-10"> <div className={menuClass}>
<button <button
className="my-1 whitespace-nowrap clear" className="my-1 whitespace-nowrap clear"
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)} onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}

View File

@ -99,8 +99,8 @@ export default function SearchHeader({
})} })}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))} onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
> >
{!isMobile && <span>My Apps</span>} <span>My Apps</span>
<FaDownload /> {!isMobile && <FaDownload />}
</button> </button>
</div> </div>
); );

View File

@ -5,7 +5,7 @@ import Modal from "./Modal";
import { getAppName } from "../utils/app"; import { getAppName } from "../utils/app";
import Loader from "./Loader"; import Loader from "./Loader";
import classNames from "classnames"; import classNames from "classnames";
import { FaU } from "react-icons/fa6"; import { FaExclamation } from "react-icons/fa6";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> { interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo; app: AppInfo;
@ -61,7 +61,7 @@ export default function UpdateButton({ app, isIcon = false, ...props }: UpdateBu
})} })}
onClick={onClick} onClick={onClick}
> >
{isIcon ? <FaU /> : 'Update'} {isIcon ? <FaExclamation /> : 'Update'}
</button> </button>
<Modal show={showModal} hide={() => setShowModal(false)}> <Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? ( {loading ? (

View File

@ -63,11 +63,11 @@ export default function MyAppsPage() { // eslint-disable-line
'gap-4 max-w-screen': isMobile, 'gap-4 max-w-screen': isMobile,
'gap-8 max-w-[900px]': !isMobile, 'gap-8 max-w-[900px]': !isMobile,
})}> })}>
<HomeButton /> {!isMobile && <HomeButton />}
<SearchHeader value={searchQuery} onChange={searchMyApps} /> <SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="flex justify-between items-center mt-2"> <div className="flex justify-between items-center mt-2">
<h3>My Packages</h3> <h3>My Packages</h3>
<button onClick={() => navigate(PUBLISH_PATH)}> <button className="alt" onClick={() => navigate(PUBLISH_PATH)}>
<FaUpload className="mr-2" /> <FaUpload className="mr-2" />
Publish Package Publish Package
</button> </button>

View File

@ -11,6 +11,8 @@ import classNames from 'classnames';
import { FaArrowRotateRight } from "react-icons/fa6"; import { FaArrowRotateRight } from "react-icons/fa6";
import { isMobileCheck } from "../utils/dimensions"; import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "../components/HomeButton"; import HomeButton from "../components/HomeButton";
import Modal from "../components/Modal";
import Loader from "../components/Loader";
interface StorePageProps extends PageProps { } interface StorePageProps extends PageProps { }
@ -24,6 +26,7 @@ export default function StorePage() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [tags, setTags] = useState<string[]>([]) const [tags, setTags] = useState<string[]>([])
const [launchPaths, setLaunchPaths] = useState<{ [package_name: string]: string }>({}) const [launchPaths, setLaunchPaths] = useState<{ [package_name: string]: string }>({})
const [isRebuildingIndex, setIsRebuildingIndex] = useState(false);
const pages = useMemo( const pages = useMemo(
() => () =>
@ -97,12 +100,18 @@ export default function StorePage() {
); );
const tryRebuildIndex = useCallback(async () => { const tryRebuildIndex = useCallback(async () => {
if (!window.confirm('Are you sure you want to rebuild the app index? This may take a few seconds.')) {
return;
}
setIsRebuildingIndex(true);
try { try {
await rebuildIndex(); await rebuildIndex();
alert("Index rebuilt successfully.");
await getListedApps(); await getListedApps();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
setIsRebuildingIndex(false);
} }
}, [rebuildIndex]); }, [rebuildIndex]);
@ -127,6 +136,7 @@ export default function StorePage() {
return ( return (
<div className={classNames("flex flex-col w-full max-h-screen p-2", { <div className={classNames("flex flex-col w-full max-h-screen p-2", {
isMobile,
'gap-4 max-w-screen': isMobile, 'gap-4 max-w-screen': isMobile,
'gap-6 max-w-[900px]': !isMobile 'gap-6 max-w-[900px]': !isMobile
})}> })}>
@ -174,41 +184,44 @@ export default function StorePage() {
</div> </div>
{!searchQuery && <div className={classNames("flex flex-col", { {!searchQuery && <div className={classNames("flex flex-col", {
'gap-4': !isMobile, 'gap-4': !isMobile,
'grow overflow-y-auto gap-2 items-center px-2': isMobile 'gap-2 items-center': isMobile
})}> })}>
<h2>Featured Apps</h2> <h2>Featured Apps</h2>
<div className={classNames("flex gap-2", { <div className={classNames("flex gap-2", {
'flex-col': isMobile 'flex-wrap': isMobile
})}> })}>
{listedApps.filter(app => { {listedApps.filter(app => {
return featuredPackageNames.indexOf(app.package) !== -1 return featuredPackageNames.indexOf(app.package) !== -1
}).map((app) => ( }).map((app) => (
<AppEntry <AppEntry
key={appId(app) + (app.state?.our_version || "")} key={appId(app) + (app.state?.our_version || "")}
size={'medium'} size={isMobile ? 'small' : 'medium'}
app={app} app={app}
launchPath={launchPaths[app.package]} launchPath={launchPaths[app.package]}
className={classNames("grow", { className={classNames("grow", {
'w-1/4': !isMobile, 'w-1/4': !isMobile,
'w-full': isMobile 'w-1/3': isMobile
})} })}
/> />
))} ))}
</div> </div>
</div>} </div>}
<h2>{searchQuery ? 'Search Results' : 'All Apps'}</h2> <h2 className={classNames({
<div className={classNames("flex flex-col grow overflow-y-auto", { 'text-center': isMobile
})}>{searchQuery ? 'Search Results' : 'All Apps'}</h2>
<div className={classNames("flex flex-col grow", {
'gap-2': isMobile, 'gap-2': isMobile,
'gap-4': !isMobile, 'gap-4 overflow-y-auto': !isMobile,
})}> })}>
{displayedApps {displayedApps
.filter(app => searchQuery ? true : featuredPackageNames.indexOf(app.package) === -1) .filter(app => searchQuery ? true : featuredPackageNames.indexOf(app.package) === -1)
.map(app => <AppEntry .map(app => <AppEntry
key={appId(app) + (app.state?.our_version || "")} key={appId(app) + (app.state?.our_version || "")}
size='large' size={isMobile ? 'medium' : 'large'}
app={app} app={app}
className="self-stretch" className="self-stretch"
overrideImageSize="medium" overrideImageSize="medium"
showMoreActions={!isMobile}
/>)} />)}
</div> </div>
{pages.length > 1 && <div className="flex flex-wrap self-center gap-2"> {pages.length > 1 && <div className="flex flex-wrap self-center gap-2">
@ -234,6 +247,9 @@ export default function StorePage() {
<FaChevronRight /> <FaChevronRight />
</button> </button>
</div>} </div>}
<Modal title="Rebuilding index..." show={isRebuildingIndex} hide={() => { }}>
<Loader msg="This may take a few seconds." />
</Modal>
</div> </div>
); );
} }

View File

@ -82,6 +82,11 @@ export default defineConfig({
target: PROXY_URL, target: PROXY_URL,
changeOrigin: true, changeOrigin: true,
}, },
'/api/*': {
target: PROXY_URL,
changeOrigin: true,
rewrite: (path) => path.replace('/api', ''),
},
// '/example': { // '/example': {
// target: PROXY_URL, // target: PROXY_URL,
// changeOrigin: true, // changeOrigin: true,

View File

@ -222,13 +222,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]] [[package]]
name = "kinode_process_lib" name = "kinode_process_lib"
<<<<<<< HEAD:modules/chess/chess/Cargo.lock
version = "0.5.7" version = "0.5.7"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.9-alpha#c1ac7227951fbd8cabf6568704f0ce11e8558c8a" source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.9-alpha#c1ac7227951fbd8cabf6568704f0ce11e8558c8a"
=======
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
>>>>>>> develop:kinode/packages/chess/chess/Cargo.lock
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-BLQ3kP3C.js"></script> <script type="module" crossorigin src="/assets/index-DRR7woJo.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css"> <link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css">
</head> </head>

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" /> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-BLQ3kP3C.js"></script> <script type="module" crossorigin src="/assets/index-DRR7woJo.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css"> <link rel="stylesheet" crossorigin href="/assets/index-BS5LP50I.css">
</head> </head>

View File

@ -1,5 +1,4 @@
import classNames from "classnames" import classNames from "classnames"
import { FaEye, FaEyeSlash } from "react-icons/fa6"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import usePersistentStore from "../store/persistentStore" import usePersistentStore from "../store/persistentStore"
import useHomepageStore from "../store/homepageStore" import useHomepageStore from "../store/homepageStore"
@ -13,8 +12,7 @@ interface WidgetProps {
const Widget: React.FC<WidgetProps> = ({ package_name, widget, forceLarge }) => { const Widget: React.FC<WidgetProps> = ({ package_name, widget, forceLarge }) => {
const { apps } = useHomepageStore() const { apps } = useHomepageStore()
const { widgetSettings, toggleWidgetVisibility } = usePersistentStore() const { widgetSettings } = usePersistentStore()
const [isHovered, setIsHovered] = useState(false)
const isMobile = isMobileCheck() const isMobile = isMobileCheck()
const isLarge = forceLarge || widgetSettings[package_name]?.size === "large" const isLarge = forceLarge || widgetSettings[package_name]?.size === "large"
const isSmall = !widgetSettings[package_name]?.size || widgetSettings[package_name]?.size === "small" const isSmall = !widgetSettings[package_name]?.size || widgetSettings[package_name]?.size === "small"
@ -31,8 +29,6 @@ const Widget: React.FC<WidgetProps> = ({ package_name, widget, forceLarge }) =>
"max-w-1/4": isSmall && !tallScreen, "max-w-1/4": isSmall && !tallScreen,
'w-full': isMobile 'w-full': isMobile
})} })}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
> >
<h6 className="flex-center my-2"> <h6 className="flex-center my-2">
{apps.find(app => app.package_name === package_name)?.label || package_name} {apps.find(app => app.package_name === package_name)?.label || package_name}
@ -42,12 +38,6 @@ const Widget: React.FC<WidgetProps> = ({ package_name, widget, forceLarge }) =>
className="grow self-stretch" className="grow self-stretch"
data-widget-code={widget} data-widget-code={widget}
/> />
{isHovered && <button
className="absolute top-0 left-0 icon"
onClick={() => toggleWidgetVisibility(package_name)}
>
{widgetSettings[package_name]?.hide ? <FaEye /> : <FaEyeSlash />}
</button>}
</div> </div>
} }

View File

@ -49,6 +49,7 @@ fn create_widget(posts: Vec<KinodeBlogPost>) -> String {
return format!( return format!(
r#"<html> r#"<html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
* {{ * {{
box-sizing: border-box; box-sizing: border-box;

View File

@ -222,13 +222,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]] [[package]]
name = "kinode_process_lib" name = "kinode_process_lib"
<<<<<<< HEAD:modules/chess/chess/Cargo.lock
version = "0.5.7" version = "0.5.7"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.9-alpha#c1ac7227951fbd8cabf6568704f0ce11e8558c8a" source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.9-alpha#c1ac7227951fbd8cabf6568704f0ce11e8558c8a"
=======
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
>>>>>>> develop:kinode/packages/chess/chess/Cargo.lock
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",