ux: show a keypress indicator after a delay

This commit is contained in:
tomholford 2022-07-16 21:14:29 -07:00 committed by fang
parent d7ec69803f
commit 8d0ca32c5d
No known key found for this signature in database
GPG Key ID: EB035760C1BBA972
4 changed files with 87 additions and 3 deletions

View File

@ -145,7 +145,7 @@ const onInput = (name: string, session: Session, e: string) => {
}
const term = session.term;
const belts = readInput(term, e);
belts.map((b) => {
belts.forEach((b) => {
useTermState.getState().set((state) => {
state.sessions[name].pending++;
});

View File

@ -0,0 +1,59 @@
import useIsMounted from './lib/useIsMounted';
import React from 'react';
import { useEffect, useState } from 'react';
// TODO: Tune these values
const DELAY_MS = 1000;
const FRAME_MS = 200;
// Some alternative ASCII spinners:
// https://stackoverflow.com/questions/2685435/cooler-ascii-spinners
// const CHARS = '⣾⣽⣻⢿⡿⣟⣯⣷';
const CHARS = '◴◷◶◵';
const Spinner = () => {
const [index, setIndex] = useState(0);
const [intervalTimer, setIntervalTimer] = useState<ReturnType<typeof setInterval> | undefined>();
const isMounted = useIsMounted();
useEffect(() => {
setIntervalTimer(
setInterval(() => {
if (isMounted()) {
setIndex(idx => idx === CHARS.length - 1 ? 0 : idx + 1);
}
}, FRAME_MS)
);
return () => {
if (intervalTimer) {
clearInterval(intervalTimer);
}
};
}, []);
return <span>&nbsp;{CHARS[index]}</span>;
};
export const DelayedSpinner = () => {
const [showSpinner, setShowSpinner] = useState(false);
const [delayTimer, setDelayTimer] = useState<ReturnType<typeof setTimeout> | undefined>();
const isMounted = useIsMounted();
useEffect(() => {
setDelayTimer(
setTimeout(() => {
if (isMounted()) {
setShowSpinner(true);
}
}, DELAY_MS)
);
return () => {
if (delayTimer) {
clearTimeout(delayTimer);
}
};
}, []);
return showSpinner ? <Spinner /> : null;
};

View File

@ -1,8 +1,9 @@
import { DEFAULT_SESSION } from './constants';
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import useTermState, { Session } from './state';
import api from './api';
import { pokeTask } from '@urbit/api/term';
import { DelayedSpinner as Spinner } from './Spinner';
interface TabProps {
session: Session;
@ -40,12 +41,19 @@ export const Tab = ( { session, name }: TabProps ) => {
});
}, [session]);
// TODO: sometimes the pending is not decremented?
useEffect(() => {
if(session) {
console.log(`${session.subscriptionId}: ${session.pending} pending`);
}
}, [session]);
return (
<div className={'tab ' + (isSelected ? 'selected' : '')} onClick={onClick}>
<a className='session-name'>
{session?.hasBell ? '🔔 ' : ''}
{name === DEFAULT_SESSION ? 'default' : name}
{session && session.pending > 0 ? ' o' : ''}
{session && session.pending > 0 ? <Spinner /> : null}
{' '}
</a>
{name === DEFAULT_SESSION ? null : <a className="delete-session" onClick={onDelete}>x</a>}

View File

@ -0,0 +1,17 @@
import { useCallback, useEffect, useRef } from 'react';
function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback(() => isMounted.current, []);
}
export default useIsMounted;