grid: distinguish app sync from system install

By using `%kiln-install` instead of `%kiln-sync` for System Updates, this ensures that the `%kids` desk is also updated.

Also, address UX feedback: render the entire source ship's patp to avoid ambiguity. (as opposed to truncating a moon's name).
This commit is contained in:
tomholford 2022-11-21 16:10:59 -08:00
parent 280c1cb19a
commit 1bd0f4be77
5 changed files with 64 additions and 31 deletions

View File

@ -3,11 +3,12 @@ import React, { HTMLAttributes } from 'react';
type ShipNameProps = { type ShipNameProps = {
name: string; name: string;
truncate?: boolean;
} & HTMLAttributes<HTMLSpanElement>; } & HTMLAttributes<HTMLSpanElement>;
export const ShipName = ({ name, ...props }: ShipNameProps) => { export const ShipName = ({ name, truncate = true, ...props }: ShipNameProps) => {
const separator = /([_^-])/; const separator = /([_^-])/;
const citedName = cite(name); const citedName = truncate ? cite(name) : name;
if (!citedName) { if (!citedName) {
return null; return null;

View File

@ -1,34 +1,39 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useAsyncCall } from '../logic/useAsyncCall'; import { useAsyncCall } from '../logic/useAsyncCall';
import useKilnState from '../state/kiln';
import { Button } from './Button'; import { Button } from './Button';
import { ShipName } from './ShipName'; import { ShipName } from './ShipName';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
interface SourceSyncerProps { interface SourceSetterProps {
appName: string; appName: string;
srcDesk: string;
srcShip?: string;
title: string; title: string;
syncDesk: string; toggleSrc: (desk: string, ship: string) => Promise<void>;
syncShip?: string;
} }
export default function SourceSyncer({ appName, title, syncDesk, syncShip }: SourceSyncerProps) { export default function SourceSetter({
const [newSyncShip, setNewSyncShip] = useState(syncShip ?? ''); appName,
const { toggleSync } = useKilnState(); srcDesk,
const { status: requestStatus, call: setSync } = useAsyncCall(toggleSync); srcShip,
const syncDirty = newSyncShip !== syncShip; title,
toggleSrc
}: SourceSetterProps) {
const [newSyncShip, setNewSyncShip] = useState(srcShip ?? '');
const { status: requestStatus, call: handleSubmit } = useAsyncCall(toggleSrc);
const syncDirty = newSyncShip !== srcShip;
const onUnsync = useCallback(() => { const onUnset = useCallback(() => {
if (!syncShip) { if (!srcShip) {
return; return;
} }
if ( if (
// eslint-disable-next-line no-alert, no-restricted-globals // eslint-disable-next-line no-alert, no-restricted-globals
confirm(`Are you sure you want to unsync ${appName}? You will no longer receive updates.`) confirm(`Are you sure you want to unsync ${appName}? You will no longer receive updates.`)
) { ) {
toggleSync(syncDesk, syncShip); toggleSrc(srcDesk, srcShip);
} }
}, [syncShip, syncDesk, toggleSync]); }, [srcShip, srcDesk]);
const handleSourceChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const handleSourceChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { target } = e; const { target } = e;
@ -39,26 +44,27 @@ export default function SourceSyncer({ appName, title, syncDesk, syncShip }: Sou
const onSubmit = useCallback( const onSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => { async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
await setSync(syncDesk, newSyncShip); await handleSubmit(srcDesk, newSyncShip);
}, },
[syncDesk, newSyncShip, toggleSync] [srcDesk, newSyncShip]
); );
return ( return (
<> <>
<h2 className="h3 mb-7">{title}</h2> <h2 className="h3 mb-7">{title}</h2>
<div className="space-y-3"> <div className="space-y-3">
{syncShip ? ( {srcShip ? (
<> <>
<h3 className="flex items-center h4 mb-2">Automatic Updates</h3> <h3 className="flex items-center h4 mb-2">Automatic Updates</h3>
<p>Automatically download and apply updates to keep {appName} up to date.</p> <p>Automatically download and apply updates to keep {appName} up to date.</p>
<div className="flex-1 flex flex-col justify-center space-y-6"> <div className="flex-1 flex flex-col justify-center space-y-6">
<p> <p>
OTA Source: <ShipName name={syncShip} className="font-semibold font-mono" /> OTA Source:{' '}
<ShipName name={srcShip} truncate={false} className="font-semibold font-mono" />
</p> </p>
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<Button onClick={onUnsync} variant="destructive"> <Button onClick={onUnset} variant="destructive">
Unsync Updates for {appName}... Unsync Updates for {appName}...
</Button> </Button>
</div> </div>

View File

@ -1,23 +1,25 @@
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import SourceSetter from '../../components/SourceSetter';
import { useCharge } from '../../state/docket'; import { useCharge } from '../../state/docket';
import { usePike } from '../../state/kiln'; import useKilnState, { usePike } from '../../state/kiln';
import { getAppName } from '../../state/util'; import { getAppName } from '../../state/util';
import SourceSyncer from '../../components/SourceSyncer';
export const AppPrefs = ({ match }: RouteComponentProps<{ desk: string }>) => { export const AppPrefs = ({ match }: RouteComponentProps<{ desk: string }>) => {
const { desk } = match.params; const { desk } = match.params;
const charge = useCharge(desk); const charge = useCharge(desk);
const appName = getAppName(charge); const appName = getAppName(charge);
const pike = usePike(desk); const pike = usePike(desk);
const syncShip = pike?.sync?.ship; const srcShip = pike?.sync?.ship;
const { toggleSync } = useKilnState();
return ( return (
<SourceSyncer <SourceSetter
appName={appName} appName={appName}
toggleSrc={toggleSync}
srcDesk={desk}
srcShip={srcShip}
title={`${appName} Settings`} title={`${appName} Settings`}
syncDesk={desk}
syncShip={syncShip}
/> />
); );
}; };

View File

@ -1,15 +1,22 @@
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import SourceSyncer from '../../components/SourceSyncer'; import SourceSetter from '../../components/SourceSetter';
import { usePike } from '../../state/kiln'; import useKilnState, { usePike } from '../../state/kiln';
export const SystemUpdatePrefs = () => { export const SystemUpdatePrefs = () => {
const desk = 'base'; const desk = 'base';
const appName = 'your Urbit'; const appName = 'your Urbit';
const pike = usePike(desk); const pike = usePike(desk);
const syncShip = pike?.sync?.ship; const srcShip = pike?.sync?.ship;
const { toggleInstall } = useKilnState();
return ( return (
<SourceSyncer appName={appName} title="System Updates" syncDesk={desk} syncShip={syncShip} /> <SourceSetter
appName={appName}
toggleSrc={toggleInstall}
srcDesk={desk}
srcShip={srcShip}
title="System Updates"
/>
); );
}; };

View File

@ -1,4 +1,13 @@
import { scryLag, getPikes, Pikes, Pike, kilnUnsync, kilnSync } from '@urbit/api'; import {
scryLag,
getPikes,
Pikes,
Pike,
kilnUnsync,
kilnSync,
kilnUninstall,
kilnInstall
} from '@urbit/api';
import create from 'zustand'; import create from 'zustand';
import produce from 'immer'; import produce from 'immer';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -12,6 +21,7 @@ interface KilnState {
lag: boolean; lag: boolean;
fetchLag: () => Promise<void>; fetchLag: () => Promise<void>;
fetchPikes: () => Promise<void>; fetchPikes: () => Promise<void>;
toggleInstall: (desk: string, ship: string) => Promise<void>;
toggleSync: (desk: string, ship: string) => Promise<void>; toggleSync: (desk: string, ship: string) => Promise<void>;
set: (s: KilnState) => void; set: (s: KilnState) => void;
initializeKiln: () => Promise<void>; initializeKiln: () => Promise<void>;
@ -33,6 +43,13 @@ const useKilnState = create<KilnState>((set, get) => ({
const lag = await api.scry<boolean>(scryLag); const lag = await api.scry<boolean>(scryLag);
set({ lag }); set({ lag });
}, },
toggleInstall: async (desk: string, ship: string) => {
const synced = !!get().pikes[desk].sync;
await (useMockData
? fakeRequest('')
: api.poke(synced ? kilnUninstall(desk) : kilnInstall(ship, desk)));
await get().fetchPikes();
},
toggleSync: async (desk: string, ship: string) => { toggleSync: async (desk: string, ship: string) => {
const synced = !!get().pikes[desk].sync; const synced = !!get().pikes[desk].sync;
await (useMockData await (useMockData