mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-20 15:08:34 +03:00
leap: making leap transitions more direct
This commit is contained in:
parent
967a074734
commit
0b22ee3bb5
1
pkg/grid/.gitignore
vendored
1
pkg/grid/.gitignore
vendored
@ -6,3 +6,4 @@ dist-ssr
|
||||
stats.html
|
||||
.eslintcache
|
||||
.vercel
|
||||
.env
|
BIN
pkg/grid/package-lock.json
generated
BIN
pkg/grid/package-lock.json
generated
Binary file not shown.
@ -41,6 +41,7 @@
|
||||
"@tailwindcss/aspect-ratio": "^0.2.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mousetrap": "^1.6.8",
|
||||
"@types/node": "^16.7.9",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { HTMLProps, ReactNode } from 'react';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { App } from '../state/docket';
|
||||
import { DocketWithDesk } from '../state/docket';
|
||||
import { getAppHref } from '../state/util';
|
||||
|
||||
type Sizes = 'xs' | 'small' | 'default';
|
||||
@ -12,11 +12,11 @@ type LinkOrAnchorProps = {
|
||||
: never;
|
||||
};
|
||||
|
||||
export type AppLinkProps = Omit<LinkOrAnchorProps, 'to'> & {
|
||||
app: App;
|
||||
export type AppLinkProps<T extends DocketWithDesk> = Omit<LinkOrAnchorProps, 'to'> & {
|
||||
app: T;
|
||||
size?: Sizes;
|
||||
selected?: boolean;
|
||||
to?: (app: App) => LinkProps['to'] | undefined;
|
||||
to?: (app: T) => LinkProps['to'] | undefined;
|
||||
};
|
||||
|
||||
const sizeMap: Record<Sizes, string> = {
|
||||
@ -25,14 +25,14 @@ const sizeMap: Record<Sizes, string> = {
|
||||
default: 'w-12 h-12 mr-3 rounded-lg'
|
||||
};
|
||||
|
||||
export const AppLink = ({
|
||||
export const AppLink = <T extends DocketWithDesk>({
|
||||
app,
|
||||
to,
|
||||
size = 'default',
|
||||
selected = false,
|
||||
className,
|
||||
...props
|
||||
}: AppLinkProps) => {
|
||||
}: AppLinkProps<T>) => {
|
||||
const linkTo = to?.(app);
|
||||
const linkClassnames = classNames(
|
||||
'flex items-center default-ring ring-offset-2 rounded-lg',
|
||||
|
@ -1,28 +1,28 @@
|
||||
import React, { MouseEvent, useCallback } from 'react';
|
||||
import { Docket } from '@urbit/api';
|
||||
import classNames from 'classnames';
|
||||
import { MatchItem } from '../nav/Nav';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
import { AppLink, AppLinkProps } from './AppLink';
|
||||
import { DocketWithDesk } from '../state/docket';
|
||||
|
||||
type AppListProps<T extends Docket> = {
|
||||
type AppListProps<T extends DocketWithDesk> = {
|
||||
apps: T[];
|
||||
labelledBy: string;
|
||||
matchAgainst?: MatchItem;
|
||||
onClick?: (e: MouseEvent<HTMLAnchorElement>, app: Docket) => void;
|
||||
onClick?: (e: MouseEvent<HTMLAnchorElement>, app: T) => void;
|
||||
listClass?: string;
|
||||
} & Omit<AppLinkProps<T>, 'app' | 'onClick'>;
|
||||
|
||||
export function appMatches(target: Docket, match?: MatchItem): boolean {
|
||||
export function appMatches(target: DocketWithDesk, match?: MatchItem): boolean {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matchValue = match.display || match.value;
|
||||
return target.title === matchValue; // TODO: need desk name or something || target.href === matchValue;
|
||||
return target.title === matchValue || target.desk === matchValue;
|
||||
}
|
||||
|
||||
export const AppList = <T extends Docket>({
|
||||
export const AppList = <T extends DocketWithDesk>({
|
||||
apps,
|
||||
labelledBy,
|
||||
matchAgainst,
|
||||
@ -32,7 +32,7 @@ export const AppList = <T extends Docket>({
|
||||
...props
|
||||
}: AppListProps<T>) => {
|
||||
const addRecentApp = useRecentsStore((state) => state.addRecentApp);
|
||||
const selected = useCallback((app: Docket) => appMatches(app, matchAgainst), [matchAgainst]);
|
||||
const selected = useCallback((app: T) => appMatches(app, matchAgainst), [matchAgainst]);
|
||||
|
||||
return (
|
||||
<ul
|
||||
@ -53,9 +53,7 @@ export const AppList = <T extends Docket>({
|
||||
selected={selected(app)}
|
||||
onClick={(e) => {
|
||||
addRecentApp(app);
|
||||
if (onClick) {
|
||||
onClick(e, app);
|
||||
}
|
||||
onClick?.(e, app);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
|
@ -7,7 +7,8 @@ import React, {
|
||||
HTMLAttributes,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useRef
|
||||
useRef,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import { Link, useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { Cross } from '../components/icons/Cross';
|
||||
@ -46,6 +47,12 @@ export const Leap = React.forwardRef(({ menu, dropdown, showClose, className }:
|
||||
useImperativeHandle(ref, () => inputRef.current);
|
||||
const { rawInput, selectedMatch, matches, selection, select } = useLeapStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (selection && rawInput === '') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [selection, rawInput]);
|
||||
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menu === 'search') {
|
||||
return;
|
||||
@ -133,32 +140,19 @@ export const Leap = React.forwardRef(({ menu, dropdown, showClose, className }:
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (selectedMatch?.href) {
|
||||
window.open(selectedMatch.href, selectedMatch.value);
|
||||
const value = inputRef.current?.value.trim();
|
||||
const currentMatch = selectedMatch || (value && getMatch(value));
|
||||
|
||||
if (!currentMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedMatch?.value) {
|
||||
if (currentMatch?.openInNewTab) {
|
||||
window.open(currentMatch.url, currentMatch.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: evaluate if we still need this for manual entry
|
||||
// const value = inputRef.current?.value.trim();
|
||||
|
||||
// if (!value) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const input = [getMatch(value)?.value || slugify(value)];
|
||||
|
||||
const input = [selectedMatch.value];
|
||||
if (appsMatch) {
|
||||
input.unshift(match?.params.query || '');
|
||||
} else {
|
||||
input.push('');
|
||||
}
|
||||
|
||||
navigateByInput(input.join('/'));
|
||||
push(currentMatch.url);
|
||||
useLeapStore.setState({ rawInput: '' });
|
||||
},
|
||||
[match, selectedMatch]
|
||||
|
@ -14,9 +14,10 @@ import { SystemMenu } from './SystemMenu';
|
||||
import { SystemPreferences } from './SystemPreferences';
|
||||
|
||||
export interface MatchItem {
|
||||
url: string;
|
||||
openInNewTab: boolean;
|
||||
value: string;
|
||||
display?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface LeapStore {
|
||||
|
@ -26,25 +26,25 @@ export const usePreferencesStore = create<PreferencesStore>((set) => ({
|
||||
*/
|
||||
toggleOTAs: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest();
|
||||
await fakeRequest({});
|
||||
set((state) => ({ otasEnabled: !state.otasEnabled }));
|
||||
}
|
||||
},
|
||||
setOTASource: async (source: string) => {
|
||||
if (useMockData) {
|
||||
await fakeRequest();
|
||||
await fakeRequest({});
|
||||
set({ otaSource: source });
|
||||
}
|
||||
},
|
||||
toggleDoNotDisturb: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest();
|
||||
await fakeRequest({});
|
||||
set((state) => ({ doNotDisturb: !state.doNotDisturb }));
|
||||
}
|
||||
},
|
||||
toggleMentions: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest();
|
||||
await fakeRequest({});
|
||||
set((state) => ({ mentions: !state.mentions }));
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,14 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import fuzzy from 'fuzzy';
|
||||
import slugify from 'slugify';
|
||||
import { Treaty } from '@urbit/api';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import useDocketState, { App, useAllyTreaties } from '../../state/docket';
|
||||
import { MatchItem, useLeapStore } from '../Nav';
|
||||
import useDocketState, { useAllyTreaties } from '../../state/docket';
|
||||
import { useLeapStore } from '../Nav';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { getAppHref } from '../../state/util';
|
||||
|
||||
type AppsProps = RouteComponentProps<{ ship: string }>;
|
||||
|
||||
export function appMatch(app: App, includeHref = false): MatchItem {
|
||||
const match: MatchItem = { value: app.desk, display: app.title };
|
||||
|
||||
if (includeHref) {
|
||||
match.href = getAppHref(app.href);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
export const Apps = ({ match }: AppsProps) => {
|
||||
const { searchInput, selectedMatch, select } = useLeapStore((state) => ({
|
||||
searchInput: state.searchInput,
|
||||
@ -48,6 +37,11 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
}, [treaties, searchInput]);
|
||||
const count = results?.length;
|
||||
|
||||
const getAppPath = useCallback(
|
||||
(app: Treaty) => `${match?.path.replace(':ship', provider)}/${app.ship}/${app.desk}`,
|
||||
[match]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { fetchAllyTreaties } = useDocketState.getState();
|
||||
fetchAllyTreaties(provider);
|
||||
@ -61,7 +55,12 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
useEffect(() => {
|
||||
if (results) {
|
||||
useLeapStore.setState({
|
||||
matches: results.map((r) => appMatch(r))
|
||||
matches: results.map((r) => ({
|
||||
url: getAppPath(r),
|
||||
openInNewTab: false,
|
||||
value: r.desk,
|
||||
display: r.title
|
||||
}))
|
||||
});
|
||||
}
|
||||
}, [results]);
|
||||
@ -87,7 +86,7 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
apps={results}
|
||||
labelledBy="developed-by"
|
||||
matchAgainst={selectedMatch}
|
||||
to={(app) => `${match?.path.replace(':ship', provider)}/${app.ship}/${slugify(app.desk)}`}
|
||||
to={getAppPath}
|
||||
/>
|
||||
)}
|
||||
<p>That's it!</p>
|
||||
|
@ -5,19 +5,19 @@ import { persist } from 'zustand/middleware';
|
||||
import { take } from 'lodash-es';
|
||||
import { Provider } from '@urbit/api';
|
||||
import { MatchItem, useLeapStore } from '../Nav';
|
||||
import { appMatch } from './Apps';
|
||||
import { providerMatch } from './Providers';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { ProviderList } from '../../components/ProviderList';
|
||||
import { AppLink } from '../../components/AppLink';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { ProviderLink } from '../../components/ProviderLink';
|
||||
import { App, useCharges } from '../../state/docket';
|
||||
import { DocketWithDesk, useCharges } from '../../state/docket';
|
||||
import { getAppHref } from '../../state/util';
|
||||
|
||||
interface RecentsStore {
|
||||
recentApps: App[];
|
||||
recentApps: DocketWithDesk[];
|
||||
recentDevs: Provider[];
|
||||
addRecentApp: (app: App) => void;
|
||||
addRecentApp: (app: DocketWithDesk) => void;
|
||||
addRecentDev: (dev: Provider) => void;
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ export function addRecentDev(dev: Provider) {
|
||||
return useRecentsStore.getState().addRecentDev(dev);
|
||||
}
|
||||
|
||||
export function addRecentApp(app: App) {
|
||||
export function addRecentApp(app: DocketWithDesk) {
|
||||
return useRecentsStore.getState().addRecentApp(app);
|
||||
}
|
||||
|
||||
@ -76,7 +76,12 @@ export const Home = () => {
|
||||
const zod = { shipName: '~zod' };
|
||||
|
||||
useEffect(() => {
|
||||
const apps = recentApps.map((app) => appMatch(app, true));
|
||||
const apps = recentApps.map((app) => ({
|
||||
url: getAppHref(app.href),
|
||||
openInNewTab: true,
|
||||
value: app.desk,
|
||||
display: app.title
|
||||
}));
|
||||
const devs = recentDevs.map(providerMatch);
|
||||
|
||||
useLeapStore.setState({
|
||||
|
@ -9,11 +9,15 @@ import { ProviderList } from '../../components/ProviderList';
|
||||
type ProvidersProps = RouteComponentProps<{ ship: string }>;
|
||||
|
||||
export function providerMatch(provider: Provider | string): MatchItem {
|
||||
if (typeof provider === 'string') {
|
||||
return { value: provider, display: provider };
|
||||
}
|
||||
const value = typeof provider === 'string' ? provider : provider.shipName;
|
||||
const display = typeof provider === 'string' ? undefined : provider.nickname;
|
||||
|
||||
return { value: provider.shipName, display: provider.nickname };
|
||||
return {
|
||||
value,
|
||||
display,
|
||||
url: `/leap/search/${value}/apps`,
|
||||
openInNewTab: false
|
||||
};
|
||||
}
|
||||
|
||||
export const Providers = ({ match }: ProvidersProps) => {
|
||||
|
@ -31,7 +31,9 @@ export interface ChargesWithDesks {
|
||||
[ref: string]: ChargeWithDesk;
|
||||
}
|
||||
|
||||
export type App = Treaty | ChargeWithDesk;
|
||||
export interface DocketWithDesk extends Docket {
|
||||
desk: string;
|
||||
}
|
||||
|
||||
interface DocketState {
|
||||
charges: ChargesWithDesks;
|
||||
|
@ -8,7 +8,7 @@ export function makeKeyFn(key: string) {
|
||||
|
||||
export const useMockData = import.meta.env.MODE === 'mock';
|
||||
|
||||
export async function fakeRequest<T>(data?: any, time = 300): Promise<T> {
|
||||
export async function fakeRequest<T>(data: T, time = 300): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(data);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { loadEnv, defineConfig } from 'vite';
|
||||
import analyze from 'rollup-plugin-analyzer';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import reactRefresh from '@vitejs/plugin-react-refresh';
|
||||
@ -7,36 +7,41 @@ import htmlPlugin from 'vite-plugin-html-config';
|
||||
const htmlPluginOpt = {
|
||||
headScripts: [{ src: '/apps/grid/desk.js' }, { src: '/session.js' }]
|
||||
};
|
||||
const SHIP_URL = process.env.SHIP_URL || 'http://localhost:8080';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => ({
|
||||
base: mode === 'mock' ? undefined : '/apps/grid/',
|
||||
server:
|
||||
mode === 'mock'
|
||||
? undefined
|
||||
: {
|
||||
proxy: {
|
||||
'^/apps/grid/desk.js': {
|
||||
target: SHIP_URL
|
||||
},
|
||||
'^((?!/apps/grid).)*$': {
|
||||
target: SHIP_URL
|
||||
export default ({ mode }) => {
|
||||
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
|
||||
: {
|
||||
proxy: {
|
||||
'^/apps/grid/desk.js': {
|
||||
target: SHIP_URL
|
||||
},
|
||||
'^((?!/apps/grid).)*$': {
|
||||
target: SHIP_URL
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
build:
|
||||
mode !== 'profile'
|
||||
? undefined
|
||||
: {
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
analyze({
|
||||
limit: 20
|
||||
}),
|
||||
visualizer()
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [htmlPlugin(htmlPluginOpt), reactRefresh()]
|
||||
}));
|
||||
},
|
||||
build:
|
||||
mode !== 'profile'
|
||||
? undefined
|
||||
: {
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
analyze({
|
||||
limit: 20
|
||||
}),
|
||||
visualizer()
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [htmlPlugin(htmlPluginOpt), reactRefresh()]
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user