mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-25 16:05:27 +03:00
grid: adding installed apps search and lots of little tweaks
This commit is contained in:
parent
2b27425dfe
commit
f62420ea17
@ -39,7 +39,7 @@ export const ProviderList = ({
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={classNames(size !== 'default' ? 'space-y-4' : 'space-y-8', listClass)}
|
||||
className={classNames('-mx-2', size !== 'default' ? 'space-y-4' : 'space-y-8', listClass)}
|
||||
aria-labelledby={labelledBy}
|
||||
>
|
||||
{providers.map((p) => (
|
||||
|
@ -70,6 +70,14 @@ export const Leap = React.forwardRef(
|
||||
}
|
||||
}, [selection, rawInput, appsMatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const newMatch = getMatch(rawInput);
|
||||
|
||||
if (newMatch && rawInput) {
|
||||
useLeapStore.setState({ selectedMatch: newMatch });
|
||||
}
|
||||
}, [rawInput, matches]);
|
||||
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menu === 'search') {
|
||||
return;
|
||||
@ -193,6 +201,9 @@ export const Leap = React.forwardRef(
|
||||
|
||||
if (arrow) {
|
||||
e.preventDefault();
|
||||
if (matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = selectedMatch
|
||||
? matches.findIndex((m) => {
|
||||
|
@ -148,7 +148,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
<DialogContent
|
||||
onInteractOutside={preventClose}
|
||||
onOpenAutoFocus={onOpen}
|
||||
className="fixed bottom-0 sm:top-0 sm:bottom-auto scroll-left-50 flex flex-col scroll-full-width max-w-[882px] px-4 sm:pb-4 text-gray-400 -translate-x-1/2 outline-none"
|
||||
className="fixed bottom-0 sm:top-0 sm:bottom-auto scroll-left-50 flex flex-col justify-end sm:justify-start scroll-full-width h-full max-w-[882px] px-4 sm:pb-4 text-gray-400 -translate-x-1/2 outline-none"
|
||||
role="combobox"
|
||||
aria-controls="leap-items"
|
||||
aria-owns="leap-items"
|
||||
@ -161,7 +161,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
/>
|
||||
<div
|
||||
id="leap-items"
|
||||
className="grid grid-rows-[fit-content(calc(100vh-6.25rem))] bg-white rounded-3xl overflow-hidden default-ring focus-visible:ring-2"
|
||||
className="grid grid-rows-[fit-content(calc(100vh-6.25rem))] mt-4 sm:mt-0 bg-white rounded-3xl overflow-hidden default-ring focus-visible:ring-2"
|
||||
tabIndex={0}
|
||||
role="listbox"
|
||||
>
|
||||
|
@ -55,7 +55,7 @@ export const Notifications = ({ history }: RouteComponentProps) => {
|
||||
variant="secondary"
|
||||
className="flex-auto sm:flex-none py-1.5 px-2 sm:px-6 text-sm sm:text-base rounded-full"
|
||||
>
|
||||
Mark All as Read
|
||||
Archive All
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Timebox } from '@urbit/api';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { Bullet } from '../components/icons/Bullet';
|
||||
import { Cross } from '../components/icons/Cross';
|
||||
import { useHarkStore } from '../state/hark';
|
||||
import { useLeapStore } from './Nav';
|
||||
|
||||
type NotificationsState = 'empty' | 'unread' | 'attention-needed' | 'open';
|
||||
|
||||
@ -42,6 +43,8 @@ export const NotificationsLink = ({
|
||||
}: NotificationsLinkProps) => {
|
||||
const unseen = useHarkStore((s) => s.unseen);
|
||||
const state = getNotificationsState(notificationsOpen, unseen);
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const clearSelection = useCallback(() => select(null), [select]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
@ -56,6 +59,7 @@ export const NotificationsLink = ({
|
||||
state === 'unread' && 'bg-blue-400 text-white',
|
||||
state === 'attention-needed' && 'text-white bg-orange-400'
|
||||
)}
|
||||
onClick={clearSelection}
|
||||
>
|
||||
{state === 'empty' && <Bullet className="w-6 h-6" />}
|
||||
{state === 'unread' && Object.keys(unseen).length}
|
||||
|
@ -9,6 +9,7 @@ import { useVat } from '../state/kiln';
|
||||
import { disableDefault, handleDropdownLink } from '../state/util';
|
||||
import { useMedia } from '../logic/useMedia';
|
||||
import { Cross } from '../components/icons/Cross';
|
||||
import { useLeapStore } from './Nav';
|
||||
|
||||
type SystemMenuProps = HTMLAttributes<HTMLButtonElement> & {
|
||||
open: boolean;
|
||||
@ -27,6 +28,8 @@ export const SystemMenu = ({ className, open, subMenuOpen, shouldDim }: SystemMe
|
||||
const garden = useVat(window.desk);
|
||||
const hash = garden ? getHash(garden) : null;
|
||||
const isMobile = useMedia('(max-width: 639px)');
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const clearSelection = useCallback(() => select(null), [select]);
|
||||
|
||||
const copyHash = useCallback(
|
||||
(event: Event) => {
|
||||
@ -69,6 +72,7 @@ export const SystemMenu = ({ className, open, subMenuOpen, shouldDim }: SystemMe
|
||||
shouldDim && 'opacity-60',
|
||||
className
|
||||
)}
|
||||
onClick={clearSelection}
|
||||
>
|
||||
{!open && !subMenuOpen && (
|
||||
<>
|
||||
|
@ -85,7 +85,7 @@ export const Home = () => {
|
||||
const selectedMatch = useLeapStore((state) => state.selectedMatch);
|
||||
const { recentApps, recentDevs } = useRecentsStore();
|
||||
const charges = useCharges();
|
||||
const groups = charges?.groups;
|
||||
const groups = charges?.landscape;
|
||||
const contacts = useContactState((s) => s.contacts);
|
||||
const defaultAlly = useDocketState((s) =>
|
||||
s.defaultAlly ? { shipName: s.defaultAlly, ...contacts[s.defaultAlly] } : null
|
||||
@ -113,10 +113,17 @@ export const Home = () => {
|
||||
Recent Apps
|
||||
</h2>
|
||||
{apps.length === 0 && (
|
||||
<div className="min-h-[150px] p-6 rounded-xl bg-gray-50">
|
||||
<p className="mb-4">Apps you use will be listed here, in the order you used them.</p>
|
||||
<p className="mb-6">You can click/tap/keyboard on a listed app to open it.</p>
|
||||
{groups && <AppLink app={groups} size="small" onClick={() => addRecentApp('groups')} />}
|
||||
<div className="p-6 rounded-xl bg-gray-50">
|
||||
<p>Apps you use will be listed here, in the order you used them.</p>
|
||||
<p className="mt-4">You can click/tap/keyboard on a listed app to open it.</p>
|
||||
{groups && (
|
||||
<AppLink
|
||||
app={groups}
|
||||
size="small"
|
||||
className="mt-6"
|
||||
onClick={() => addRecentApp('groups')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{apps.length > 0 && (
|
||||
@ -127,7 +134,7 @@ export const Home = () => {
|
||||
Recent Developers
|
||||
</h2>
|
||||
{recentDevs.length === 0 && (
|
||||
<div className="min-h-[150px] p-6 rounded-xl bg-gray-50">
|
||||
<div className="p-6 rounded-xl bg-gray-50">
|
||||
<p className="mb-4">Urbit app developers you search for will be listed here.</p>
|
||||
{defaultAlly && (
|
||||
<>
|
||||
|
@ -3,9 +3,11 @@ import { RouteComponentProps } from 'react-router-dom';
|
||||
import fuzzy from 'fuzzy';
|
||||
import { Provider } from '@urbit/api';
|
||||
import { MatchItem, useLeapStore } from '../Nav';
|
||||
import { useAllies } from '../../state/docket';
|
||||
import { useAllies, useCharges } from '../../state/docket';
|
||||
import { ProviderList } from '../../components/ProviderList';
|
||||
import useContactState from '../../state/contact';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { getAppHref } from '../../state/util';
|
||||
|
||||
type ProvidersProps = RouteComponentProps<{ ship: string }>;
|
||||
|
||||
@ -21,12 +23,36 @@ export function providerMatch(provider: Provider | string): MatchItem {
|
||||
};
|
||||
}
|
||||
|
||||
function fuzzySort(search: string) {
|
||||
return (a: fuzzy.FilterResult<string>, b: fuzzy.FilterResult<string>): number => {
|
||||
const left = a.string.startsWith(search) ? a.score + 1 : a.score;
|
||||
const right = b.string.startsWith(search) ? b.score + 1 : b.score;
|
||||
|
||||
return right - left;
|
||||
};
|
||||
}
|
||||
|
||||
export const Providers = ({ match }: ProvidersProps) => {
|
||||
const selectedMatch = useLeapStore((state) => state.selectedMatch);
|
||||
const provider = match?.params.ship;
|
||||
const contacts = useContactState((s) => s.contacts);
|
||||
const charges = useCharges();
|
||||
const allies = useAllies();
|
||||
const search = provider || '';
|
||||
const chargeArray = Object.entries(charges);
|
||||
const appResults = useMemo(
|
||||
() =>
|
||||
charges
|
||||
? fuzzy
|
||||
.filter(
|
||||
search,
|
||||
chargeArray.map(([desk, charge]) => charge.title + desk)
|
||||
)
|
||||
.sort(fuzzySort(search))
|
||||
.map((el) => chargeArray[el.index][1])
|
||||
: [],
|
||||
[charges, search]
|
||||
);
|
||||
const results = useMemo(
|
||||
() =>
|
||||
allies
|
||||
@ -35,12 +61,7 @@ export const Providers = ({ match }: ProvidersProps) => {
|
||||
search,
|
||||
Object.entries(allies).map(([ship]) => ship)
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const left = a.string.startsWith(search) ? a.score + 1 : a.score;
|
||||
const right = b.string.startsWith(search) ? b.score + 1 : b.score;
|
||||
|
||||
return right - left;
|
||||
})
|
||||
.sort(fuzzySort(search))
|
||||
.map((el) => ({ shipName: el.original, ...contacts[el.original] }))
|
||||
: [],
|
||||
[allies, search, contacts]
|
||||
@ -48,24 +69,63 @@ export const Providers = ({ match }: ProvidersProps) => {
|
||||
|
||||
const count = results?.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
useLeapStore.setState({ rawInput: search });
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (results) {
|
||||
const providerMatches = results ? results.map(providerMatch) : [];
|
||||
const appMatches = appResults
|
||||
? appResults.map((app) => ({
|
||||
url: getAppHref(app.href),
|
||||
openInNewTab: true,
|
||||
value: app.desk,
|
||||
display: app.title
|
||||
}))
|
||||
: [];
|
||||
|
||||
useLeapStore.setState({
|
||||
matches: results.map(providerMatch)
|
||||
matches: ([] as MatchItem[]).concat(appMatches, providerMatches)
|
||||
});
|
||||
}
|
||||
}, [results]);
|
||||
|
||||
return (
|
||||
<div className="dialog-inner-container md:px-6 md:py-8 h4 text-gray-400" aria-live="polite">
|
||||
<div id="providers">
|
||||
<h2 className="mb-3">Searching Software Providers</h2>
|
||||
<p>
|
||||
{count} result{count === 1 ? '' : 's'}
|
||||
</p>
|
||||
</div>
|
||||
{results && (
|
||||
<ProviderList providers={results} labelledBy="providers" matchAgainst={selectedMatch} />
|
||||
<div
|
||||
className="dialog-inner-container md:px-6 md:py-8 space-y-0 h4 text-gray-400"
|
||||
aria-live="polite"
|
||||
>
|
||||
{appResults && !(results?.length > 0 && appResults.length === 0) && (
|
||||
<div>
|
||||
<h2 id="installed" className="mb-3">
|
||||
Installed Apps
|
||||
</h2>
|
||||
<AppList
|
||||
apps={appResults}
|
||||
labelledBy="installed"
|
||||
matchAgainst={selectedMatch}
|
||||
listClass="mb-6"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{results && !(appResults?.length > 0 && results.length === 0) && (
|
||||
<div>
|
||||
<div id="providers">
|
||||
<h2 className="mb-1">Searching Software Providers</h2>
|
||||
<p className="mb-3">
|
||||
{count} result{count === 1 ? '' : 's'}
|
||||
</p>
|
||||
</div>
|
||||
<ProviderList
|
||||
providers={results}
|
||||
labelledBy="providers"
|
||||
matchAgainst={selectedMatch}
|
||||
listClass="mb-6"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<p>That's it!</p>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@ export const TreatyInfo = () => {
|
||||
|
||||
useEffect(() => {
|
||||
select(<>{treaty?.title}</>);
|
||||
useLeapStore.setState({ matches: [] });
|
||||
}, [treaty?.title]);
|
||||
|
||||
if (!treaty) {
|
||||
|
@ -7,6 +7,8 @@ import { getAppHref } from '../state/util';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
import { ChargeWithDesk } from '../state/docket';
|
||||
import { useTileColor } from './useTileColor';
|
||||
import { useVat } from '../state/kiln';
|
||||
import { Bullet } from '../components/icons/Bullet';
|
||||
|
||||
type TileProps = {
|
||||
charge: ChargeWithDesk;
|
||||
@ -16,6 +18,7 @@ type TileProps = {
|
||||
export const Tile: FunctionComponent<TileProps> = ({ charge, desk }) => {
|
||||
const addRecentApp = useRecentsStore((state) => state.addRecentApp);
|
||||
const { title, image, color, chad, href } = charge;
|
||||
const vat = useVat(desk);
|
||||
const { lightText, tileColor, menuColor, suspendColor, suspendMenuColor } = useTileColor(color);
|
||||
const loading = 'install' in chad;
|
||||
const suspended = 'suspend' in chad;
|
||||
@ -42,13 +45,18 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk }) => {
|
||||
<Spinner className="h-6 w-6" />
|
||||
</div>
|
||||
) : (
|
||||
<TileMenu
|
||||
desk={desk}
|
||||
active={active}
|
||||
menuColor={active ? menuColor : suspendMenuColor}
|
||||
lightText={lightText}
|
||||
className="absolute z-10 top-2.5 right-2.5 sm:top-4 sm:right-4 opacity-0 pointer-coarse:opacity-100 hover-none:opacity-100 focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
<>
|
||||
{vat?.arak.rail?.paused && (
|
||||
<Bullet className="absolute z-10 top-5 left-5 sm:top-6 sm:left-7 w-4 h-4 text-orange-500 dark:text-black" />
|
||||
)}
|
||||
<TileMenu
|
||||
desk={desk}
|
||||
active={active}
|
||||
menuColor={active ? menuColor : suspendMenuColor}
|
||||
lightText={lightText}
|
||||
className="absolute z-10 top-2.5 right-2.5 sm:top-4 sm:right-4 opacity-0 pointer-coarse:opacity-100 hover-none:opacity-100 focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="h4 absolute z-10 bottom-[8%] left-[5%] sm:bottom-7 sm:left-5 py-1 px-3 rounded-lg"
|
||||
|
@ -5,22 +5,23 @@ import reactRefresh from '@vitejs/plugin-react-refresh';
|
||||
import { urbitPlugin } from '@urbit/vite-plugin-urbit';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
// using current commit until release
|
||||
const GIT_DESC = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
|
||||
process.env.VITE_SHORTHASH = GIT_DESC;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ mode }) => {
|
||||
if (mode !== 'mock') {
|
||||
// using current commit until release
|
||||
const GIT_DESC = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
|
||||
process.env.VITE_SHORTHASH = GIT_DESC;
|
||||
} else {
|
||||
process.env.VITE_SHORTHASH = '1';
|
||||
}
|
||||
|
||||
Object.assign(process.env, loadEnv(mode, process.cwd()));
|
||||
const SHIP_URL = process.env.SHIP_URL || process.env.VITE_SHIP_URL || 'http://localhost:8080';
|
||||
console.log(SHIP_URL);
|
||||
|
||||
return defineConfig({
|
||||
base: mode === 'mock' ? undefined : '/apps/grid/',
|
||||
server:
|
||||
mode === 'mock'
|
||||
? undefined
|
||||
: { https: true },
|
||||
server: mode === 'mock' ? undefined : { https: true },
|
||||
build:
|
||||
mode !== 'profile'
|
||||
? undefined
|
||||
@ -34,6 +35,7 @@ export default ({ mode }) => {
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [urbitPlugin({ base: 'grid', target: SHIP_URL }), reactRefresh()]
|
||||
plugins:
|
||||
mode === 'mock' ? [] : [urbitPlugin({ base: 'grid', target: SHIP_URL }), reactRefresh()]
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user