grid: permalink to apps and treaties

This commit is contained in:
Liam Fitzgerald 2021-09-08 13:04:03 +10:00
parent b3e634db48
commit 0dfac89e26
7 changed files with 162 additions and 14 deletions

View File

@ -3,6 +3,8 @@ import Mousetrap from 'mousetrap';
import { BrowserRouter, Switch, Route, useHistory } from 'react-router-dom';
import { Grid } from './pages/Grid';
import useDocketState from './state/docket';
import {PermalinkRoutes} from './pages/PermalinkRoutes';
import useKilnState from './state/kiln';
const AppRoutes = () => {
const { push } = useHistory();
@ -13,7 +15,9 @@ const AppRoutes = () => {
const { fetchAllies, fetchCharges } = useDocketState.getState();
fetchCharges();
fetchAllies();
const { fetchVats, fetchLag } = useKilnState.getState();
fetchVats();
fetchLag();
Mousetrap.bind(['command+/', 'ctrl+/'], () => {
push('/leap/search');
});
@ -21,6 +25,7 @@ const AppRoutes = () => {
return (
<Switch>
<Route path="/perma" component={PermalinkRoutes} />
<Route path={['/leap/:menu', '/']} component={Grid} />
</Switch>
);

View File

@ -0,0 +1,46 @@
import _ from 'lodash';
import { useCallback, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
function mergeQuery(search: URLSearchParams, added: Record<string, string>) {
_.forIn(added, (v, k) => {
if (v) {
search.append(k, v);
} else {
search.delete(k);
}
});
}
export function useQuery() {
const { search, pathname } = useLocation();
const query = useMemo(() => new URLSearchParams(search), [search]);
const appendQuery = useCallback(
(added: Record<string, string>) => {
const q = new URLSearchParams(search);
mergeQuery(q, added);
return q.toString();
},
[search]
);
const toQuery = useCallback(
(params: Record<string, string>, path = pathname) => {
const q = new URLSearchParams(search);
mergeQuery(q, params);
return {
pathname: path,
search: q.toString()
};
},
[search, pathname]
);
return {
query,
appendQuery,
toQuery
};
}

View File

@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { AppInfo } from '../../components/AppInfo';
import { ShipName } from '../../components/ShipName';
import { useCharge, useTreaty } from '../../state/docket';
import useDocketState, { useCharge, useTreaty } from '../../state/docket';
import { useVat } from '../../state/kiln';
import { useLeapStore } from '../Nav';
@ -13,10 +13,16 @@ export const TreatyInfo = () => {
const vat = useVat(desk);
const charge = useCharge(desk);
useEffect(() => {
if(!charge) {
useDocketState.getState().requestTreaty(host, desk);
}
}, [host, desk]);
useEffect(() => {
select(
<>
Apps by <ShipName name={ship} className="font-mono" />: {treaty?.title}
{treaty?.title}
</>
);
}, [treaty?.title]);

View File

@ -17,15 +17,6 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
const charges = useCharges();
const chargesLoaded = Object.keys(charges).length > 0;
useEffect(() => {
const { fetchCharges, fetchAllies } = useDocketState.getState();
const { fetchVats, fetchLag } = useKilnState.getState();
fetchCharges();
fetchAllies();
fetchVats();
fetchLag();
}, []);
return (
<div className="flex flex-col">
<header className="fixed sm:sticky bottom-0 sm:bottom-auto sm:top-0 left-0 z-30 flex justify-center w-full bg-white">

View File

@ -0,0 +1,92 @@
import React, { useEffect } from 'react';
import { Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom';
import { Spinner } from '../components/Spinner';
import { useQuery } from '../logic/useQuery';
import { useCharge } from '../state/docket';
import useKilnState, { useKilnLoaded, useVat } from '../state/kiln';
import { getAppHref } from '../state/util';
function getDeskByForeignRef(ship: string, desk: string): string | undefined {
const { vats } = useKilnState.getState();
console.log(ship, desk);
const found = Object.entries(vats).find(
([d, vat]) => vat.arak.ship === ship && vat.arak.desk === desk
);
return !!found ? found[0] : undefined;
}
interface AppLinkProps
extends RouteComponentProps<{
ship: string;
desk: string;
link: string;
}> {}
function AppLink(props: AppLinkProps) {
const { ship, desk, link = '' } = props.match.params;
const ourDesk = getDeskByForeignRef(ship, desk);
if (ourDesk) {
return <AppLinkRedirect desk={ourDesk} link={link} />;
}
return <AppLinkNotFound {...props} />;
}
function AppLinkNotFound(props: AppLinkProps) {
const { ship, desk } = props.match.params;
return (<Redirect to={`/leap/search/direct/apps/${ship}/${desk}`} />);
}
function AppLinkInvalid(props: AppLinkProps) {
return (
<div>
<h4>Link was malformed</h4>
<p>The link you tried to follow was invalid</p>
</div>
);
}
function AppLinkRedirect({ desk, link }: { desk: string; link: string }) {
const vat = useVat(desk);
const charge = useCharge(desk);
useEffect(() => {
const query = new URLSearchParams({
'grid-link': encodeURIComponent(`/${link}`)
});
const url = `${getAppHref(charge.href)}?${query.toString()}`;
window.open(url, desk);
}, []);
return <Redirect to="/" />;
}
const LANDSCAPE_SHIP = '~zod';
const LANDSCAPE_DESK = 'groups';
function LandscapeLink(props: RouteComponentProps<{ link: string }>) {
const { link } = props.match.params;
return <Redirect to={`/perma/${LANDSCAPE_SHIP}/${LANDSCAPE_DESK}/${link}`} />;
}
export function PermalinkRoutes() {
const loaded = useKilnLoaded();
const { query } = useQuery();
if (query.has('ext')) {
const ext = query.get('ext')!;
const url = `/perma${ext.slice(16)}`;
return <Redirect to={url} />;
}
if (!loaded) {
return <Spinner />;
}
return (
<Switch>
<Route path="/perma/group/:link+" component={LandscapeLink} />
<Route path="/perma/:ship/:desk/:link*" component={AppLink} />
<Route path="/" component={AppLinkInvalid} />
</Switch>
);
}

View File

@ -87,7 +87,7 @@ const useDocketState = create<DocketState>((set, get) => ({
return treaties[key];
}
const result = await api.subscribeOnce('docket', `/treaty/${key}`, 20000);
const result = await api.subscribeOnce('treaty', `/treaty/${key}`, 20000);
const treaty = { ...normalizeDocket(result, desk), ship };
set((state) => ({
treaties: { ...state.treaties, [key]: treaty }

View File

@ -8,6 +8,7 @@ import { mockVats } from './mock-data';
interface KilnState {
vats: Vats;
loaded: boolean;
fetchVats: () => Promise<void>;
lag: boolean;
fetchLag: () => Promise<void>;
@ -16,13 +17,15 @@ interface KilnState {
export const useKilnState = create<KilnState>((set) => ({
vats: useMockData ? mockVats : {},
lag: !!useMockData,
loaded: false,
fetchVats: async () => {
if (useMockData) {
await fakeRequest({}, 500);
set({ loaded: true });
return;
}
const vats = await api.scry<Vats>(getVats);
set({ vats });
set({ vats, loaded: true });
},
fetchLag: async () => {
const lag = await api.scry<boolean>(scryLag);
@ -53,4 +56,9 @@ export function useLag() {
return useKilnState(selLag);
}
const selLoaded = (s: KilnState) => s.loaded;
export function useKilnLoaded() {
return useKilnState(selLoaded);
}
export default useKilnState;