diff --git a/backend/init.go b/backend/init.go index 10c4902..44f96e5 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
", b.version, runtime.GOOS, runtime.GOARCH) + onboardingComplete := false + if b.Settings.ReadwiseToken != "" { + onboardingComplete = true + } + return fmt.Sprintf("
System Details
", b.version, runtime.GOOS, runtime.GOARCH, onboardingComplete) } func (b *Backend) NavigateExplorerToLogLocation() { @@ -148,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 9c9637f..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) { @@ -23,8 +22,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 { @@ -87,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/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/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 => { 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..81cd1ba 100644 --- a/frontend/src/pages/Overview.jsx +++ b/frontend/src/pages/Overview.jsx @@ -3,18 +3,12 @@ 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) 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) @@ -33,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) }); @@ -56,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 }) } }) } @@ -91,11 +82,7 @@ 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. -

    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    ) } diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 2f74921..ac64c18 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -11,8 +11,7 @@ import { import { SaveToken, SaveCoverUploading, - SaveStoreHighlights, - MarkUploadStorePromptShown + SaveStoreHighlights } from "../../wailsjs/go/backend/Settings"; import { CheckTokenValidity } from "../../wailsjs/go/backend/Readwise"; diff --git a/frontend/wailsjs/go/backend/Settings.d.ts b/frontend/wailsjs/go/backend/Settings.d.ts index 28d2c57..0ba8e8c 100755 --- a/frontend/wailsjs/go/backend/Settings.d.ts +++ b/frontend/wailsjs/go/backend/Settings.d.ts @@ -1,8 +1,6 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -export function MarkUploadStorePromptShown():Promise; - export function Save():Promise; export function SaveCoverUploading(arg1:boolean):Promise; diff --git a/frontend/wailsjs/go/backend/Settings.js b/frontend/wailsjs/go/backend/Settings.js index 614e2fc..ba927e3 100755 --- a/frontend/wailsjs/go/backend/Settings.js +++ b/frontend/wailsjs/go/backend/Settings.js @@ -2,10 +2,6 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -export function MarkUploadStorePromptShown() { - return window['go']['backend']['Settings']['MarkUploadStorePromptShown'](); -} - export function Save() { return window['go']['backend']['Settings']['Save'](); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index d82c0be..e6eae8d 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -206,7 +206,6 @@ export namespace backend { readwise_token: string; upload_covers: boolean; upload_store_highlights: boolean; - upload_store_prompt_shown: boolean; static createFrom(source: any = {}) { return new Settings(source); @@ -217,7 +216,6 @@ export namespace backend { this.readwise_token = source["readwise_token"]; this.upload_covers = source["upload_covers"]; this.upload_store_highlights = source["upload_store_highlights"]; - this.upload_store_prompt_shown = source["upload_store_prompt_shown"]; } }