diff --git a/extensions/web-clipper/src/views/main.tsx b/extensions/web-clipper/src/views/main.tsx index c69b53be2..80b0de2a3 100644 --- a/extensions/web-clipper/src/views/main.tsx +++ b/extensions/web-clipper/src/views/main.tsx @@ -87,6 +87,20 @@ const clipModes: { name: string; id: ClipMode; icon: string; pro?: boolean }[] = } ]; +enum ClipperState { + Idle = "idle", + Clipping = "clipping", + Clipped = "clipped", + Error = "error" +} + +const clipperButtonLabelMap: Record = { + [ClipperState.Clipping]: "Clipping...", + [ClipperState.Clipped]: "Save clip", + [ClipperState.Error]: "Retry Clip", + [ClipperState.Idle]: "Start clip" +}; + export function Main() { const [error, setError] = useState(); // const [colorMode, setColorMode] = useColorMode(); @@ -98,7 +112,6 @@ export function Main() { const [title, setTitle] = useState(); const [hasPermission, setHasPermission] = useState(false); const [url, setUrl] = useState(); - const [clipNonce, setClipNonce] = useState(0); const [clipMode, setClipMode] = usePersistentState( "clipMode", "simplified" @@ -107,10 +120,12 @@ export function Main() { "clipArea", "article" ); - const [isClipping, setIsClipping] = useState(false); const [note, setNote] = usePersistentState("note"); const [refs, setRefs] = usePersistentState("refs", []); const [clipData, setClipData] = useState(); + const [clipperState, setClipperState] = useState( + ClipperState.Idle + ); const pageTitle = useRef(); useEffect(() => { @@ -129,7 +144,6 @@ export function Main() { useEffect(() => { (async () => { - if (!clipArea || !clipMode) return; if ( !isPremium && (clipMode === "complete" || clipMode === "screenshot") @@ -137,25 +151,8 @@ export function Main() { setClipMode("simplified"); return; } - - try { - setIsClipping(true); - setClipData( - await clip(clipArea, clipMode, { - ...DEFAULT_SETTINGS, - ...settings, - images: isPremium, - inlineImages: isPremium - }) - ); - } catch (e) { - console.error(e); - if (e instanceof Error) setError(e.message); - } finally { - setIsClipping(false); - } })(); - }, [isPremium, clipArea, clipMode, clipNonce]); + }, [isPremium, clipArea, clipMode]); useEffect(() => { (async () => { @@ -168,6 +165,32 @@ export function Main() { })(); }, [settings]); + async function startClip() { + if (!clipArea || !clipMode) return; + + try { + setError(undefined); + setClipperState(ClipperState.Clipping); + setClipData( + await clip(clipArea, clipMode, { + ...DEFAULT_SETTINGS, + ...settings, + images: isPremium, + inlineImages: isPremium + }) + ); + setClipperState(ClipperState.Clipped); + } catch (e) { + console.error(e); + if (e instanceof Error) { + setError(e.message); + } + setClipperState(ClipperState.Error); + } + } + + const isClipping = clipperState === ClipperState.Clipping; + if (!hasPermission && !!settings?.corsProxy) { return ( @@ -241,10 +264,11 @@ export function Main() { key={item.id} variant="icon" onClick={() => { + setError(undefined); + setClipperState(ClipperState.Idle); setClipArea(item.id); - setClipNonce((s) => ++s); }} - disabled={isClipping} + disabled={isClipping || clipperState === ClipperState.Clipped} sx={{ display: "flex", borderRadius: "default", @@ -283,10 +307,15 @@ export function Main() { key={item.id} variant="icon" onClick={() => { + setError(undefined); + setClipperState(ClipperState.Idle); setClipMode(item.id); - setClipNonce((s) => ++s); }} - disabled={isClipping || (item.pro && !isPremium)} + disabled={ + isClipping || + clipperState === ClipperState.Clipped || + (item.pro && !isPremium) + } sx={{ display: "flex", borderRadius: "default", @@ -314,31 +343,53 @@ export function Main() { ))} {clipData && clipData.data && !isClipping && ( - { - const winUrl = URL.createObjectURL( - new Blob(["\ufeff", clipData.data], { type: "text/html" }) - ); - await browser.windows.create({ - url: winUrl - }); - }} - > - Clip done. Click here to preview. - + + { + const winUrl = URL.createObjectURL( + new Blob(["\ufeff", clipData.data], { type: "text/html" }) + ); + await browser.windows.create({ + url: winUrl + }); + }} + > + Clip done. Click here to preview. + + { + setClipData(undefined); + setClipperState(ClipperState.Idle); + }} + > + Discard + + )} {error && ( @@ -357,7 +408,7 @@ export function Main() { } }} onClick={async () => { - setClipNonce((s) => ++s); + await startClip(); }} > {ERROR_MAP[error] || error} @@ -400,8 +451,16 @@ export function Main() {