mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +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() {
|
export function Main() {
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
// const [colorMode, setColorMode] = useColorMode();
|
// const [colorMode, setColorMode] = useColorMode();
|
||||||
@@ -98,7 +112,6 @@ export function Main() {
|
|||||||
const [title, setTitle] = useState<string>();
|
const [title, setTitle] = useState<string>();
|
||||||
const [hasPermission, setHasPermission] = useState<boolean>(false);
|
const [hasPermission, setHasPermission] = useState<boolean>(false);
|
||||||
const [url, setUrl] = useState<string>();
|
const [url, setUrl] = useState<string>();
|
||||||
const [clipNonce, setClipNonce] = useState(0);
|
|
||||||
const [clipMode, setClipMode] = usePersistentState<ClipMode>(
|
const [clipMode, setClipMode] = usePersistentState<ClipMode>(
|
||||||
"clipMode",
|
"clipMode",
|
||||||
"simplified"
|
"simplified"
|
||||||
@@ -107,10 +120,12 @@ export function Main() {
|
|||||||
"clipArea",
|
"clipArea",
|
||||||
"article"
|
"article"
|
||||||
);
|
);
|
||||||
const [isClipping, setIsClipping] = useState(false);
|
|
||||||
const [note, setNote] = usePersistentState<ItemReference>("note");
|
const [note, setNote] = usePersistentState<ItemReference>("note");
|
||||||
const [refs, setRefs] = usePersistentState<SelectedReference[]>("refs", []);
|
const [refs, setRefs] = usePersistentState<SelectedReference[]>("refs", []);
|
||||||
const [clipData, setClipData] = useState<ClipData>();
|
const [clipData, setClipData] = useState<ClipData>();
|
||||||
|
const [clipperState, setClipperState] = useState<ClipperState>(
|
||||||
|
ClipperState.Idle
|
||||||
|
);
|
||||||
const pageTitle = useRef<string>();
|
const pageTitle = useRef<string>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -129,7 +144,6 @@ export function Main() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!clipArea || !clipMode) return;
|
|
||||||
if (
|
if (
|
||||||
!isPremium &&
|
!isPremium &&
|
||||||
(clipMode === "complete" || clipMode === "screenshot")
|
(clipMode === "complete" || clipMode === "screenshot")
|
||||||
@@ -137,25 +151,8 @@ export function Main() {
|
|||||||
setClipMode("simplified");
|
setClipMode("simplified");
|
||||||
return;
|
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(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -168,6 +165,32 @@ export function Main() {
|
|||||||
})();
|
})();
|
||||||
}, [settings]);
|
}, [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) {
|
if (!hasPermission && !!settings?.corsProxy) {
|
||||||
return (
|
return (
|
||||||
<FlexScrollContainer style={{ maxHeight: 560 }}>
|
<FlexScrollContainer style={{ maxHeight: 560 }}>
|
||||||
@@ -241,10 +264,11 @@ export function Main() {
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setError(undefined);
|
||||||
|
setClipperState(ClipperState.Idle);
|
||||||
setClipArea(item.id);
|
setClipArea(item.id);
|
||||||
setClipNonce((s) => ++s);
|
|
||||||
}}
|
}}
|
||||||
disabled={isClipping}
|
disabled={isClipping || clipperState === ClipperState.Clipped}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
borderRadius: "default",
|
borderRadius: "default",
|
||||||
@@ -283,10 +307,15 @@ export function Main() {
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setError(undefined);
|
||||||
|
setClipperState(ClipperState.Idle);
|
||||||
setClipMode(item.id);
|
setClipMode(item.id);
|
||||||
setClipNonce((s) => ++s);
|
|
||||||
}}
|
}}
|
||||||
disabled={isClipping || (item.pro && !isPremium)}
|
disabled={
|
||||||
|
isClipping ||
|
||||||
|
clipperState === ClipperState.Clipped ||
|
||||||
|
(item.pro && !isPremium)
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
borderRadius: "default",
|
borderRadius: "default",
|
||||||
@@ -314,31 +343,53 @@ export function Main() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{clipData && clipData.data && !isClipping && (
|
{clipData && clipData.data && !isClipping && (
|
||||||
<Text
|
<Flex sx={{ gap: 1, justifyContent: "space-between" }}>
|
||||||
variant="body"
|
<Text
|
||||||
sx={{
|
variant="body"
|
||||||
mt: 1,
|
sx={{
|
||||||
bg: "shade",
|
flex: 1,
|
||||||
color: "accent",
|
mt: 1,
|
||||||
p: 1,
|
bg: "shade",
|
||||||
border: "1px solid var(--accent)",
|
color: "accent",
|
||||||
borderRadius: "default",
|
p: 1,
|
||||||
cursor: "pointer",
|
border: "1px solid var(--accent)",
|
||||||
":hover": {
|
borderRadius: "default",
|
||||||
filter: "brightness(80%)"
|
cursor: "pointer",
|
||||||
}
|
":hover": {
|
||||||
}}
|
filter: "brightness(80%)"
|
||||||
onClick={async () => {
|
}
|
||||||
const winUrl = URL.createObjectURL(
|
}}
|
||||||
new Blob(["\ufeff", clipData.data], { type: "text/html" })
|
onClick={async () => {
|
||||||
);
|
const winUrl = URL.createObjectURL(
|
||||||
await browser.windows.create({
|
new Blob(["\ufeff", clipData.data], { type: "text/html" })
|
||||||
url: winUrl
|
);
|
||||||
});
|
await browser.windows.create({
|
||||||
}}
|
url: winUrl
|
||||||
>
|
});
|
||||||
Clip done. Click here to preview.
|
}}
|
||||||
</Text>
|
>
|
||||||
|
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 && (
|
{error && (
|
||||||
@@ -357,7 +408,7 @@ export function Main() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setClipNonce((s) => ++s);
|
await startClip();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ERROR_MAP[error] || error}
|
{ERROR_MAP[error] || error}
|
||||||
@@ -400,8 +451,16 @@ export function Main() {
|
|||||||
<Button
|
<Button
|
||||||
variant="accent"
|
variant="accent"
|
||||||
sx={{ mt: 1 }}
|
sx={{ mt: 1 }}
|
||||||
disabled={!clipData}
|
disabled={isClipping}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
if (
|
||||||
|
clipperState === ClipperState.Idle ||
|
||||||
|
clipperState === ClipperState.Error
|
||||||
|
) {
|
||||||
|
await startClip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!clipData || !title || !clipArea || !clipMode || !url) return;
|
if (!clipData || !title || !clipArea || !clipMode || !url) return;
|
||||||
|
|
||||||
const notesnook = await connectApi(false);
|
const notesnook = await connectApi(false);
|
||||||
@@ -433,7 +492,7 @@ export function Main() {
|
|||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Save clip
|
{clipperButtonLabelMap[clipperState]}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
Reference in New Issue
Block a user