mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
Merge pull request #5236 from urbit/hm/grid-qa-fixes
grid: qa fixes round 1
This commit is contained in:
commit
8748b115a2
@ -1,6 +1,6 @@
|
||||
import { chadIsRunning, Treaty } from '@urbit/api';
|
||||
import clipboardCopy from 'clipboard-copy';
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Vat } from '@urbit/api/hood';
|
||||
import { Button, PillButton } from './Button';
|
||||
@ -50,6 +50,7 @@ function getRemoteDesk(docket: App, vat?: Vat) {
|
||||
export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
||||
const installStatus = getInstallStatus(docket);
|
||||
const [ship, desk] = getRemoteDesk(docket, vat);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const installApp = async () => {
|
||||
if (installStatus === 'installed') {
|
||||
@ -57,15 +58,21 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
||||
}
|
||||
await useDocketState.getState().installDocket(ship, desk);
|
||||
};
|
||||
const copyApp = () => {
|
||||
clipboardCopy(`web+urbitgraph://app/${ship}/${desk}`);
|
||||
};
|
||||
|
||||
const copyApp = useCallback(() => {
|
||||
setCopied(true);
|
||||
clipboardCopy(`web+urbitgraph://${ship}/${desk}`);
|
||||
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1250);
|
||||
}, []);
|
||||
|
||||
if (!docket) {
|
||||
// TODO: maybe replace spinner with skeletons
|
||||
return (
|
||||
<div className="dialog-inner-container text-black">
|
||||
<span>Loading...</span>
|
||||
<div className="dialog-inner-container flex justify-center text-black">
|
||||
<Spinner className="w-10 h-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -115,7 +122,8 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
||||
</Dialog>
|
||||
)}
|
||||
<PillButton variant="alt-secondary" onClick={copyApp}>
|
||||
Copy App Link
|
||||
{!copied && 'Copy App Link'}
|
||||
{copied && 'copied!'}
|
||||
</PillButton>
|
||||
</div>
|
||||
</DocketHeader>
|
||||
|
@ -242,6 +242,7 @@ export const Leap = React.forwardRef(
|
||||
onFocus={onFocus}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
autoComplete="off"
|
||||
aria-autocomplete="both"
|
||||
aria-controls={dropdown}
|
||||
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link, NavLink, Route, Switch } from 'react-router-dom';
|
||||
import { useLeapStore } from './Nav';
|
||||
import { Button } from '../components/Button';
|
||||
import { useHarkStore } from '../state/hark';
|
||||
import { Inbox } from './notifications/Inbox';
|
||||
|
||||
export const Notifications = () => {
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const markAllAsRead = () => {
|
||||
const { archiveAll } = useHarkStore.getState();
|
||||
archiveAll();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
select('Notifications');
|
||||
|
||||
function visibilitychange() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
useHarkStore.getState().opened();
|
||||
|
@ -12,7 +12,10 @@ type SearchProps = RouteComponentProps<{
|
||||
export const Search = ({ match }: SearchProps) => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.path}/:ship/apps/:host/:desk`} component={TreatyInfo} />
|
||||
<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} />
|
||||
|
@ -28,16 +28,22 @@ export const SystemMenu = ({ className, open, subMenuOpen, shouldDim }: SystemMe
|
||||
const hash = garden ? getHash(garden) : null;
|
||||
const isMobile = useMedia('(max-width: 639px)');
|
||||
|
||||
const copyHash = useCallback((event: Event) => {
|
||||
event.preventDefault();
|
||||
const copyHash = useCallback(
|
||||
(event: Event) => {
|
||||
event.preventDefault();
|
||||
if (!hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCopied(true);
|
||||
clipboardCopy('fjuhl');
|
||||
setCopied(true);
|
||||
clipboardCopy(hash);
|
||||
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1250);
|
||||
}, []);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1250);
|
||||
},
|
||||
[hash]
|
||||
);
|
||||
|
||||
const preventFlash = useCallback((e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
@ -5,10 +5,9 @@ import { map, take } from 'lodash';
|
||||
import { useCharge } from '../../state/docket';
|
||||
import { Elbow } from '../../components/icons/Elbow';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { getAppHref } from '../../state/util';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DeskLink } from '../../components/DeskLink';
|
||||
import {useHarkStore} from '../../state/hark';
|
||||
import { useHarkStore } from '../../state/hark';
|
||||
import { DocketImage } from '../../components/DocketImage';
|
||||
|
||||
interface BasicNotificationProps {
|
||||
notification: Notification;
|
||||
@ -43,7 +42,6 @@ export const BasicNotification = ({ notification, lid }: BasicNotificationProps)
|
||||
const contents = map(notification.body, 'content').filter((c) => c.length > 0);
|
||||
const large = contents.length === 0;
|
||||
const archive = () => {
|
||||
const { bin } = notification;
|
||||
useHarkStore.getState().archiveNote(notification.bin, lid);
|
||||
};
|
||||
|
||||
@ -53,17 +51,14 @@ export const BasicNotification = ({ notification, lid }: BasicNotificationProps)
|
||||
to={`?grid-note=${encodeURIComponent(first.link)}`}
|
||||
desk={desk}
|
||||
className={cn(
|
||||
'text-black rounded',
|
||||
'text-black rounded-xl',
|
||||
'unseen' in lid ? 'bg-blue-100' : 'bg-gray-100',
|
||||
large ? 'note-grid-no-content' : 'note-grid-content'
|
||||
)}
|
||||
aria-labelledby={id}
|
||||
>
|
||||
<header id={id} className="contents">
|
||||
<div
|
||||
className="note-grid-icon rounded w-full h-full"
|
||||
style={{ backgroundColor: charge?.color ?? '#ee5432' }}
|
||||
/>
|
||||
<DocketImage {...charge} size={!large ? 'xs' : 'default'} className="note-grid-icon" />
|
||||
<div className="note-grid-title font-semibold">{charge?.title || desk}</div>
|
||||
{!large ? <Elbow className="note-grid-arrow w-6 h-6 text-gray-300" /> : null}
|
||||
<h2 id={`${id}-title`} className="note-grid-head font-semibold text-gray-600">
|
||||
|
@ -1,47 +1,57 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '../../components/Button';
|
||||
import { useCurrentTheme } from '../../state/local';
|
||||
import { getDarkColor } from '../../state/util';
|
||||
|
||||
const cards: OnboardingCardProps[] = [
|
||||
{
|
||||
title: 'Terminal',
|
||||
body: "Install a web terminal to access your Urbit's command line",
|
||||
button: 'Install',
|
||||
color: '#9CA4B1'
|
||||
color: '#9CA4B1',
|
||||
href: '/leap/search/direct/apps/~zod/webterm'
|
||||
},
|
||||
{
|
||||
title: 'Groups',
|
||||
body: 'Install Groups, a suite of social software to communicate with other urbit users',
|
||||
button: 'Install',
|
||||
color: '#D1DDD3'
|
||||
color: '#D1DDD3',
|
||||
href: '/leap/search/direct/apps/~zod/landscape'
|
||||
},
|
||||
{
|
||||
title: 'Bitcoin',
|
||||
body: 'Install a bitcoin wallet. You can send bitcoin to any btc address, or even another ship',
|
||||
button: 'Install',
|
||||
color: '#F6EBDB'
|
||||
},
|
||||
{
|
||||
title: 'Debug',
|
||||
body: "Install a debugger. You can inspect your ship's internals using this interface",
|
||||
button: 'Install'
|
||||
},
|
||||
{
|
||||
title: 'Build an app',
|
||||
body: 'You can instantly get started building new things on Urbit. Just right click your Landscape and select “New App”',
|
||||
button: 'Learn more',
|
||||
color: '#82A6CA'
|
||||
color: '#F6EBDB',
|
||||
href: '/leap/search/direct/apps/~zod/bitcoin'
|
||||
}
|
||||
// Commenting out until we have something real
|
||||
// {
|
||||
// title: 'Debug',
|
||||
// body: "Install a debugger. You can inspect your ship's internals using this interface",
|
||||
// button: 'Install',
|
||||
// color: '#E5E5E5',
|
||||
// href: '/leap/search/direct/apps/~zod/debug'
|
||||
// }
|
||||
// {
|
||||
// title: 'Build an app',
|
||||
// body: 'You can instantly get started building new things on Urbit. Just right click your Landscape and select “New App”',
|
||||
// button: 'Learn more',
|
||||
// color: '#82A6CA'
|
||||
// }
|
||||
];
|
||||
|
||||
interface OnboardingCardProps {
|
||||
title: string;
|
||||
button: string;
|
||||
href: string;
|
||||
body: string;
|
||||
color?: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const OnboardingCard = ({ title, button, body, color }: OnboardingCardProps) => (
|
||||
const OnboardingCard = ({ title, button, href, body, color }: OnboardingCardProps) => (
|
||||
<div
|
||||
className="p-4 flex flex-col space-y-2 text-black bg-gray-100 justify-between rounded-xl"
|
||||
style={color ? { backgroundColor: color } : {}}
|
||||
@ -50,7 +60,7 @@ const OnboardingCard = ({ title, button, body, color }: OnboardingCardProps) =>
|
||||
<h4 className="font-semibold text-black">{title}</h4>
|
||||
<p>{body}</p>
|
||||
</div>
|
||||
<Button variant="primary" className="bg-gray-500">
|
||||
<Button as={Link} to={href} variant="primary" className="bg-gray-500">
|
||||
{button}
|
||||
</Button>
|
||||
</div>
|
||||
@ -60,27 +70,35 @@ interface OnboardingNotificationProps {
|
||||
unread: boolean;
|
||||
}
|
||||
|
||||
export const OnboardingNotification = ({ unread }: OnboardingNotificationProps) => (
|
||||
<section
|
||||
className={cn('notification space-y-2 text-black', unread ? 'bg-blue-100' : 'bg-gray-100')}
|
||||
aria-labelledby=""
|
||||
>
|
||||
<header id="system-updates-blocked" className="relative space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<span className="inline-block w-6 h-6 bg-orange-500 rounded-full" />
|
||||
<span className="font-medium">Grid</span>
|
||||
export const OnboardingNotification = ({ unread }: OnboardingNotificationProps) => {
|
||||
const theme = useCurrentTheme();
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn('notification space-y-2 text-black', unread ? 'bg-blue-100' : 'bg-gray-100')}
|
||||
aria-labelledby=""
|
||||
>
|
||||
<header id="system-updates-blocked" className="relative space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<span className="inline-block w-6 h-6 bg-orange-500 rounded-full" />
|
||||
<span className="font-medium">Grid</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<h2 id="runtime-lag">Hello there, welcome to Grid!</h2>
|
||||
</div>
|
||||
</header>
|
||||
<div className="grid grid-cols-3 grid-rows-2 gap-4">
|
||||
{
|
||||
/* eslint-disable-next-line react/no-array-index-key */
|
||||
cards.map((card, i) => (
|
||||
<OnboardingCard
|
||||
key={i}
|
||||
{...card}
|
||||
color={theme === 'dark' ? getDarkColor(card.color) : card.color}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<h2 id="runtime-lag">Hello there, welcome to Grid!</h2>
|
||||
</div>
|
||||
</header>
|
||||
<div className="grid grid-cols-3 grid-rows-2 gap-4">
|
||||
{
|
||||
/* eslint-disable-next-line react/no-array-index-key */
|
||||
cards.map((card, i) => (
|
||||
<OnboardingCard key={i} {...card} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -78,7 +78,8 @@ export const BaseBlockedNotification = () => {
|
||||
<DialogTrigger as={NotificationButton}>Dismiss</DialogTrigger>
|
||||
<DialogContent
|
||||
showClose={false}
|
||||
className="max-w-[400px] space-y-6 text-base tracking-tight"
|
||||
className="space-y-6 text-base tracking-tight"
|
||||
containerClass="w-full max-w-md"
|
||||
>
|
||||
<h2 className="h4">Skip System Update</h2>
|
||||
<p>
|
||||
@ -108,7 +109,8 @@ export const BaseBlockedNotification = () => {
|
||||
<DialogContent
|
||||
showClose={false}
|
||||
onOpenAutoFocus={disableDefault}
|
||||
className="max-w-[400px] space-y-6 text-base tracking-tight"
|
||||
className="space-y-6 text-base tracking-tight"
|
||||
containerClass="w-full max-w-md"
|
||||
>
|
||||
<h2 className="h4">Archive ({count}) Apps and Apply System Update</h2>
|
||||
<p>
|
||||
|
@ -53,7 +53,8 @@ export const useRecentsStore = create<RecentsStore>(
|
||||
}),
|
||||
{
|
||||
whitelist: ['recentApps', 'recentDevs'],
|
||||
name: 'recents-store'
|
||||
name: 'recents-store',
|
||||
version: import.meta.env.VITE_SHORTHASH as any
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppInfo } from '../../components/AppInfo';
|
||||
import { Spinner } from '../../components/Spinner';
|
||||
import useDocketState, { useCharge, useTreaty } from '../../state/docket';
|
||||
import { useVat } from '../../state/kiln';
|
||||
import { useLeapStore } from '../Nav';
|
||||
@ -25,8 +26,8 @@ export const TreatyInfo = () => {
|
||||
if (!treaty) {
|
||||
// TODO: maybe replace spinner with skeletons
|
||||
return (
|
||||
<div className="dialog-inner-container text-black">
|
||||
<span>Loading...</span>
|
||||
<div className="dialog-inner-container flex justify-center text-black">
|
||||
<Spinner className="w-10 h-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -149,8 +149,8 @@ export const createState = <T extends Record<string, unknown>>(
|
||||
}),
|
||||
{
|
||||
blacklist,
|
||||
name: stateStorageKey(name)
|
||||
// version: process.env.LANDSCAPE_SHORTHASH as any
|
||||
name: stateStorageKey(name),
|
||||
version: import.meta.env.VITE_SHORTHASH as any
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -17,7 +17,7 @@ interface KilnState {
|
||||
toggleOTAs: (desk: string, on: boolean) => Promise<void>;
|
||||
set: (s: KilnState) => void;
|
||||
}
|
||||
const useKilnState = create<KilnState>((set) => ({
|
||||
const useKilnState = create<KilnState>((set, get) => ({
|
||||
vats: useMockData ? mockVats : {},
|
||||
lag: !!useMockData,
|
||||
loaded: false,
|
||||
@ -66,6 +66,10 @@ const useKilnState = create<KilnState>((set) => ({
|
||||
}
|
||||
|
||||
await api.poke(on ? kilnResume(desk) : kilnPause(desk));
|
||||
|
||||
if (!on) {
|
||||
get().fetchVats(); // refresh vat state
|
||||
}
|
||||
},
|
||||
set: produce(set)
|
||||
}));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DocketHref } from '@urbit/api/docket';
|
||||
import { hsla, parseToHsla } from 'color2k';
|
||||
|
||||
export const useMockData = import.meta.env.MODE === 'mock';
|
||||
|
||||
@ -33,3 +34,8 @@ export function deSig(ship: string): string {
|
||||
}
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
export function getDarkColor(color: string): string {
|
||||
const hslaColor = parseToHsla(color);
|
||||
return hsla(hslaColor[0], hslaColor[1], 1 - hslaColor[2], 1);
|
||||
}
|
||||
|
@ -1,3 +1,26 @@
|
||||
body {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #e5e5e5 transparent;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
scrollbar-color: #333333 transparent;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background-color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.h1 {
|
||||
@apply text-2xl sm:text-3xl font-bold leading-relaxed tracking-tighter;
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
@apply fixed z-40 top-1/2 left-1/2 min-w-80 transform -translate-x-1/2 -translate-y-1/2;
|
||||
@apply fixed z-40 top-1/2 left-1/2 p-4 transform -translate-x-1/2 -translate-y-1/2;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
@apply relative p-5 sm:p-8 bg-white rounded-3xl;
|
||||
@apply relative max-h-[calc(100vh-2rem)] max-w-[calc(100vw-2rem)] p-5 sm:p-8 bg-white rounded-3xl overflow-auto;
|
||||
}
|
||||
|
||||
.dialog-inner-container {
|
||||
|
@ -18,7 +18,7 @@ export const RemoveApp = () => {
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
|
||||
<DialogContent showClose={false} className="space-y-6" containerClass="w-full max-w-md">
|
||||
<h1 className="h4">Remove “{docket?.title || ''}”?</h1>
|
||||
<p className="text-base tracking-tight pr-6">
|
||||
This will remove the software's tile from your home screen.
|
||||
|
@ -21,7 +21,7 @@ export const SuspendApp = () => {
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
|
||||
<DialogContent showClose={false} className="space-y-6" containerClass="w-full max-w-md">
|
||||
<h1 className="h4">Suspend “{charge?.title || ''}”</h1>
|
||||
<p className="text-base tracking-tight pr-6">
|
||||
Suspending an app will freeze its current state and render it unable
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { darken, hsla, lighten, parseToHsla, readableColorIsBlack } from 'color2k';
|
||||
import { useCurrentTheme } from '../state/local';
|
||||
|
||||
function getDarkColor(color: string): string {
|
||||
const hslaColor = parseToHsla(color);
|
||||
return hsla(hslaColor[0], hslaColor[1], 1 - hslaColor[2], 1);
|
||||
}
|
||||
import { getDarkColor } from '../state/util';
|
||||
|
||||
function bgAdjustedColor(color: string, darkBg: boolean): string {
|
||||
return darkBg ? lighten(color, 0.1) : darken(color, 0.1);
|
||||
|
@ -3,11 +3,15 @@ import analyze from 'rollup-plugin-analyzer';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import reactRefresh from '@vitejs/plugin-react-refresh';
|
||||
import htmlPlugin from 'vite-plugin-html-config';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const htmlPluginOpt = {
|
||||
headScripts: [{ src: '/apps/grid/desk.js' }, { src: '/session.js' }]
|
||||
};
|
||||
|
||||
const GIT_DESC = execSync('git describe --always', { encoding: 'utf8' }).trim();
|
||||
process.env.VITE_SHORTHASH = GIT_DESC;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ mode }) => {
|
||||
Object.assign(process.env, loadEnv(mode, process.cwd()));
|
||||
|
Loading…
Reference in New Issue
Block a user