From 78034b6cf8bc31f93be670059631b6b65821664f Mon Sep 17 00:00:00 2001 From: Marcus Crane Date: Tue, 18 Jun 2024 19:48:22 +1200 Subject: [PATCH 1/4] Fix duplicate toasts on startup Signed-off-by: Marcus Crane --- frontend/src/pages/DeviceSelector.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/DeviceSelector.jsx b/frontend/src/pages/DeviceSelector.jsx index c4833d6..3cb3a60 100644 --- a/frontend/src/pages/DeviceSelector.jsx +++ b/frontend/src/pages/DeviceSelector.jsx @@ -20,12 +20,11 @@ export default function DeviceSelector() { function detectDevices() { DetectKobos() .then(devices => { - console.log(devices) if (devices == null) { - toast("No devices were found") + toast("No devices were found", { id: 'no-devices-found' }) return } - toast.success(`${devices.length} kobos detected`) + toast.success(`${devices.length} kobo${devices.length > 1 ? "s" : ""} detected`, { id: 'devices-found' }) setDevices(devices) }) .catch(err => { From 9c02d4991cd66396a53f738dec1cf53063c93bdf Mon Sep 17 00:00:00 2001 From: Marcus Crane Date: Tue, 18 Jun 2024 19:48:50 +1200 Subject: [PATCH 2/4] Remove sync prompt in favour of onboarding flow Signed-off-by: Marcus Crane --- backend/init.go | 6 +- backend/settings.go | 5 +- frontend/src/components/Navbar.jsx | 2 +- frontend/src/main.jsx | 4 +- frontend/src/pages/Onboarding.jsx | 255 +++++++++++++++++++++++++++++ frontend/src/pages/Overview.jsx | 74 --------- 6 files changed, 267 insertions(+), 79 deletions(-) create mode 100644 frontend/src/pages/Onboarding.jsx diff --git a/backend/init.go b/backend/init.go index 10c4902..b1970c9 100644 --- a/backend/init.go +++ b/backend/init.go @@ -70,7 +70,11 @@ func (b *Backend) GetPlainSystemDetails() string { } func (b *Backend) FormatSystemDetails() string { - return fmt.Sprintf("
System Details
  • Version: %s
  • Platform: %s
  • Architecture: %s
", b.version, runtime.GOOS, runtime.GOARCH) + onboardingComplete := false + if b.Settings.ReadwiseToken != "" { + onboardingComplete = true + } + return fmt.Sprintf("
System Details
  • Version: %s
  • Platform: %s
  • Architecture: %s
  • Onboarding Complete: %t
", b.version, runtime.GOOS, runtime.GOARCH, onboardingComplete) } func (b *Backend) NavigateExplorerToLogLocation() { diff --git a/backend/settings.go b/backend/settings.go index 9c9637f..cb1cffe 100644 --- a/backend/settings.go +++ b/backend/settings.go @@ -23,8 +23,9 @@ func LoadSettings(portable bool) (*Settings, error) { return nil, errors.Wrap(err, "Failed to create settings directory. Do you have proper permissions?") } s := &Settings{ - path: settingsPath, - UploadCovers: false, + path: settingsPath, + UploadStoreHighlights: true, // default on as users with only store purchased books are blocked from usage otherwise but give ample warning during setup + UploadCovers: false, } b, err := os.ReadFile(settingsPath) if err != nil { diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index c90645e..a78c9f6 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -7,7 +7,7 @@ export default function Navbar() { return (
- {location.pathname === "/overview" && Pick a different device} + {location.pathname === "/overview" && Pick a different device} {location.pathname === "/settings" && Return to device overview}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 88a7714..ed5175b 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -6,6 +6,7 @@ import { Toaster } from 'react-hot-toast' import DeviceSelector from './pages/DeviceSelector'; import Overview from './pages/Overview'; import Settings from './pages/Settings'; +import Onboarding from './pages/Onboarding' import './style.css'; @@ -13,7 +14,8 @@ const routes = ( - } /> + } /> + } /> } /> } /> diff --git a/frontend/src/pages/Onboarding.jsx b/frontend/src/pages/Onboarding.jsx new file mode 100644 index 0000000..fcbede3 --- /dev/null +++ b/frontend/src/pages/Onboarding.jsx @@ -0,0 +1,255 @@ +import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import Navbar from "../components/Navbar" +import logo from '../logo.png' +import {BrowserOpenURL} from "../../wailsjs/runtime"; +import { + FormatSystemDetails, + GetPlainSystemDetails, + GetSettings, + NavigateExplorerToLogLocation +} from "../../wailsjs/go/backend/Backend"; +import {SaveCoverUploading, SaveStoreHighlights, SaveToken} from "../../wailsjs/go/backend/Settings"; +import {toast} from "react-hot-toast"; +import {CheckTokenValidity} from "../../wailsjs/go/backend/Readwise"; + +export default function Onboarding() { + const [loaded, setLoadState] = useState(false); + const [onboardingComplete, setOnboardingComplete] = useState(false) + const [token, setToken] = useState("") + const [coversUploading, setCoversUploading] = useState(false); + const [storeHighlights, setStoreHighlights] = useState(false); // default on as users run into this issue more than not but give ample warning + const [tokenInput, setTokenInput] = useState(""); + const [systemDetails, setSystemDetails] = useState( + "Fetching system details..." + ); + + useEffect(() => { + GetSettings().then((settings) => { + setLoadState(true); + if (settings.readwise_token !== "") { + setOnboardingComplete(true) + } + setToken(settings.readwise_token); + setTokenInput(settings.readwise_token); + setCoversUploading(settings.upload_covers); + setStoreHighlights(settings.upload_store_highlights) + }); + GetPlainSystemDetails().then((details) => setSystemDetails(details)); + }, [loaded]); + + function saveAllSettings() { + if (tokenInput === "") { + toast.error("Please enter your Readwise token") + return + } + SaveToken(tokenInput); + SaveCoverUploading(coversUploading); + SaveStoreHighlights(storeHighlights); + navigate("/selector") + } + + function checkTokenValid() { + toast.promise(CheckTokenValidity(tokenInput), { + loading: "Contacting Readwise...", + success: () => "Your API token is valid!", + error: (err) => { + if (err === "401 Unauthorized") { + return "Readwise rejected your token"; + } + return err; + }, + }); + } + + const navigate = useNavigate() + + if (onboardingComplete) { + navigate("/selector") + } + return ( +
+ +
+
+ The October logo, which is a cartoon octopus reading a book. +

+ First time setup with October +

+

+ This should only take a minute of your time +

+
+
+
+
+

+ Set your Readwise access token +

+
+

+ You can find your access token at{" "} + +

+
+
e.preventDefault()} + className="sm:flex flex-col" + > +
+ setTokenInput(e.target.value)} + type="text" + name="token" + id="token" + className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:bg-gray-200 focus:bg-white rounded-md" + placeholder="Your access token goes here" + value={tokenInput} + /> +
+
+ +
+
+
+
+
+
+
+
+ + Highlight Settings + +
+
+
+ { + setStoreHighlights(!e.currentTarget.checked) + }} + checked={storeHighlights} + id="storeBought" + name="storeBought" + type="checkbox" + className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" + /> +
+
+ +

+ WARNING: If you are using the{" "} + {" "} + to sync books from the Kobo store, you should disable this option or risk having duplicate highlights! +

+
+
+
+
+
+
+ + setCoversUploading(!e.currentTarget.checked) + } + checked={coversUploading} + id="comments" + name="comments" + type="checkbox" + className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" + /> +
+
+ +

+ This will slow down the upload process a bit. It also + requires you to have to get the most benefit. +

+
+
+
+
+
+
+
+
+
+
+
+ + All done? + +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/frontend/src/pages/Overview.jsx b/frontend/src/pages/Overview.jsx index 2478d8b..988dd92 100644 --- a/frontend/src/pages/Overview.jsx +++ b/frontend/src/pages/Overview.jsx @@ -3,9 +3,6 @@ import Navbar from "../components/Navbar"; import { toast } from "react-hot-toast" import { CountDeviceBookmarks } from "../../wailsjs/go/backend/Kobo"; import { GetSettings, GetSelectedKobo, ForwardToReadwise } from "../../wailsjs/go/backend/Backend"; -import { MarkUploadStorePromptShown } from "../../wailsjs/go/backend/Settings"; -import { Dialog, Transition } from '@headlessui/react' -import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' export default function Overview(props) { const [settingsLoaded, setSettingsLoaded] = useState(false) @@ -121,77 +118,6 @@ export default function Overview(props) {
- - - -
- - -
-
- - -
-
-
-
- - Want to sync highlights from store-purchased books? - -
-

- You appear to have some highlights from officially purchased titles. -

-

- If you would like to use October to sync highlights from the Kobo store, you can do this from the Settings page. -

-

- This functionality is disabled by default in order to avoid duplicate highlights for users of the official Readwise Kobo integration. -

-

- This message won't be shown again once accepted. -

-
-
-
-
- -
-
-
-
-
-
-
) } From 9516cbf006c2dede3e503cae06b6e2837b77df29 Mon Sep 17 00:00:00 2001 From: Marcus Crane Date: Tue, 18 Jun 2024 20:14:27 +1200 Subject: [PATCH 3/4] Better validation of invalid sync states Signed-off-by: Marcus Crane --- backend/init.go | 9 +++++++++ backend/settings.go | 15 ++++----------- frontend/dist/.gitkeep | 0 frontend/src/pages/Overview.jsx | 12 +----------- frontend/src/pages/Settings.jsx | 3 +-- frontend/wailsjs/go/backend/Settings.d.ts | 2 -- frontend/wailsjs/go/backend/Settings.js | 4 ---- frontend/wailsjs/go/models.ts | 2 -- 8 files changed, 15 insertions(+), 32 deletions(-) delete mode 100644 frontend/dist/.gitkeep diff --git a/backend/init.go b/backend/init.go index b1970c9..44f96e5 100644 --- a/backend/init.go +++ b/backend/init.go @@ -152,7 +152,16 @@ func (b *Backend) PromptForLocalDBPath() error { } func (b *Backend) ForwardToReadwise() (int, error) { + highlightBreakdown := b.Kobo.CountDeviceBookmarks() + if highlightBreakdown.Total == 0 { + logrus.Error("Tried to submit highlights when there are none on device.") + return 0, fmt.Errorf("Your device doesn't seem to have any highlights so there is nothing left to sync.") + } includeStoreBought := b.Settings.UploadStoreHighlights + if !includeStoreBought && highlightBreakdown.Sideloaded == 0 { + logrus.Error("Tried to submit highlights with no sideloaded highlights + store-bought syncing disabled. Result is that no highlights would be fetched.") + return 0, fmt.Errorf("You have disabled store-bought syncing but you don't have any sideloaded highlights either. This combination means there are no highlights left to be synced.") + } content, err := b.Kobo.ListDeviceContent(includeStoreBought) if err != nil { return 0, err diff --git a/backend/settings.go b/backend/settings.go index cb1cffe..9e6adaa 100644 --- a/backend/settings.go +++ b/backend/settings.go @@ -10,11 +10,10 @@ import ( ) type Settings struct { - path string `json:"-"` - ReadwiseToken string `json:"readwise_token"` - UploadCovers bool `json:"upload_covers"` - UploadStoreHighlights bool `json:"upload_store_highlights"` - UploadStorePromptShown bool `json:"upload_store_prompt_shown"` + path string `json:"-"` + ReadwiseToken string `json:"readwise_token"` + UploadCovers bool `json:"upload_covers"` + UploadStoreHighlights bool `json:"upload_store_highlights"` } func LoadSettings(portable bool) (*Settings, error) { @@ -88,11 +87,5 @@ func (s *Settings) SaveCoverUploading(uploadCovers bool) error { func (s *Settings) SaveStoreHighlights(uploadStoreHighlights bool) error { s.UploadStoreHighlights = uploadStoreHighlights - s.UploadStorePromptShown = uploadStoreHighlights - return s.Save() -} - -func (s *Settings) MarkUploadStorePromptShown() error { - s.UploadStorePromptShown = true return s.Save() } diff --git a/frontend/dist/.gitkeep b/frontend/dist/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/pages/Overview.jsx b/frontend/src/pages/Overview.jsx index 988dd92..81cd1ba 100644 --- a/frontend/src/pages/Overview.jsx +++ b/frontend/src/pages/Overview.jsx @@ -7,11 +7,8 @@ import { GetSettings, GetSelectedKobo, ForwardToReadwise } from "../../wailsjs/g export default function Overview(props) { const [settingsLoaded, setSettingsLoaded] = useState(false) const [readwiseConfigured, setReadwiseConfigured] = useState(false) - const [uploadStorePromptSeen, setUploadStorePromptSeen] = useState(false) const [selectedKobo, setSelectedKobo] = useState({}) const [highlightCounts, setHighlightCounts] = useState({}) - const [storeUploadWarningOpen, setStoreUploadWarningOpen] = useState(false) - const [uploadStoreHighlights, setUploadStoreHighlights] = useState(false) const cancelButtonRef = useRef(null) @@ -30,7 +27,6 @@ export default function Overview(props) { useEffect(() => { GetSettings().then((settings) => { setSettingsLoaded(true); - setUploadStorePromptSeen(settings.upload_store_prompt_shown) setReadwiseConfigured(settings.readwise_token !== "") setUploadStoreHighlights(settings.upload_store_highlights) }); @@ -53,10 +49,8 @@ export default function Overview(props) { .catch(err => { if (err.includes("401")) { toast.error("Received 401 Unauthorised from Readwise. Is your access token correct?", { id: toastId }) - } else if (err.includes("failed to upload covers")) { - toast.error(err, { id: toastId }) } else { - toast.error(`There was a problem sending your highlights: ${err}`, { id: toastId }) + toast.error(err, { id: toastId, duration: 8000 }) } }) } @@ -88,11 +82,7 @@ export default function Overview(props) {