mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
Merge pull request #8254 from 01zulfi/webclipper/start-clip-button
webclipper: improve clipping ux
This commit is contained in:
@@ -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, string> = {
|
||||
[ClipperState.Clipping]: "Clipping...",
|
||||
[ClipperState.Clipped]: "Save clip",
|
||||
[ClipperState.Error]: "Retry Clip",
|
||||
[ClipperState.Idle]: "Start clip"
|
||||
};
|
||||
|
||||
export function Main() {
|
||||
const [error, setError] = useState<string>();
|
||||
// const [colorMode, setColorMode] = useColorMode();
|
||||
@@ -98,7 +112,6 @@ export function Main() {
|
||||
const [title, setTitle] = useState<string>();
|
||||
const [hasPermission, setHasPermission] = useState<boolean>(false);
|
||||
const [url, setUrl] = useState<string>();
|
||||
const [clipNonce, setClipNonce] = useState(0);
|
||||
const [clipMode, setClipMode] = usePersistentState<ClipMode>(
|
||||
"clipMode",
|
||||
"simplified"
|
||||
@@ -107,10 +120,12 @@ export function Main() {
|
||||
"clipArea",
|
||||
"article"
|
||||
);
|
||||
const [isClipping, setIsClipping] = useState(false);
|
||||
const [note, setNote] = usePersistentState<ItemReference>("note");
|
||||
const [refs, setRefs] = usePersistentState<SelectedReference[]>("refs", []);
|
||||
const [clipData, setClipData] = useState<ClipData>();
|
||||
const [clipperState, setClipperState] = useState<ClipperState>(
|
||||
ClipperState.Idle
|
||||
);
|
||||
const pageTitle = useRef<string>();
|
||||
|
||||
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 (
|
||||
<FlexScrollContainer style={{ maxHeight: 560 }}>
|
||||
@@ -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 && (
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
mt: 1,
|
||||
bg: "shade",
|
||||
color: "accent",
|
||||
p: 1,
|
||||
border: "1px solid var(--accent)",
|
||||
borderRadius: "default",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
filter: "brightness(80%)"
|
||||
}
|
||||
}}
|
||||
onClick={async () => {
|
||||
const winUrl = URL.createObjectURL(
|
||||
new Blob(["\ufeff", clipData.data], { type: "text/html" })
|
||||
);
|
||||
await browser.windows.create({
|
||||
url: winUrl
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clip done. Click here to preview.
|
||||
</Text>
|
||||
<Flex sx={{ gap: 1, justifyContent: "space-between" }}>
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
flex: 1,
|
||||
mt: 1,
|
||||
bg: "shade",
|
||||
color: "accent",
|
||||
p: 1,
|
||||
border: "1px solid var(--accent)",
|
||||
borderRadius: "default",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
filter: "brightness(80%)"
|
||||
}
|
||||
}}
|
||||
onClick={async () => {
|
||||
const winUrl = URL.createObjectURL(
|
||||
new Blob(["\ufeff", clipData.data], { type: "text/html" })
|
||||
);
|
||||
await browser.windows.create({
|
||||
url: winUrl
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clip done. Click here to preview.
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
mt: 1,
|
||||
bg: "background-secondary",
|
||||
p: 1,
|
||||
borderRadius: "default",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
filter: "brightness(80%)"
|
||||
}
|
||||
}}
|
||||
onClick={async () => {
|
||||
setClipData(undefined);
|
||||
setClipperState(ClipperState.Idle);
|
||||
}}
|
||||
>
|
||||
Discard
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{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() {
|
||||
<Button
|
||||
variant="accent"
|
||||
sx={{ mt: 1 }}
|
||||
disabled={!clipData}
|
||||
disabled={isClipping}
|
||||
onClick={async () => {
|
||||
if (
|
||||
clipperState === ClipperState.Idle ||
|
||||
clipperState === ClipperState.Error
|
||||
) {
|
||||
await startClip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clipData || !title || !clipArea || !clipMode || !url) return;
|
||||
|
||||
const notesnook = await connectApi(false);
|
||||
@@ -433,7 +492,7 @@ export function Main() {
|
||||
window.close();
|
||||
}}
|
||||
>
|
||||
Save clip
|
||||
{clipperButtonLabelMap[clipperState]}
|
||||
</Button>
|
||||
|
||||
<Flex
|
||||
|
||||
Reference in New Issue
Block a user