mirror of
https://github.com/tloncorp/landscape.git
synced 2025-01-08 17:42:49 +03:00
Merge pull request #194 from tloncorp/po/get-apps-updates
get-apps: rework search interaction, update UX
This commit is contained in:
commit
fc712eb77f
226
ui/package-lock.json
generated
226
ui/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "landscape",
|
||||
"version": "0.0.0",
|
||||
"version": "1.11.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "landscape",
|
||||
"version": "0.0.0",
|
||||
"version": "1.11.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.348.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.348.0",
|
||||
@ -52,7 +52,7 @@
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.38.0",
|
||||
"react-image-size": "^2.0.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"slugify": "^1.6.0",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
@ -65,7 +65,6 @@
|
||||
"@types/node": "^16.11.56",
|
||||
"@types/react": "^16.0.0",
|
||||
"@types/react-dom": "^16.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/parser": "^4.26.1",
|
||||
"@urbit/vite-plugin-urbit": "^0.8.0",
|
||||
@ -4174,6 +4173,14 @@
|
||||
"version": "3.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz",
|
||||
"integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "4.1.1",
|
||||
"dev": true,
|
||||
@ -4384,11 +4391,6 @@
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
|
||||
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
||||
},
|
||||
"node_modules/@types/history": {
|
||||
"version": "4.7.9",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.9",
|
||||
"dev": true,
|
||||
@ -4438,25 +4440,6 @@
|
||||
"@types/react": "^16"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router": {
|
||||
"version": "5.1.16",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router-dom": {
|
||||
"version": "5.1.8",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"devOptional": true,
|
||||
@ -7487,18 +7470,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/history": {
|
||||
"version": "4.10.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"license": "BSD-3-Clause",
|
||||
@ -8075,10 +8046,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
@ -8633,18 +8600,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-create-react-context": {
|
||||
"version": "0.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"tiny-warning": "^1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.0.0",
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"dev": true,
|
||||
@ -9082,13 +9037,6 @@
|
||||
"version": "1.0.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "1.8.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
@ -9340,6 +9288,7 @@
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.7.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
@ -9587,38 +9536,33 @@
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "5.2.0",
|
||||
"license": "MIT",
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.2.tgz",
|
||||
"integrity": "sha512-74z9xUSaSX07t3LM+pS6Un0T55ibUE/79CzfZpy5wsPDZaea1F8QkrsiyRnA2YQ7LwE/umaydzXZV80iDCPkMg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.4.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "5.2.0",
|
||||
"license": "MIT",
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.2.tgz",
|
||||
"integrity": "sha512-JNbKtAeh1VSJQnH6RvBDNhxNwemRj7KxCzc5jb7zvDSKRnPWIFj9pO+eXqjM69gQJ0r46hSz1x4l9y0651DKWw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.2.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.6.2",
|
||||
"react-router": "6.11.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
@ -9826,10 +9770,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pathname": {
|
||||
"version": "3.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
@ -10577,14 +10517,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.1",
|
||||
"dev": true,
|
||||
@ -10864,10 +10796,6 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/value-equal": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
|
||||
@ -14455,6 +14383,11 @@
|
||||
"@react-dnd/shallowequal": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz",
|
||||
"integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA=="
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "4.1.1",
|
||||
"dev": true,
|
||||
@ -14588,10 +14521,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.9",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.9",
|
||||
"dev": true
|
||||
@ -14634,23 +14563,6 @@
|
||||
"@types/react": "^16"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.16",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router-dom": {
|
||||
"version": "5.1.8",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"devOptional": true
|
||||
@ -16585,17 +16497,6 @@
|
||||
"version": "1.1.0",
|
||||
"dev": true
|
||||
},
|
||||
"history": {
|
||||
"version": "4.10.1",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"requires": {
|
||||
@ -16939,9 +16840,6 @@
|
||||
"is-docker": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"dev": true
|
||||
@ -17303,13 +17201,6 @@
|
||||
"version": "2.1.0",
|
||||
"dev": true
|
||||
},
|
||||
"mini-create-react-context": {
|
||||
"version": "0.4.1",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"dev": true,
|
||||
@ -17572,12 +17463,6 @@
|
||||
"path-parse": {
|
||||
"version": "1.0.7"
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "1.8.0",
|
||||
"requires": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
},
|
||||
"path-type": {
|
||||
"version": "4.0.0",
|
||||
"dev": true
|
||||
@ -17714,6 +17599,7 @@
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@ -17850,30 +17736,20 @@
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.2.0",
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.2.tgz",
|
||||
"integrity": "sha512-74z9xUSaSX07t3LM+pS6Un0T55ibUE/79CzfZpy5wsPDZaea1F8QkrsiyRnA2YQ7LwE/umaydzXZV80iDCPkMg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.4.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.6.2"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "5.2.0",
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.2.tgz",
|
||||
"integrity": "sha512-JNbKtAeh1VSJQnH6RvBDNhxNwemRj7KxCzc5jb7zvDSKRnPWIFj9pO+eXqjM69gQJ0r46hSz1x4l9y0651DKWw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.2.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.6.2",
|
||||
"react-router": "6.11.2"
|
||||
}
|
||||
},
|
||||
"react-style-singleton": {
|
||||
@ -18010,9 +17886,6 @@
|
||||
"version": "4.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"resolve-pathname": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
@ -18499,12 +18372,6 @@
|
||||
"version": "2.3.8",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"tiny-warning": {
|
||||
"version": "1.0.3"
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.2.1",
|
||||
"dev": true,
|
||||
@ -18679,9 +18546,6 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"value-equal": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
|
||||
|
@ -61,8 +61,8 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.38.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-image-size": "^2.0.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"slugify": "^1.6.0",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
@ -75,7 +75,6 @@
|
||||
"@types/node": "^16.11.56",
|
||||
"@types/react": "^16.0.0",
|
||||
"@types/react-dom": "^16.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/parser": "^4.26.1",
|
||||
"@urbit/vite-plugin-urbit": "^0.8.0",
|
||||
|
@ -3,12 +3,11 @@ import Mousetrap from 'mousetrap';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import {
|
||||
BrowserRouter,
|
||||
Switch,
|
||||
Route,
|
||||
useHistory,
|
||||
useLocation,
|
||||
RouteComponentProps,
|
||||
Redirect,
|
||||
Routes,
|
||||
useNavigate,
|
||||
Navigate,
|
||||
} from 'react-router-dom';
|
||||
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
@ -49,13 +48,14 @@ const getId = async () => {
|
||||
return result.visitorId;
|
||||
};
|
||||
|
||||
function OldLeapRedirect({ location }: RouteComponentProps) {
|
||||
function OldLeapRedirect() {
|
||||
const location = useLocation();
|
||||
const path = location.pathname.replace('/leap', '');
|
||||
return <Redirect to={path} />;
|
||||
return <Navigate to={path} />;
|
||||
}
|
||||
|
||||
const AppRoutes = () => {
|
||||
const { push } = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const { search } = useLocation();
|
||||
const handleError = useErrorHandler();
|
||||
const browserId = useBrowserId();
|
||||
@ -70,7 +70,7 @@ const AppRoutes = () => {
|
||||
const query = new URLSearchParams(search);
|
||||
if (query.has('grid-note')) {
|
||||
const redir = getNoteRedirect(query.get('grid-note')!);
|
||||
push(redir);
|
||||
navigate(redir);
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
@ -105,18 +105,20 @@ const AppRoutes = () => {
|
||||
.wait(() => useContactState.getState().start(), 5);
|
||||
|
||||
Mousetrap.bind(['command+/', 'ctrl+/'], () => {
|
||||
push('/search');
|
||||
navigate('/search');
|
||||
});
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/perma" component={PermalinkRoutes} />
|
||||
<Route path="/leap/*" component={OldLeapRedirect} />
|
||||
<Route path={['/:menu', '/']} component={Grid} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="perma/*" element={<PermalinkRoutes />} />
|
||||
<Route path="leap/*" element={<OldLeapRedirect />} />
|
||||
<Route path="/" element={<Grid />}>
|
||||
<Route path=":menu/*" element={<Grid />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useCharge } from '../state/docket';
|
||||
import { getAppHref } from '@/logic/utils';
|
||||
|
||||
@ -17,7 +17,7 @@ export function DeskLink({
|
||||
to = '',
|
||||
...rest
|
||||
}: DeskLinkProps) {
|
||||
const { push } = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const charge = useCharge(desk);
|
||||
|
||||
if (!charge) {
|
||||
@ -42,7 +42,7 @@ export function DeskLink({
|
||||
if (rest.onClick) {
|
||||
rest.onClick(event);
|
||||
}
|
||||
push('/');
|
||||
navigate('/');
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -45,15 +45,15 @@ const WayfindingAppLink = ({
|
||||
</div>
|
||||
</div>
|
||||
{installed ? (
|
||||
<Button variant="alt-primary" as="a" href={link} target="_blank">
|
||||
Open App
|
||||
<Button variant="primary" as="a" href={link} target="_blank">
|
||||
Open
|
||||
</Button>
|
||||
) : (
|
||||
<NavLink to={`/search/${source}/apps/${source}/${desk}`}>
|
||||
<Button
|
||||
variant="alt-primary"
|
||||
>
|
||||
Install App
|
||||
Get
|
||||
</Button>
|
||||
</NavLink>
|
||||
)}
|
||||
|
@ -4,8 +4,20 @@ import { IconProps } from './icon';
|
||||
export default function InvitesIcon({ className }: IconProps) {
|
||||
// Placeholder, please make a real icon
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 5.23077C9 7.60984 7.65685 8.46154 6 8.46154C4.34315 8.46154 3 7.60984 3 5.23077C3 2.8517 4.34315 2 6 2C7.65685 2 9 2.8517 9 5.23077ZM6 8.46154C2.68629 8.46154 0 9.92159 0 14H12C12 9.92159 9.31371 8.46154 6 8.46154ZM14 4C14 3.44772 13.5523 3 13 3C12.4477 3 12 3.44772 12 4V5H11C10.4477 5 10 5.44772 10 6C10 6.55228 10.4477 7 11 7H12V8C12 8.55228 12.4477 9 13 9C13.5523 9 14 8.55228 14 8V7H15C15.5523 7 16 6.55228 16 6C16 5.44772 15.5523 5 15 5H14V4Z" fill="#666666"/>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 5.23077C9 7.60984 7.65685 8.46154 6 8.46154C4.34315 8.46154 3 7.60984 3 5.23077C3 2.8517 4.34315 2 6 2C7.65685 2 9 2.8517 9 5.23077ZM6 8.46154C2.68629 8.46154 0 9.92159 0 14H12C12 9.92159 9.31371 8.46154 6 8.46154ZM14 4C14 3.44772 13.5523 3 13 3C12.4477 3 12 3.44772 12 4V5H11C10.4477 5 10 5.44772 10 6C10 6.55228 10.4477 7 11 7H12V8C12 8.55228 12.4477 9 13 9C13.5523 9 14 8.55228 14 8V7H15C15.5523 7 16 6.55228 16 6C16 5.44772 15.5523 5 15 5H14V4Z"
|
||||
fill="#666666"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { kilnBump, Pike } from '@/gear';
|
||||
import { partition, pick } from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import api from '../api';
|
||||
import { useCharges } from '../state/docket';
|
||||
import useKilnState, { usePike } from '../state/kiln';
|
||||
@ -14,7 +14,7 @@ function pikeIsBlocked(newKelvin: number, pike: Pike) {
|
||||
}
|
||||
|
||||
export function useSystemUpdate() {
|
||||
const { push } = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const base = usePike('base');
|
||||
const nextUpdate = base?.wefts[0];
|
||||
const newKelvin = base?.wefts[0]?.kelvin ?? 417;
|
||||
@ -32,7 +32,7 @@ export function useSystemUpdate() {
|
||||
|
||||
const freezeApps = useCallback(async () => {
|
||||
await api.poke(kilnBump());
|
||||
push('/leap/upgrading');
|
||||
navigate('/leap/upgrading');
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
@ -1,21 +1,18 @@
|
||||
import MagnifyingGlass16Icon from '@/components/icons/MagnifyingGlass16Icon';
|
||||
import classNames from 'classnames';
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
FocusEvent,
|
||||
FormEvent,
|
||||
KeyboardEvent,
|
||||
HTMLAttributes,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { Link, useMatch, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Cross } from '../components/icons/Cross';
|
||||
import { useDebounce } from '../logic/useDebounce';
|
||||
import { useErrorHandler } from '../logic/useErrorHandler';
|
||||
import { useMedia } from '../logic/useMedia';
|
||||
import { MenuState, useAppSearchStore } from './Nav';
|
||||
|
||||
function normalizePathEnding(path: string) {
|
||||
@ -34,12 +31,6 @@ export function createPreviousPath(current: string): string {
|
||||
return parts.join('/');
|
||||
}
|
||||
|
||||
type LeapProps = {
|
||||
menu: MenuState;
|
||||
dropdown: string;
|
||||
navOpen: boolean;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
function normalizeMatchString(match: string, keepAltChars: boolean): string {
|
||||
let normalizedString = match.toLocaleLowerCase().trim();
|
||||
|
||||
@ -50,260 +41,240 @@ function normalizeMatchString(match: string, keepAltChars: boolean): string {
|
||||
return normalizedString;
|
||||
}
|
||||
|
||||
export const AppSearch = React.forwardRef(
|
||||
({ menu, dropdown, navOpen, className }: LeapProps, ref) => {
|
||||
const { push } = useHistory();
|
||||
const deskMatch = useRouteMatch<{
|
||||
menu?: MenuState;
|
||||
query?: string;
|
||||
desk?: string;
|
||||
}>(`/${menu}/:query?/(apps)?/:desk?`);
|
||||
const appsMatch = useRouteMatch(`/${menu}/${deskMatch?.params.query}/apps`);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useImperativeHandle(ref, () => inputRef.current);
|
||||
const { rawInput, selectedMatch, matches, selection, select } =
|
||||
useAppSearchStore();
|
||||
const handleError = useErrorHandler();
|
||||
export const AppSearch = () => {
|
||||
const { menu, query } = useParams<{ menu: MenuState; query: string }>();
|
||||
const menuState = menu || 'closed';
|
||||
const isOpen =
|
||||
menuState !== 'upgrading' && menuState !== 'closed' && menuState !== 'app';
|
||||
const navigate = useNavigate();
|
||||
const appsMatch = useMatch(`${menuState}/${query}/apps/*`);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { rawInput, selectedMatch, matches, selection, select } =
|
||||
useAppSearchStore();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
useEffect(() => {
|
||||
const onTreaty = appsMatch && !appsMatch.isExact;
|
||||
if (selection && rawInput === '' && !onTreaty) {
|
||||
inputRef.current?.focus();
|
||||
} else if (selection && onTreaty) {
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
}, [selection, rawInput, appsMatch]);
|
||||
useEffect(() => {
|
||||
const newMatch = getMatch(rawInput);
|
||||
if (newMatch && rawInput) {
|
||||
useAppSearchStore.setState({ selectedMatch: newMatch });
|
||||
}
|
||||
}, [rawInput, matches]);
|
||||
|
||||
useEffect(() => {
|
||||
const newMatch = getMatch(rawInput);
|
||||
if (newMatch && rawInput) {
|
||||
useAppSearchStore.setState({ selectedMatch: newMatch });
|
||||
}
|
||||
}, [rawInput, matches]);
|
||||
useEffect(() => {
|
||||
if (menuState === 'search') {
|
||||
inputRef.current?.focus();
|
||||
} else {
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
}, [menuState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (menu === 'search') {
|
||||
inputRef.current?.focus();
|
||||
} else {
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
}, [menu]);
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menuState === 'search') {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (selection || menu === 'search') {
|
||||
navigate('/search');
|
||||
}, [selection, menuState]);
|
||||
|
||||
const onFocus = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
// refocusing tab with input focused is false trigger
|
||||
const windowFocus = e.nativeEvent.currentTarget === document.body;
|
||||
if (windowFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
push('/search');
|
||||
}, [selection, menu]);
|
||||
toggleSearch();
|
||||
},
|
||||
[toggleSearch]
|
||||
);
|
||||
|
||||
const onFocus = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
// refocusing tab with input focused is false trigger
|
||||
const windowFocus = e.nativeEvent.currentTarget === document.body;
|
||||
if (windowFocus) {
|
||||
return;
|
||||
}
|
||||
const getMatch = useCallback(
|
||||
(value: string) => {
|
||||
const onlySymbols = !value.match(/[\w]/g);
|
||||
const normValue = normalizeMatchString(value, onlySymbols);
|
||||
return matches.find((m) =>
|
||||
normalizeMatchString(m.value, onlySymbols).startsWith(normValue)
|
||||
);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
|
||||
toggleSearch();
|
||||
},
|
||||
[toggleSearch]
|
||||
);
|
||||
const navigateByInput = useCallback(
|
||||
(input: string) => {
|
||||
const normalizedValue = input
|
||||
.trim()
|
||||
.replace('%', '')
|
||||
.replace(/(~?[\w^_-]{3,56})\//, '$1/apps/$1/');
|
||||
navigate(`/${menuState}/${normalizedValue}`);
|
||||
},
|
||||
[menuState]
|
||||
);
|
||||
|
||||
const getMatch = useCallback(
|
||||
(value: string) => {
|
||||
const onlySymbols = !value.match(/[\w]/g);
|
||||
const normValue = normalizeMatchString(value, onlySymbols);
|
||||
return matches.find((m) =>
|
||||
normalizeMatchString(m.value, onlySymbols).startsWith(normValue)
|
||||
);
|
||||
},
|
||||
[matches]
|
||||
);
|
||||
const debouncedSearch = useDebounce(
|
||||
(input: string) => {
|
||||
useAppSearchStore.setState({ searchInput: input });
|
||||
navigateByInput(input);
|
||||
},
|
||||
300,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
const navigateByInput = useCallback(
|
||||
(input: string) => {
|
||||
const normalizedValue = input
|
||||
.trim()
|
||||
.replace('%', '')
|
||||
.replace(/(~?[\w^_-]{3,56})\//, '$1/apps/$1/');
|
||||
push(`/${menu}/${normalizedValue}`);
|
||||
},
|
||||
[menu]
|
||||
);
|
||||
const handleSearch = useCallback(debouncedSearch, []);
|
||||
|
||||
const debouncedSearch = useDebounce(
|
||||
(input: string) => {
|
||||
if (!deskMatch || appsMatch) {
|
||||
return;
|
||||
}
|
||||
const onChange = useCallback(
|
||||
handleError((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const isDeletion =
|
||||
(e.nativeEvent as InputEvent).inputType === 'deleteContentBackward';
|
||||
const inputMatch = getMatch(value);
|
||||
const matchValue = inputMatch?.value;
|
||||
|
||||
useAppSearchStore.setState({ searchInput: input });
|
||||
navigateByInput(input);
|
||||
},
|
||||
300,
|
||||
{ leading: true }
|
||||
);
|
||||
if (matchValue && inputRef.current && !isDeletion) {
|
||||
inputRef.current.value = matchValue;
|
||||
const start = matchValue.startsWith(value)
|
||||
? value.length
|
||||
: matchValue.substring(0, matchValue.indexOf(value)).length +
|
||||
value.length;
|
||||
inputRef.current.setSelectionRange(start, matchValue.length);
|
||||
useAppSearchStore.setState({
|
||||
rawInput: matchValue,
|
||||
selectedMatch: inputMatch,
|
||||
});
|
||||
} else {
|
||||
useAppSearchStore.setState({
|
||||
rawInput: value,
|
||||
selectedMatch: matches[0],
|
||||
});
|
||||
}
|
||||
|
||||
const handleSearch = useCallback(debouncedSearch, [deskMatch]);
|
||||
handleSearch(value);
|
||||
}),
|
||||
[matches, menu]
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
handleError((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const isDeletion =
|
||||
(e.nativeEvent as InputEvent).inputType === 'deleteContentBackward';
|
||||
const inputMatch = getMatch(value);
|
||||
const matchValue = inputMatch?.value;
|
||||
const onSubmit = useCallback(
|
||||
handleError((e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (matchValue && inputRef.current && !isDeletion) {
|
||||
inputRef.current.value = matchValue;
|
||||
const start = matchValue.startsWith(value)
|
||||
? value.length
|
||||
: matchValue.substring(0, matchValue.indexOf(value)).length +
|
||||
value.length;
|
||||
inputRef.current.setSelectionRange(start, matchValue.length);
|
||||
useAppSearchStore.setState({
|
||||
rawInput: matchValue,
|
||||
selectedMatch: inputMatch,
|
||||
});
|
||||
} else {
|
||||
useAppSearchStore.setState({
|
||||
rawInput: value,
|
||||
selectedMatch: matches[0],
|
||||
});
|
||||
}
|
||||
const value = inputRef.current?.value.trim();
|
||||
const currentMatch = selectedMatch || (value && getMatch(value));
|
||||
|
||||
handleSearch(value);
|
||||
}),
|
||||
[matches]
|
||||
);
|
||||
if (!currentMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onSubmit = useCallback(
|
||||
handleError((e: FormEvent<HTMLFormElement>) => {
|
||||
if (currentMatch?.openInNewTab) {
|
||||
window.open(currentMatch.url, currentMatch.value);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(currentMatch.url);
|
||||
useAppSearchStore.setState({ rawInput: '' });
|
||||
}),
|
||||
[selectedMatch]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
handleError((e: KeyboardEvent<HTMLDivElement>) => {
|
||||
const deletion = e.key === 'Backspace' || e.key === 'Delete';
|
||||
const arrow = e.key === 'ArrowDown' || e.key === 'ArrowUp';
|
||||
|
||||
if (deletion && !rawInput && selection) {
|
||||
e.preventDefault();
|
||||
select(null, appsMatch && !appsMatch.pattern.end ? undefined : query);
|
||||
navigate('..');
|
||||
}
|
||||
|
||||
const value = inputRef.current?.value.trim();
|
||||
const currentMatch = selectedMatch || (value && getMatch(value));
|
||||
|
||||
if (!currentMatch) {
|
||||
if (arrow) {
|
||||
e.preventDefault();
|
||||
if (matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMatch?.openInNewTab) {
|
||||
window.open(currentMatch.url, currentMatch.value);
|
||||
return;
|
||||
}
|
||||
const currentIndex = selectedMatch
|
||||
? matches.findIndex((m) => {
|
||||
const matchValue = m.value;
|
||||
const searchValue = selectedMatch.value;
|
||||
return matchValue === searchValue;
|
||||
})
|
||||
: 0;
|
||||
const unsafeIndex =
|
||||
e.key === 'ArrowUp' ? currentIndex - 1 : currentIndex + 1;
|
||||
const index = (unsafeIndex + matches.length) % matches.length;
|
||||
|
||||
push(currentMatch.url);
|
||||
useAppSearchStore.setState({ rawInput: '' });
|
||||
}),
|
||||
[deskMatch, selectedMatch]
|
||||
);
|
||||
const newMatch = matches[index];
|
||||
useAppSearchStore.setState({
|
||||
rawInput: newMatch.value,
|
||||
// searchInput: matchValue,
|
||||
selectedMatch: newMatch,
|
||||
});
|
||||
}
|
||||
}),
|
||||
[selection, rawInput, query, matches, selectedMatch]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
handleError((e: KeyboardEvent<HTMLDivElement>) => {
|
||||
const deletion = e.key === 'Backspace' || e.key === 'Delete';
|
||||
const arrow = e.key === 'ArrowDown' || e.key === 'ArrowUp';
|
||||
|
||||
if (deletion && !rawInput && selection) {
|
||||
e.preventDefault();
|
||||
select(
|
||||
null,
|
||||
appsMatch && !appsMatch.isExact
|
||||
? undefined
|
||||
: deskMatch?.params.query
|
||||
);
|
||||
const pathBack = createPreviousPath(deskMatch?.url || '');
|
||||
push(pathBack);
|
||||
}
|
||||
|
||||
if (arrow) {
|
||||
e.preventDefault();
|
||||
if (matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = selectedMatch
|
||||
? matches.findIndex((m) => {
|
||||
const matchValue = m.value;
|
||||
const searchValue = selectedMatch.value;
|
||||
return matchValue === searchValue;
|
||||
})
|
||||
: 0;
|
||||
const unsafeIndex =
|
||||
e.key === 'ArrowUp' ? currentIndex - 1 : currentIndex + 1;
|
||||
const index = (unsafeIndex + matches.length) % matches.length;
|
||||
|
||||
const newMatch = matches[index];
|
||||
useAppSearchStore.setState({
|
||||
rawInput: newMatch.value,
|
||||
// searchInput: matchValue,
|
||||
selectedMatch: newMatch,
|
||||
});
|
||||
}
|
||||
}),
|
||||
[selection, rawInput, deskMatch, matches, selectedMatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative z-50 w-full">
|
||||
<form
|
||||
className={classNames(
|
||||
'default-ring flex h-9 w-full items-center rounded-lg bg-white px-2 focus-within:ring-2',
|
||||
!navOpen ? 'bg-gray-50' : '',
|
||||
menu === 'upgrading' ? 'bg-orange-500' : '',
|
||||
className
|
||||
)}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<label
|
||||
htmlFor="leap"
|
||||
className={classNames(
|
||||
'h4 inline-block flex-none p-2 ',
|
||||
menu === 'upgrading'
|
||||
? 'text-white'
|
||||
: !selection
|
||||
? 'sr-only'
|
||||
: 'text-blue-400'
|
||||
)}
|
||||
>
|
||||
{menu === 'upgrading'
|
||||
? 'Your Urbit is being updated, this page will update when ready'
|
||||
: selection || 'Search'}
|
||||
</label>
|
||||
{menu !== 'upgrading' ? (
|
||||
<input
|
||||
id="leap"
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
placeholder={selection ? '' : 'e.g., ~paldev or ~paldev/pals'}
|
||||
// TODO: style placeholder text with 100% opacity.
|
||||
// Not immediately clear how to do this within tailwind.
|
||||
className="outline-none h-full w-full flex-1 bg-transparent px-2 text-lg text-gray-800 sm:text-base"
|
||||
value={rawInput}
|
||||
onClick={toggleSearch}
|
||||
onFocus={onFocus}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
autoComplete="off"
|
||||
aria-autocomplete="both"
|
||||
aria-controls={dropdown}
|
||||
aria-activedescendant={selectedMatch?.value}
|
||||
/>
|
||||
) : null}
|
||||
</form>
|
||||
{menu === 'search' && (
|
||||
<Link
|
||||
to="/get-apps"
|
||||
className="circle-button default-ring absolute top-1/2 right-2 h-8 w-8 flex-none -translate-y-1/2 text-gray-600"
|
||||
onClick={() => select(null)}
|
||||
>
|
||||
<Cross className="h-3 w-3" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Link>
|
||||
return (
|
||||
<div className="relative z-50 mb-4 w-full">
|
||||
<form
|
||||
className={classNames(
|
||||
'default-ring flex h-9 w-full items-center rounded-lg bg-gray-50 px-2 focus-within:ring-2',
|
||||
!isOpen ? 'bg-gray-50' : '',
|
||||
menuState === 'upgrading' ? 'bg-orange-500' : ''
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<label
|
||||
htmlFor="leap"
|
||||
className={classNames(
|
||||
'h4 inline-block flex-none p-2 ',
|
||||
menuState === 'upgrading'
|
||||
? 'text-white'
|
||||
: !selection
|
||||
? 'sr-only'
|
||||
: 'text-blue-400'
|
||||
)}
|
||||
>
|
||||
{menuState === 'upgrading'
|
||||
? 'Your Urbit is being updated, this page will update when ready'
|
||||
: selection || 'Search'}
|
||||
</label>
|
||||
{menuState !== 'upgrading' ? (
|
||||
<MagnifyingGlass16Icon className="ml-2 mt-1 h-4 w-4 text-gray-600" />
|
||||
) : null}
|
||||
{menuState !== 'upgrading' ? (
|
||||
<input
|
||||
id="leap"
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
placeholder={selection ? '' : 'e.g., ~paldev or ~paldev/pals'}
|
||||
// TODO: style placeholder text with 100% opacity.
|
||||
// Not immediately clear how to do this within tailwind.
|
||||
className="outline-none h-full w-full flex-1 rounded-md bg-gray-50 px-2 text-lg text-gray-800 sm:text-base"
|
||||
value={rawInput}
|
||||
onClick={toggleSearch}
|
||||
onFocus={onFocus}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
autoFocus={menuState === 'search'}
|
||||
autoComplete="off"
|
||||
aria-autocomplete="both"
|
||||
aria-controls="search-items"
|
||||
aria-activedescendant={selectedMatch?.value}
|
||||
/>
|
||||
) : null}
|
||||
</form>
|
||||
{menuState === 'search' && (
|
||||
<Link
|
||||
to="/get-apps"
|
||||
className="circle-button default-ring absolute top-1/2 right-2 h-8 w-8 flex-none -translate-y-1/2 text-gray-600"
|
||||
onClick={() => select(null)}
|
||||
>
|
||||
<Cross className="h-3 w-3" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,28 +1,24 @@
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import WayfindingAppLink from '../components/WayfindingAppLink';
|
||||
import { useCharges } from '../state/docket';
|
||||
import { APPS, SECTIONS } from '../constants';
|
||||
import { useMedia } from '../logic/useMedia';
|
||||
import { AppSearch } from './AppSearch';
|
||||
|
||||
export default function GetApps() {
|
||||
const charges = useCharges();
|
||||
const isMobile = useMedia('(max-width: 639px)');
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col space-y-8 overflow-y-scroll p-8">
|
||||
<h1 className="text-xl font-bold text-gray-800">Find Urbit Apps</h1>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h2 className="font-semibold text-gray-800">
|
||||
Find Urbit App Developers
|
||||
Browse by Developer ID or Shortcode
|
||||
</h2>
|
||||
<span>
|
||||
Use the search field {isMobile ? 'below' : 'above'} to find apps or
|
||||
ships hosting apps.
|
||||
</span>
|
||||
<AppSearch />
|
||||
</div>
|
||||
{Object.entries(SECTIONS).map(([key, name]) => (
|
||||
<div key={key} className="flex flex-col space-y-2">
|
||||
<h2 className="text-lg font-bold text-gray-800">{name}</h2>
|
||||
<h2 className="text-lg font-bold text-gray-400">{name}</h2>
|
||||
<div className="flex flex-col space-y-2 px-2">
|
||||
{APPS.map((app) => {
|
||||
if (app.section === name) {
|
||||
@ -56,6 +52,18 @@ export default function GetApps() {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<p className="text-sm">
|
||||
You can find more software in the Urbit Foundation's{' '}
|
||||
<a
|
||||
className="underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://urbit.org/ecosystem?type=applications"
|
||||
>
|
||||
directory
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,16 +13,15 @@ import {
|
||||
Link,
|
||||
LinkProps,
|
||||
Route,
|
||||
Switch,
|
||||
useHistory,
|
||||
useRouteMatch,
|
||||
Routes,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import create from 'zustand';
|
||||
import { Avatar } from '../components/Avatar';
|
||||
import { Dialog } from '../components/Dialog';
|
||||
import { ErrorAlert } from '../components/ErrorAlert';
|
||||
import { Help } from './Help';
|
||||
import { AppSearch } from './AppSearch';
|
||||
import { Notifications } from './notifications/Notifications';
|
||||
import { NotificationsLink } from './notifications/NotificationsLink';
|
||||
import { Search } from './Search';
|
||||
@ -31,7 +30,6 @@ import { useSystemUpdate } from '../logic/useSystemUpdate';
|
||||
import useVereState from '../state/vere';
|
||||
import { Bullet } from '../components/icons/Bullet';
|
||||
import { Cross } from '../components/icons/Cross';
|
||||
import MagnifyingGlass16Icon from '../components/icons/MagnifyingGlass16Icon';
|
||||
import GetApps from './GetApps';
|
||||
import LandscapeWayfinding from '../components/LandscapeWayfinding';
|
||||
import { useCalm } from '../state/settings';
|
||||
@ -78,13 +76,9 @@ export type MenuState =
|
||||
| 'system-preferences'
|
||||
| 'upgrading';
|
||||
|
||||
interface NavProps {
|
||||
menu?: MenuState;
|
||||
}
|
||||
|
||||
type PrefsLinkProps = Omit<LinkProps<HTMLAnchorElement>, 'to'> & {
|
||||
type PrefsLinkProps = Omit<LinkProps, 'to'> & {
|
||||
menuState: string;
|
||||
systemBlocked?: string[];
|
||||
systemBlocked?: boolean;
|
||||
};
|
||||
|
||||
export const SystemPrefsLink = ({
|
||||
@ -122,9 +116,8 @@ export const GetAppsLink = () => {
|
||||
return (
|
||||
<Link
|
||||
to="/get-apps"
|
||||
className="flex h-9 w-[150px] items-center justify-center space-x-2 rounded-lg bg-blue-soft px-3 py-2.5"
|
||||
className="flex h-9 w-[125px] items-center justify-center space-x-2 rounded-lg bg-blue-soft px-3 py-2.5"
|
||||
>
|
||||
<MagnifyingGlass16Icon className="h-4 w-4 fill-current text-blue" />
|
||||
<span className="whitespace-nowrap font-semibold text-blue">
|
||||
Get Urbit Apps
|
||||
</span>
|
||||
@ -132,18 +125,17 @@ export const GetAppsLink = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
const { push } = useHistory();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
export const Nav: FunctionComponent = () => {
|
||||
const navigate = useNavigate();
|
||||
const { menu } = useParams<{ menu: MenuState }>();
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
const dialogNavRef = useRef<HTMLDivElement>(null);
|
||||
const { disableWayfinding } = useCalm();
|
||||
const { systemBlocked } = useSystemUpdate();
|
||||
const { isLatest, loaded } = useVereState();
|
||||
const [dialogContentOpen, setDialogContentOpen] = useState(false);
|
||||
const select = useAppSearchStore((state) => state.select);
|
||||
|
||||
const runtimeOutOfDate = (loaded && !(isLatest));
|
||||
const runtimeOutOfDate = loaded && !isLatest;
|
||||
|
||||
const menuState = menu || 'closed';
|
||||
const isOpen =
|
||||
@ -156,57 +148,28 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const onOpen = useCallback(
|
||||
(event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
setDialogContentOpen(true);
|
||||
if (menu === 'search' && inputRef.current) {
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[menu]
|
||||
);
|
||||
|
||||
const onDialogClose = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
push('/');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const preventClose = useCallback((e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const hasNavAncestor = target.closest('#dialog-nav');
|
||||
|
||||
if (hasNavAncestor) {
|
||||
e.preventDefault();
|
||||
navigate('/');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorAlert} onReset={() => push('/')}>
|
||||
<ErrorBoundary FallbackComponent={ErrorAlert} onReset={() => navigate('/')}>
|
||||
{/* Using portal so that we can retain the same nav items both in the dialog and in the base header */}
|
||||
<Portal.Root
|
||||
containerRef={dialogContentOpen ? dialogNavRef : navRef}
|
||||
containerRef={navRef}
|
||||
className="flex w-full items-center space-x-2 sm:justify-center"
|
||||
>
|
||||
<SystemPrefsLink menuState={menuState} systemBlocked={systemBlocked || runtimeOutOfDate} />
|
||||
<SystemPrefsLink
|
||||
menuState={menuState}
|
||||
systemBlocked={!!systemBlocked || runtimeOutOfDate}
|
||||
/>
|
||||
<NotificationsLink
|
||||
navOpen={isOpen}
|
||||
notificationsOpen={menu === 'notifications'}
|
||||
/>
|
||||
{menuState === 'search' || menuState === 'get-apps' ? (
|
||||
<AppSearch
|
||||
ref={inputRef}
|
||||
menu={menuState}
|
||||
dropdown="leap-items"
|
||||
navOpen={isOpen}
|
||||
/>
|
||||
) : (
|
||||
<GetAppsLink />
|
||||
)}
|
||||
<GetAppsLink />
|
||||
{!disableWayfinding && <LandscapeWayfinding className="sm:hidden" />}
|
||||
</Portal.Root>
|
||||
|
||||
@ -223,32 +186,30 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
||||
/>
|
||||
<Dialog open={isOpen} onOpenChange={onDialogClose}>
|
||||
<DialogContent
|
||||
onInteractOutside={preventClose}
|
||||
onOpenAutoFocus={onOpen}
|
||||
className="scroll-left-50 scroll-full-width outline-none fixed bottom-0 z-50 flex h-full max-h-full max-w-[882px] -translate-x-1/2 flex-col justify-end px-4 text-gray-400 sm:top-0 sm:bottom-auto sm:h-auto sm:justify-start sm:pb-4"
|
||||
className="scroll-left-50 scroll-full-width outline-none fixed top-0 z-50 mt-4 flex h-auto max-w-[882px] -translate-x-1/2 flex-col justify-start px-4 text-gray-400 sm:bottom-auto sm:mt-12 sm:pb-4"
|
||||
role="combobox"
|
||||
aria-controls="leap-items"
|
||||
aria-owns="leap-items"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<header
|
||||
id="dialog-nav"
|
||||
ref={dialogNavRef}
|
||||
className="order-last mx-auto my-6 w-full max-w-[712px] sm:order-none sm:mb-3"
|
||||
/>
|
||||
<div
|
||||
id="leap-items"
|
||||
className="default-ring mt-4 grid grid-rows-[fit-content(calc(100vh-6.25rem))] overflow-hidden rounded-xl bg-white focus-visible:ring-2 sm:mt-0"
|
||||
className="default-ring grid grid-rows-[fit-content(calc(100vh-6.25rem))] overflow-hidden rounded-xl bg-white focus-visible:ring-2"
|
||||
tabIndex={0}
|
||||
role="listbox"
|
||||
>
|
||||
<Switch>
|
||||
<Route path="/notifications" component={Notifications} />
|
||||
<Route path="/system-preferences" component={SystemPreferences} />
|
||||
<Route path="/help-and-support" component={Help} />
|
||||
<Route path="/get-apps" component={GetApps} />
|
||||
<Route path={['/search']} component={Search} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="notifications" element={<Notifications />} />
|
||||
<Route
|
||||
path="system-preferences/*"
|
||||
element={<SystemPreferences />}
|
||||
>
|
||||
<Route path=":submenu/*" element={<SystemPreferences />} />
|
||||
</Route>
|
||||
<Route path="help-and-support" element={<Help />} />
|
||||
<Route path="get-apps" element={<GetApps />} />
|
||||
<Route path="search/*" element={<Search />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { TreatyInfo } from './search/TreatyInfo';
|
||||
import { Apps } from './search/Apps';
|
||||
@ -7,22 +7,22 @@ import { Home } from './search/Home';
|
||||
import { Providers } from './search/Providers';
|
||||
import { ErrorAlert } from '../components/ErrorAlert';
|
||||
|
||||
type SearchProps = RouteComponentProps<{
|
||||
query?: string;
|
||||
}>;
|
||||
|
||||
export const Search = ({ match, history }: SearchProps) => {
|
||||
export const Search = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorAlert} onReset={() => history.push('/leap/search')}>
|
||||
<Switch>
|
||||
<Route
|
||||
path={[`${match.path}/direct/apps/:host/:desk`, `${match.path}/:ship/apps/:host/:desk`]}
|
||||
component={TreatyInfo}
|
||||
/>
|
||||
<Route path={`${match.path}/:ship/apps`} component={Apps} />
|
||||
<Route path={`${match.path}/:ship`} component={Providers} />
|
||||
<Route path={`${match.path}`} component={Home} />
|
||||
</Switch>
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorAlert}
|
||||
onReset={() => navigate('/leap/search')}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col p-4">
|
||||
<Routes>
|
||||
<Route path="direct/apps/:host/:desk" element={<TreatyInfo />} />
|
||||
<Route path=":ship/apps/:host/:desk" element={<TreatyInfo />} />
|
||||
<Route path=":ship/apps" element={<Apps />} />
|
||||
<Route path=":ship" element={<Providers />} />
|
||||
<Route index element={<Home />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
import clipboardCopy from 'clipboard-copy';
|
||||
import React, { HTMLAttributes, useCallback, useState } from 'react';
|
||||
import { Link, Route, useHistory } from 'react-router-dom';
|
||||
import { Link, Route, useNavigate } from 'react-router-dom';
|
||||
import { Pike } from '@/gear';
|
||||
import { Adjust } from '../components/icons/Adjust';
|
||||
import { usePike } from '../state/kiln';
|
||||
@ -28,7 +28,7 @@ export const SystemMenu = ({
|
||||
subMenuOpen,
|
||||
shouldDim,
|
||||
}: SystemMenuProps) => {
|
||||
const { push } = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const garden = usePike(window.desk);
|
||||
const hash = garden ? getHash(garden) : null;
|
||||
@ -67,7 +67,9 @@ export const SystemMenu = ({
|
||||
<DropdownMenu.Root
|
||||
modal={false}
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => setTimeout(() => !isOpen && push('/'), 15)}
|
||||
onOpenChange={(isOpen) =>
|
||||
setTimeout(() => !isOpen && navigate('/'), 15)
|
||||
}
|
||||
>
|
||||
<Link
|
||||
to={open || subMenuOpen ? '/' : '/system-menu'}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import cn from 'classnames';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ErrorAlert } from '../../components/ErrorAlert';
|
||||
import { useGroups } from './groups';
|
||||
import Notification from './Notification';
|
||||
@ -59,14 +59,15 @@ function NotificationPlaceholder() {
|
||||
);
|
||||
}
|
||||
|
||||
export const Notifications = ({ history }: RouteComponentProps) => {
|
||||
export const Notifications = () => {
|
||||
const navigate = useNavigate();
|
||||
const { notifications, count, loaded } = useNotifications();
|
||||
const groups = useGroups();
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorAlert}
|
||||
onReset={() => history.push('/leap/notifications')}
|
||||
onReset={() => navigate('/leap/notifications')}
|
||||
>
|
||||
<div className="h-full overflow-y-scroll p-4 pr-2 md:p-9 md:pr-7">
|
||||
<div className="mb-4 flex w-full items-center justify-between">
|
||||
|
@ -25,7 +25,7 @@ function getNotificationsState(
|
||||
return 'unread';
|
||||
}
|
||||
|
||||
type NotificationsLinkProps = Omit<LinkProps<HTMLAnchorElement>, 'to'> & {
|
||||
type NotificationsLinkProps = Omit<LinkProps, 'to'> & {
|
||||
navOpen: boolean;
|
||||
notificationsOpen: boolean;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import fuzzy from 'fuzzy';
|
||||
import { Treaty } from '@/gear';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
@ -8,16 +8,17 @@ import { useAppSearchStore } from '../Nav';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { addRecentDev } from './Home';
|
||||
import { Spinner } from '../../components/Spinner';
|
||||
import { AppSearch } from '../AppSearch';
|
||||
|
||||
type AppsProps = RouteComponentProps<{ ship: string }>;
|
||||
|
||||
export const Apps = ({ match }: AppsProps) => {
|
||||
export const Apps = () => {
|
||||
const { searchInput, selectedMatch, select } = useAppSearchStore((state) => ({
|
||||
searchInput: state.searchInput,
|
||||
select: state.select,
|
||||
selectedMatch: state.selectedMatch
|
||||
selectedMatch: state.selectedMatch,
|
||||
}));
|
||||
const provider = match?.params.ship;
|
||||
const { ship = '' } = useParams<{ ship: string }>();
|
||||
const { pathname } = useLocation();
|
||||
const provider = ship;
|
||||
const { treaties, status } = useAllyTreaties(provider);
|
||||
|
||||
useEffect(() => {
|
||||
@ -47,8 +48,9 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
const count = results?.length;
|
||||
|
||||
const getAppPath = useCallback(
|
||||
(app: Treaty) => `${match?.path.replace(':ship', provider)}/${app.ship}/${app.desk}`,
|
||||
[match]
|
||||
(app: Treaty) =>
|
||||
`${pathname.replace(':ship', provider)}/${app.ship}/${app.desk}`,
|
||||
[pathname]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -66,27 +68,30 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
url: getAppPath(r),
|
||||
openInNewTab: false,
|
||||
value: r.desk,
|
||||
display: r.title
|
||||
}))
|
||||
display: r.title,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}, [results]);
|
||||
|
||||
const showNone =
|
||||
status === 'error' || ((status === 'success' || status === 'initial') && results?.length === 0);
|
||||
status === 'error' ||
|
||||
((status === 'success' || status === 'initial') && results?.length === 0);
|
||||
|
||||
return (
|
||||
<div className="dialog-inner-container md:px-6 md:py-8 h4 text-gray-400">
|
||||
<div className="dialog-inner-container h4 text-gray-400 md:px-6 md:py-8">
|
||||
<AppSearch />
|
||||
{status === 'loading' && (
|
||||
<span className="mb-3">
|
||||
<Spinner className="w-7 h-7 mr-3" /> Finding software...
|
||||
<Spinner className="mr-3 h-7 w-7" /> Finding software...
|
||||
</span>
|
||||
)}
|
||||
{results && results.length > 0 && (
|
||||
<>
|
||||
<div id="developed-by">
|
||||
<h2 className="mb-3">
|
||||
Software developed by <ShipName name={provider} className="font-mono" />
|
||||
Software developed by{' '}
|
||||
<ShipName name={provider} className="font-mono" />
|
||||
</h2>
|
||||
<p>
|
||||
{count} result{count === 1 ? '' : 's'}
|
||||
@ -103,7 +108,8 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
)}
|
||||
{showNone && (
|
||||
<h2>
|
||||
Unable to find software developed by <ShipName name={provider} className="font-mono" />
|
||||
Unable to find software developed by{' '}
|
||||
<ShipName name={provider} className="font-mono" />
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
getAppHref,
|
||||
} from '@/logic/utils';
|
||||
import useContactState, { emptyContact } from '../../state/contact';
|
||||
import { AppSearch } from '../AppSearch';
|
||||
|
||||
export interface RecentsStore {
|
||||
recentApps: string[];
|
||||
@ -126,8 +127,9 @@ export const Home = () => {
|
||||
}, [recentApps, recentDevs]);
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto p-4 font-semibold leading-tight text-black md:p-8">
|
||||
<h2 id="recent-apps" className="h4 mb-4 text-gray-500">
|
||||
<div className="h-full overflow-y-auto p-4 font-semibold leading-tight text-black md:px-6 md:py-8">
|
||||
<AppSearch />
|
||||
<h2 id="recent-apps" className="h4 mt-4 mb-4 text-gray-500">
|
||||
Recent Apps
|
||||
</h2>
|
||||
{apps.length === 0 && (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import fuzzy from 'fuzzy';
|
||||
import { deSig, isValidPatp } from '@urbit/aura';
|
||||
import { Provider } from '@/gear';
|
||||
@ -9,8 +9,7 @@ import { ProviderList } from '../../components/ProviderList';
|
||||
import useContactState, { emptyContact } from '../../state/contact';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { getAppHref } from '@/logic/utils';
|
||||
|
||||
type ProvidersProps = RouteComponentProps<{ ship: string }>;
|
||||
import { AppSearch } from '../AppSearch';
|
||||
|
||||
export function providerMatch(provider: Provider | string): MatchItem {
|
||||
const value = typeof provider === 'string' ? provider : provider.shipName;
|
||||
@ -36,9 +35,10 @@ function fuzzySort(search: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export const Providers = ({ match }: ProvidersProps) => {
|
||||
export const Providers = () => {
|
||||
const { ship } = useParams<{ ship: string }>();
|
||||
const selectedMatch = useAppSearchStore((state) => state.selectedMatch);
|
||||
const provider = match?.params.ship;
|
||||
const provider = ship;
|
||||
const contacts = useContactState((s) => s.contacts);
|
||||
const charges = useCharges();
|
||||
const allies = useAllies();
|
||||
@ -135,6 +135,7 @@ export const Providers = ({ match }: ProvidersProps) => {
|
||||
className="dialog-inner-container h4 space-y-0 text-gray-400 md:px-6 md:py-8"
|
||||
aria-live="polite"
|
||||
>
|
||||
<AppSearch />
|
||||
{appResults && !(results?.length > 0 && appResults.length === 0) && (
|
||||
<div>
|
||||
<h2 id="installed" className="mb-3">
|
||||
|
@ -6,10 +6,11 @@ import useDocketState, { useCharge, useTreaty } from '../../state/docket';
|
||||
import { usePike } from '../../state/kiln';
|
||||
import { getAppName } from '@/logic/utils';
|
||||
import { useAppSearchStore } from '../Nav';
|
||||
import { AppSearch } from '../AppSearch';
|
||||
|
||||
export const TreatyInfo = () => {
|
||||
const select = useAppSearchStore((state) => state.select);
|
||||
const { host, desk } = useParams<{ host: string; desk: string }>();
|
||||
const { host = '', desk = '' } = useParams<{ host: string; desk: string }>();
|
||||
const treaty = useTreaty(host, desk);
|
||||
const pike = usePike(desk);
|
||||
const charge = useCharge(desk);
|
||||
@ -35,11 +36,14 @@ export const TreatyInfo = () => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AppInfo
|
||||
treatyInfoShip={treaty.ship}
|
||||
className="dialog-inner-container"
|
||||
docket={charge || treaty}
|
||||
pike={pike}
|
||||
/>
|
||||
<div className="flex h-full w-full flex-col p-4">
|
||||
<AppSearch />
|
||||
<AppInfo
|
||||
treatyInfoShip={treaty.ship}
|
||||
className="dialog-inner-container"
|
||||
docket={charge || treaty}
|
||||
pike={pike}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent, useEffect } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Route, useHistory, useParams } from 'react-router-dom';
|
||||
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
import { ErrorAlert } from '../components/ErrorAlert';
|
||||
import LandscapeWayfinding from '../components/LandscapeWayfinding';
|
||||
import { MenuState, Nav } from '../nav/Nav';
|
||||
@ -11,13 +11,9 @@ import { SuspendApp } from '../tiles/SuspendApp';
|
||||
import { TileGrid } from '../tiles/TileGrid';
|
||||
import { TileInfo } from '../tiles/TileInfo';
|
||||
|
||||
interface RouteProps {
|
||||
menu?: MenuState;
|
||||
}
|
||||
|
||||
export const Grid: FunctionComponent = () => {
|
||||
const { push } = useHistory();
|
||||
const { menu } = useParams<RouteProps>();
|
||||
const navigate = useNavigate();
|
||||
const { menu } = useParams<{ menu: MenuState }>();
|
||||
const { disableWayfinding } = useCalm();
|
||||
|
||||
useEffect(() => {
|
||||
@ -33,7 +29,7 @@ export const Grid: FunctionComponent = () => {
|
||||
if (performance.now() - start > 5000) {
|
||||
attempt(count + 1);
|
||||
} else {
|
||||
push('/');
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
if (menu === 'upgrading') {
|
||||
@ -44,21 +40,20 @@ export const Grid: FunctionComponent = () => {
|
||||
return (
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<header className="fixed bottom-0 left-0 z-30 flex w-full justify-center px-4 sm:sticky sm:bottom-auto sm:top-0">
|
||||
<Nav menu={menu} />
|
||||
<Nav />
|
||||
</header>
|
||||
|
||||
<main className="relative z-0 flex h-full w-full justify-center pt-4 pb-32 md:pt-16">
|
||||
<TileGrid menu={menu} />
|
||||
<ErrorBoundary FallbackComponent={ErrorAlert} onReset={() => push('/')}>
|
||||
<Route exact path="/app/:desk">
|
||||
<TileInfo />
|
||||
</Route>
|
||||
<Route exact path="/app/:desk/suspend">
|
||||
<SuspendApp />
|
||||
</Route>
|
||||
<Route exact path="/app/:desk/remove">
|
||||
<RemoveApp />
|
||||
</Route>
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorAlert}
|
||||
onReset={() => navigate('/')}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="app/:desk" element={<TileInfo/>}/>
|
||||
<Route path="app/:desk/suspend" element={<SuspendApp/>}/>
|
||||
<Route path="app/:desk/remove" element={<RemoveApp/>}/>
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
{!disableWayfinding && (
|
||||
<LandscapeWayfinding className="hidden sm:fixed sm:bottom-4 sm:left-4 sm:z-[100] sm:block" />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Pikes } from '@/gear';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { Route, Navigate, Routes, useParams } from 'react-router-dom';
|
||||
import { Spinner } from '../components/Spinner';
|
||||
import { useQuery } from '../logic/useQuery';
|
||||
import { useCharge } from '../state/docket';
|
||||
@ -18,28 +18,24 @@ function getDeskByForeignRef(
|
||||
return found ? found[0] : undefined;
|
||||
}
|
||||
|
||||
type AppLinkProps = RouteComponentProps<{
|
||||
ship: string;
|
||||
desk: string;
|
||||
link: string;
|
||||
}>;
|
||||
|
||||
function AppLink({ match, history, location }: AppLinkProps) {
|
||||
const { ship, desk, link = '' } = match.params;
|
||||
function AppLink() {
|
||||
const {
|
||||
ship = '',
|
||||
desk = '',
|
||||
link = '',
|
||||
} = useParams<{ ship: string; desk: string; link: string }>();
|
||||
const pikes = usePikes();
|
||||
const ourDesk = getDeskByForeignRef(pikes, ship, desk);
|
||||
|
||||
if (ourDesk) {
|
||||
return <AppLinkRedirect desk={ourDesk} link={link} />;
|
||||
return <AppLinkNavigate desk={ourDesk} link={link} />;
|
||||
}
|
||||
return (
|
||||
<AppLinkNotFound match={match} history={history} location={location} />
|
||||
);
|
||||
return <AppLinkNotFound />;
|
||||
}
|
||||
|
||||
function AppLinkNotFound({ match }: AppLinkProps) {
|
||||
const { ship, desk } = match.params;
|
||||
return <Redirect to={`/leap/search/direct/apps/${ship}/${desk}`} />;
|
||||
function AppLinkNotFound() {
|
||||
const { ship, desk } = useParams<{ ship: string; desk: string }>();
|
||||
return <Navigate to={`/leap/search/direct/apps/${ship}/${desk}`} />;
|
||||
}
|
||||
|
||||
function AppLinkInvalid() {
|
||||
@ -50,7 +46,7 @@ function AppLinkInvalid() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function AppLinkRedirect({ desk, link }: { desk: string; link: string }) {
|
||||
function AppLinkNavigate({ desk, link }: { desk: string; link: string }) {
|
||||
const charge = useCharge(desk);
|
||||
|
||||
useEffect(() => {
|
||||
@ -66,17 +62,17 @@ function AppLinkRedirect({ desk, link }: { desk: string; link: string }) {
|
||||
window.open(url, desk);
|
||||
}, [charge]);
|
||||
|
||||
return <Redirect to="/" />;
|
||||
return <Navigate to="/" />;
|
||||
}
|
||||
|
||||
const LANDSCAPE_DESK = 'landscape';
|
||||
const LANDSCAPE_HOST = '~lander-dister-dozzod-dozzod';
|
||||
|
||||
function LandscapeLink({ match }: RouteComponentProps<{ link: string }>) {
|
||||
const { link } = match.params;
|
||||
function LandscapeLink() {
|
||||
const { link } = useParams<{ link: string }>();
|
||||
|
||||
return (
|
||||
<Redirect to={`/perma/${LANDSCAPE_HOST}/${LANDSCAPE_DESK}/group/${link}`} />
|
||||
<Navigate to={`/perma/${LANDSCAPE_HOST}/${LANDSCAPE_DESK}/group/${link}`} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -88,7 +84,7 @@ export function PermalinkRoutes() {
|
||||
if (query.has('ext')) {
|
||||
const ext = query.get('ext')!;
|
||||
const url = `/perma${ext.slice(16)}`;
|
||||
return <Redirect to={url} />;
|
||||
return <Navigate to={url} />;
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
@ -96,10 +92,10 @@ export function PermalinkRoutes() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/perma/group/:link+" component={LandscapeLink} />
|
||||
<Route path="/perma/:ship/:desk/:link*" component={AppLink} />
|
||||
<Route path="/" component={AppLinkInvalid} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="perma/group/:link+" element={<LandscapeLink />} />
|
||||
<Route path="perma/:ship/:desk/:link*" element={<AppLink />} />
|
||||
<Route index element={<AppLinkInvalid />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useCharge } from '../state/docket';
|
||||
import useKilnState, { usePike } from '../state/kiln';
|
||||
import { getAppName } from '@/logic/utils';
|
||||
import SourceSetter from '../components/SourceSetter';
|
||||
|
||||
export const AppPrefs = ({ match }: RouteComponentProps<{ desk: string }>) => {
|
||||
const { desk } = match.params;
|
||||
export const AppPrefs = () => {
|
||||
const { desk = '' } = useParams<{ desk: string }>();
|
||||
const charge = useCharge(desk);
|
||||
const appName = getAppName(charge);
|
||||
const pike = usePike(desk);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { ChangeEvent, KeyboardEvent, useEffect, useState } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import fuzzy from 'fuzzy';
|
||||
import classNames from 'classnames';
|
||||
import MagnifyingGlassIcon from '../components/icons/MagnifyingGlassIcon';
|
||||
@ -12,57 +12,59 @@ import LogoutIcon from '../components/icons/LogoutIcon';
|
||||
import PencilIcon from '../components/icons/PencilIcon';
|
||||
import ForwardSlashIcon from '../components/icons/ForwardSlashIcon';
|
||||
|
||||
const navOptions: { route: string; title: string; icon: React.ReactElement }[] = [
|
||||
type NavOption = {
|
||||
route: string;
|
||||
title: string;
|
||||
icon: React.ReactElement;
|
||||
};
|
||||
|
||||
const navOptions: NavOption[] = [
|
||||
{
|
||||
route: 'help',
|
||||
title: 'Help and Support',
|
||||
icon: <HelpIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <HelpIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'interface',
|
||||
title: 'Interface Settings',
|
||||
icon: <Interface className="w-4 h-4 text-gray-600" />
|
||||
icon: <Interface className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'notifications',
|
||||
title: 'Notifications',
|
||||
icon: <BellIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <BellIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'appearance',
|
||||
title: 'Appearance',
|
||||
icon: <PencilIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <PencilIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'shortcuts',
|
||||
title: 'Shortcuts',
|
||||
icon: <ForwardSlashIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <ForwardSlashIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'privacy',
|
||||
title: 'Attention & Privacy',
|
||||
icon: <BurstIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <BurstIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'security',
|
||||
title: 'Log Out...',
|
||||
icon: <LogoutIcon className="w-4 h-4 text-gray-600" />
|
||||
icon: <LogoutIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
{
|
||||
route: 'system-updates',
|
||||
title: 'About System',
|
||||
icon: <TlonIcon className="w-4 h-4 text-gray-600" />
|
||||
}
|
||||
icon: <TlonIcon className="h-4 w-4 text-gray-600" />,
|
||||
},
|
||||
];
|
||||
|
||||
interface SearchSystemPrefencesProps {
|
||||
subUrl: (submenu: string) => string;
|
||||
}
|
||||
|
||||
export default function SearchSystemPreferences({ subUrl }: SearchSystemPrefencesProps) {
|
||||
const { push } = useHistory();
|
||||
export default function SearchSystemPreferences() {
|
||||
const navigate = useNavigate();
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [matchingNavOptions, setMatchingNavOptions] = useState<string[]>([]);
|
||||
const [matchingNavOptions, setMatchingNavOptions] = useState<NavOption[]>([]);
|
||||
const [highlightNavOption, setHighlightNavOption] = useState<number>();
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -74,7 +76,22 @@ export default function SearchSystemPreferences({ subUrl }: SearchSystemPrefence
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
const { key } = e;
|
||||
if (key === 'ArrowDown' && searchInput !== '' && matchingNavOptions.length > 0) {
|
||||
|
||||
if (searchInput === '') {
|
||||
setHighlightNavOption(undefined);
|
||||
}
|
||||
|
||||
if (key === 'Escape') {
|
||||
setSearchInput('');
|
||||
setHighlightNavOption(undefined);
|
||||
}
|
||||
|
||||
if (
|
||||
key === 'ArrowDown' &&
|
||||
searchInput !== '' &&
|
||||
matchingNavOptions.length > 0 &&
|
||||
highlightNavOption !== matchingNavOptions.length - 1
|
||||
) {
|
||||
if (highlightNavOption === undefined) {
|
||||
setHighlightNavOption(0);
|
||||
} else {
|
||||
@ -92,8 +109,13 @@ export default function SearchSystemPreferences({ subUrl }: SearchSystemPrefence
|
||||
setHighlightNavOption((prevState) => prevState! - 1);
|
||||
}
|
||||
|
||||
if (key === 'Enter' && searchInput !== '' && highlightNavOption !== undefined) {
|
||||
push(subUrl(navOptions[highlightNavOption].route));
|
||||
if (
|
||||
key === 'Enter' &&
|
||||
searchInput !== '' &&
|
||||
highlightNavOption !== undefined
|
||||
) {
|
||||
navigate(matchingNavOptions[highlightNavOption].route);
|
||||
setHighlightNavOption(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
@ -102,20 +124,26 @@ export default function SearchSystemPreferences({ subUrl }: SearchSystemPrefence
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const results = fuzzy.filter(searchInput, navOptions, { extract: (obj) => obj.title });
|
||||
const results = fuzzy.filter(searchInput, navOptions, {
|
||||
extract: (obj) => obj.title,
|
||||
});
|
||||
const matches = results.map((el) => el.string);
|
||||
setMatchingNavOptions(matches);
|
||||
const matchedNavOptions = navOptions.filter((navOpt) =>
|
||||
matches.includes(navOpt.title)
|
||||
);
|
||||
|
||||
setMatchingNavOptions(matchedNavOptions);
|
||||
}, [searchInput]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="relative flex items-center">
|
||||
<span className="sr-only">Search Prefences</span>
|
||||
<span className="absolute h-8 w-8 text-gray-400 flex items-center pl-2 inset-y-1 left-0">
|
||||
<span className="absolute inset-y-1 left-0 flex h-8 w-8 items-center pl-2 text-gray-400">
|
||||
<MagnifyingGlassIcon />
|
||||
</span>
|
||||
<input
|
||||
className="input bg-gray-50 pl-8 placeholder:font-semibold mb-5 h-10"
|
||||
className="input mb-5 h-10 bg-gray-50 pl-8 placeholder:font-semibold"
|
||||
placeholder="Search Preferences"
|
||||
value={searchInput}
|
||||
onChange={handleChange}
|
||||
@ -125,22 +153,26 @@ export default function SearchSystemPreferences({ subUrl }: SearchSystemPrefence
|
||||
</label>
|
||||
<div className="relative">
|
||||
{matchingNavOptions.length > 0 && searchInput !== '' ? (
|
||||
<div className="absolute -top-3 flex flex-col bg-white space-y-2 rounded-2xl shadow-md w-full py-3">
|
||||
<div className="absolute -top-3 flex w-full flex-col space-y-2 rounded-2xl bg-white py-3 shadow-md">
|
||||
{matchingNavOptions.map((opt, index) => {
|
||||
const matchingNavOption = navOptions.find((navOpt) => navOpt.title === opt);
|
||||
const matchingNavOption = navOptions.find(
|
||||
(navOpt) => navOpt.title === opt.title
|
||||
);
|
||||
if (matchingNavOption !== undefined) {
|
||||
return (
|
||||
<Link
|
||||
className={classNames(
|
||||
'flex px-2 py-3 items-center space-x-2 hover:text-black hover:bg-gray-50',
|
||||
'flex items-center space-x-2 px-2 py-3 hover:bg-gray-50 hover:text-black',
|
||||
{
|
||||
'bg-gray-50': highlightNavOption === index
|
||||
'bg-gray-50': highlightNavOption === index,
|
||||
}
|
||||
)}
|
||||
to={subUrl(matchingNavOption.route)}
|
||||
to={matchingNavOption.route}
|
||||
>
|
||||
{matchingNavOption.icon}
|
||||
<span className="text-gray-900">{matchingNavOption?.title}</span>
|
||||
<span className="text-gray-900">
|
||||
{matchingNavOption?.title}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button } from '../components/Button';
|
||||
import { Checkbox } from '../components/Checkbox';
|
||||
import { Dialog, DialogContent } from '../components/Dialog';
|
||||
|
||||
export const SecurityPrefs = () => {
|
||||
const [allSessions, setAllSessions] = useState(false);
|
||||
const { push } = useHistory();
|
||||
|
||||
return (
|
||||
<div className="inner-section space-y-8">
|
||||
|
@ -2,9 +2,11 @@ import React, { PropsWithChildren, useCallback } from 'react';
|
||||
import {
|
||||
Link,
|
||||
Route,
|
||||
RouteComponentProps,
|
||||
Switch,
|
||||
useRouteMatch,
|
||||
Routes,
|
||||
useLocation,
|
||||
useMatch,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import classNames from 'classnames';
|
||||
@ -19,7 +21,7 @@ import { StoragePrefs } from './StoragePrefs';
|
||||
import { InvitePrefs } from './InvitePrefs';
|
||||
import { DocketImage } from '../components/DocketImage';
|
||||
import { ErrorAlert } from '../components/ErrorAlert';
|
||||
import { useMedia } from '../logic/useMedia';
|
||||
import { useIsMobile, useMedia } from '../logic/useMedia';
|
||||
import { LeftArrow } from '../components/icons/LeftArrow';
|
||||
import { getAppName } from '@/logic/utils';
|
||||
import { Help } from '../nav/Help';
|
||||
@ -40,13 +42,13 @@ import { ShortcutPrefs } from './ShortcutPrefs';
|
||||
import { AttentionAndPrivacy } from './AttentionAndPrivacy';
|
||||
|
||||
interface SystemPreferencesSectionProps {
|
||||
url: string;
|
||||
to: string;
|
||||
active: boolean;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
function SystemPreferencesSection({
|
||||
url,
|
||||
to,
|
||||
active,
|
||||
children,
|
||||
visible = true,
|
||||
@ -54,7 +56,7 @@ function SystemPreferencesSection({
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
to={url}
|
||||
to={to}
|
||||
className={classNames(
|
||||
'flex items-center rounded-lg px-2 py-2 hover:bg-gray-50 hover:text-black',
|
||||
active && 'bg-gray-50 text-black',
|
||||
@ -67,20 +69,16 @@ function SystemPreferencesSection({
|
||||
);
|
||||
}
|
||||
|
||||
export const SystemPreferences = (
|
||||
props: RouteComponentProps<{ submenu: string }>
|
||||
) => {
|
||||
const { match, history } = props;
|
||||
const subMatch = useRouteMatch<{ submenu: string; desk?: string }>(
|
||||
`${match.url}/:submenu/:desk?`
|
||||
);
|
||||
export const SystemPreferences = () => {
|
||||
const navigate = useNavigate();
|
||||
const { submenu } = useParams<{ submenu: string }>();
|
||||
const deskMatch = useMatch('/system-preferences/:submenu/:desk');
|
||||
const { systemBlocked } = useSystemUpdate();
|
||||
const charges = useCharges();
|
||||
const filteredCharges = Object.values(charges).filter(
|
||||
(charge) => charge.desk !== 'landscape'
|
||||
);
|
||||
const isMobile = useMedia('(max-width: 639px)');
|
||||
const settingsPath = isMobile ? `${match.url}/:submenu` : '/';
|
||||
|
||||
const matchSub = useCallback(
|
||||
(target: string, desk?: string) => {
|
||||
@ -88,40 +86,35 @@ export const SystemPreferences = (
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subMatch && target === 'system-updates') {
|
||||
return true;
|
||||
if (!submenu) {
|
||||
return target === 'system-updates';
|
||||
}
|
||||
|
||||
if (desk && subMatch?.params.desk !== desk) {
|
||||
if (desk && deskMatch?.params.desk !== desk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return subMatch?.params.submenu === target;
|
||||
return submenu === target;
|
||||
},
|
||||
[match, subMatch]
|
||||
);
|
||||
|
||||
const subUrl = useCallback(
|
||||
(submenu: string) => `${match.url}/${submenu}`,
|
||||
[match]
|
||||
[deskMatch, submenu]
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorAlert}
|
||||
onReset={() => history.push('/leap/system-preferences')}
|
||||
onReset={() => navigate('/leap/system-preferences')}
|
||||
>
|
||||
<div className="system-preferences-grid bg-gray-50">
|
||||
<Route exact={isMobile} path={match.url}>
|
||||
{isMobile && submenu ? null : (
|
||||
<aside className="system-preferences-aside min-h-fit flex max-h-[calc(100vh-6.25rem)] w-full min-w-60 flex-col border-r-2 border-gray-50 bg-white py-4 font-semibold text-black sm:w-auto sm:py-8 sm:text-gray-600">
|
||||
<nav className="flex flex-col px-2 sm:px-6">
|
||||
<SearchSystemPreferences subUrl={subUrl} />
|
||||
<SearchSystemPreferences />
|
||||
<span className="pt-1 pl-2 pb-3 text-sm font-semibold text-gray-400">
|
||||
Landscape
|
||||
</span>
|
||||
<ul className="space-y-1">
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('system-updates')}
|
||||
to="system-updates"
|
||||
active={matchSub('system-updates')}
|
||||
>
|
||||
<TlonIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
@ -130,15 +123,12 @@ export const SystemPreferences = (
|
||||
<Bullet className="ml-auto h-5 w-5 text-orange-500" />
|
||||
)}
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('help')}
|
||||
active={matchSub('help')}
|
||||
>
|
||||
<SystemPreferencesSection to="help" active={matchSub('help')}>
|
||||
<HelpIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Help and Support
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('security')}
|
||||
to="security"
|
||||
active={matchSub('security')}
|
||||
>
|
||||
<LogoutIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
@ -152,49 +142,49 @@ export const SystemPreferences = (
|
||||
</span>
|
||||
<ul className="space-y-1">
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('notifications')}
|
||||
to="notifications"
|
||||
active={matchSub('notifications')}
|
||||
>
|
||||
<BellIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Notifications
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('privacy')}
|
||||
to="privacy"
|
||||
active={matchSub('privacy')}
|
||||
>
|
||||
<BurstIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Attention & Privacy
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('appearance')}
|
||||
to="appearance"
|
||||
active={matchSub('appearance')}
|
||||
>
|
||||
<PencilIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Appearance
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('shortcuts')}
|
||||
to="shortcuts"
|
||||
active={matchSub('shortcuts')}
|
||||
>
|
||||
<ForwardSlashIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Shortcuts
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('interface')}
|
||||
to="interface"
|
||||
active={matchSub('interface')}
|
||||
>
|
||||
<Sig16Icon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Interface Settings
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('storage')}
|
||||
to="storage"
|
||||
active={matchSub('storage')}
|
||||
>
|
||||
<SlidersIcon className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
Remote Storage
|
||||
</SystemPreferencesSection>
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('invites')}
|
||||
to="invites"
|
||||
active={matchSub('invites')}
|
||||
>
|
||||
<InvitesIcom className="mr-3 h-6 w-6 rounded-md text-gray-600" />
|
||||
@ -212,7 +202,7 @@ export const SystemPreferences = (
|
||||
.map((charge) => (
|
||||
<SystemPreferencesSection
|
||||
key={charge.desk}
|
||||
url={subUrl(`apps/${charge.desk}`)}
|
||||
to={`apps/${charge.desk}`}
|
||||
active={matchSub('apps', charge.desk)}
|
||||
>
|
||||
<DocketImage size="small" className="mr-3" {...charge} />
|
||||
@ -222,48 +212,31 @@ export const SystemPreferences = (
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</Route>
|
||||
<Route path={settingsPath}>
|
||||
)}
|
||||
{isMobile && !submenu ? null : (
|
||||
<section className="system-preferences-content min-h-fit max-h-[calc(100vh-6.25rem)] flex-1 flex-col bg-gray-50 p-4 text-gray-800 sm:p-8">
|
||||
<Switch>
|
||||
<Route path={`${match.url}/apps/:desk`} component={AppPrefs} />
|
||||
<Route path={`${match.url}/help`} component={Help} />
|
||||
<Route
|
||||
path={`${match.url}/interface`}
|
||||
component={InterfacePrefs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/appearance`}
|
||||
component={AppearancePrefs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/shortcuts`}
|
||||
component={ShortcutPrefs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/notifications`}
|
||||
component={NotificationPrefs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/privacy`}
|
||||
component={AttentionAndPrivacy}
|
||||
/>
|
||||
<Route path={[`${match.url}/storage`]} component={StoragePrefs} />
|
||||
<Route path={`${match.url}/security`} component={SecurityPrefs} />
|
||||
<Route path={[`${match.url}/invites`]} component={InvitePrefs} />
|
||||
<Route
|
||||
path={[`${match.url}/system-updates`, match.url]}
|
||||
component={AboutSystem}
|
||||
/>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="apps/:desk" element={<AppPrefs />} />
|
||||
<Route path="help" element={<Help />} />
|
||||
<Route path="interface" element={<InterfacePrefs />} />
|
||||
<Route path="appearance" element={<AppearancePrefs />} />
|
||||
<Route path="shortcuts" element={<ShortcutPrefs />} />
|
||||
<Route path="notifications" element={<NotificationPrefs />} />
|
||||
<Route path="privacy" element={<AttentionAndPrivacy />} />
|
||||
<Route path="storage" element={<StoragePrefs />} />
|
||||
<Route path="security" element={<SecurityPrefs />} />
|
||||
<Route path="invites" element={<InvitePrefs />} />
|
||||
<Route path="system-updates" element={<AboutSystem />} />
|
||||
<Route index element={<AboutSystem />} />
|
||||
</Routes>
|
||||
<Link
|
||||
to={match.url}
|
||||
to="/system-preferences"
|
||||
className="sm:none h4 mt-auto inline-flex items-center pt-4 text-gray-400 sm:hidden"
|
||||
>
|
||||
<LeftArrow className="mr-2 h-3 w-3" /> Back
|
||||
</Link>
|
||||
</section>
|
||||
</Route>
|
||||
)}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
@ -23,31 +23,54 @@ interface VereState {
|
||||
}
|
||||
|
||||
const useVereState = create<Vere>((set, get) => ({
|
||||
cur: {
|
||||
rev: '',
|
||||
},
|
||||
loaded: false,
|
||||
set
|
||||
}))
|
||||
isLatest: true,
|
||||
vereVersion: '',
|
||||
latestVereVersion: '',
|
||||
set,
|
||||
}));
|
||||
|
||||
const fetchRuntimeVersion = () => {
|
||||
api.thread({
|
||||
inputMark: 'noun',
|
||||
outputMark: 'vere-update',
|
||||
desk: 'base',
|
||||
threadName: 'runtime-version',
|
||||
body: '',
|
||||
}).then((data: Vere) => {
|
||||
useVereState.setState((state) => {
|
||||
const vereVersion = data.cur.rev.split('/vere/~.')[1];
|
||||
const isLatest = data.next === undefined;
|
||||
const latestVereVersion = !isLatest ? data.next.rev.split('/vere/~.')[1] : vereVersion
|
||||
return Object.assign(data, {loaded: true, isLatest, vereVersion, latestVereVersion});
|
||||
api
|
||||
.thread({
|
||||
inputMark: 'noun',
|
||||
outputMark: 'vere-update',
|
||||
desk: 'base',
|
||||
threadName: 'runtime-version',
|
||||
body: '',
|
||||
})
|
||||
});
|
||||
}
|
||||
.then((data) => {
|
||||
useVereState.setState((state) => {
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
const vereData = data as Vere;
|
||||
const vereVersion = vereData.cur.rev.split('/vere/~.')[1];
|
||||
const isLatest = vereData.next === undefined;
|
||||
const latestVereVersion =
|
||||
vereData.next !== undefined
|
||||
? vereData.next.rev.split('/vere/~.')[1]
|
||||
: vereVersion;
|
||||
return Object.assign(vereData, {
|
||||
loaded: true,
|
||||
isLatest,
|
||||
vereVersion,
|
||||
latestVereVersion,
|
||||
});
|
||||
}
|
||||
return state;
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
fetchRuntimeVersion()
|
||||
fetchRuntimeVersion();
|
||||
|
||||
setInterval(fetchRuntimeVersion, 1800000)
|
||||
setInterval(fetchRuntimeVersion, 1800000);
|
||||
|
||||
export default useVereState;;
|
||||
export default useVereState;
|
||||
|
||||
// window.vere = useVereState.getState;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button } from '../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
@ -7,8 +7,8 @@ import useDocketState, { useCharges } from '../state/docket';
|
||||
import { getAppName } from '@/logic/utils';
|
||||
|
||||
export const RemoveApp = () => {
|
||||
const history = useHistory();
|
||||
const { desk } = useParams<{ desk: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { desk = '' } = useParams<{ desk: string }>();
|
||||
const charges = useCharges();
|
||||
const docket = charges[desk];
|
||||
const uninstallDocket = useDocketState((s) => s.uninstallDocket);
|
||||
@ -20,7 +20,7 @@ export const RemoveApp = () => {
|
||||
}, [desk]);
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||
<Dialog open onOpenChange={(open) => !open && navigate('/')}>
|
||||
<DialogContent
|
||||
showClose={false}
|
||||
className="space-y-6"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Redirect, useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button } from '../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
@ -7,8 +7,8 @@ import useDocketState, { useCharges } from '../state/docket';
|
||||
import { getAppName } from '@/logic/utils';
|
||||
|
||||
export const SuspendApp = () => {
|
||||
const history = useHistory();
|
||||
const { desk } = useParams<{ desk: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { desk = '' } = useParams<{ desk: string }>();
|
||||
const charges = useCharges();
|
||||
const charge = charges[desk];
|
||||
|
||||
@ -19,11 +19,11 @@ export const SuspendApp = () => {
|
||||
}, [desk]);
|
||||
|
||||
if ('suspend' in charge.chad) {
|
||||
return <Redirect to="/" />;
|
||||
navigate('/');
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||
<Dialog open onOpenChange={(open) => !open && navigate('/')}>
|
||||
<DialogContent
|
||||
showClose={false}
|
||||
className="space-y-6"
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Dialog, DialogContent } from '../components/Dialog';
|
||||
import { AppInfo } from '../components/AppInfo';
|
||||
import { useCharge } from '../state/docket';
|
||||
import { usePike } from '../state/kiln';
|
||||
|
||||
export const TileInfo = () => {
|
||||
const { desk } = useParams<{ desk: string }>();
|
||||
const { push } = useHistory();
|
||||
const { desk = '' } = useParams<{ desk: string }>();
|
||||
const navigate = useNavigate();
|
||||
const charge = useCharge(desk);
|
||||
const pike = usePike(desk);
|
||||
|
||||
@ -16,7 +16,7 @@ export const TileInfo = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && push('/')}>
|
||||
<Dialog open onOpenChange={(open) => !open && navigate('/')}>
|
||||
<DialogContent>
|
||||
<AppInfo pike={pike} docket={charge} />
|
||||
</DialogContent>
|
||||
|
Loading…
Reference in New Issue
Block a user