Skip to content

Commit

Permalink
Merge pull request #129 from marcus-crane/v1.10.0
Browse files Browse the repository at this point in the history
v1.10.0
  • Loading branch information
marcus-crane authored Jun 23, 2024
2 parents 85e9b0b + 6eb4b67 commit 1d074c3
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 114 deletions.
15 changes: 14 additions & 1 deletion backend/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ func (b *Backend) GetPlainSystemDetails() string {
}

func (b *Backend) FormatSystemDetails() string {
return fmt.Sprintf("<details><summary>System Details</summary><ul><li>Version: %s</li><li>Platform: %s</li><li>Architecture: %s</li></details>", b.version, runtime.GOOS, runtime.GOARCH)
onboardingComplete := false
if b.Settings.ReadwiseToken != "" {
onboardingComplete = true
}
return fmt.Sprintf("<details><summary>System Details</summary><ul><li>Version: %s</li><li>Platform: %s</li><li>Architecture: %s</li><li>Onboarding Complete: %t</li></details>", b.version, runtime.GOOS, runtime.GOARCH, onboardingComplete)
}

func (b *Backend) NavigateExplorerToLogLocation() {
Expand Down Expand Up @@ -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
Expand Down
20 changes: 7 additions & 13 deletions backend/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}
2 changes: 1 addition & 1 deletion frontend/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function Navbar() {
return (
<header className="flex p-3">
<div className="w-full text-left text-gray-900 dark:text-gray-300">
{location.pathname === "/overview" && <NavLink to="/"><CpuChipIcon className="h-5 w-5 inline-block" /> Pick a different device</NavLink>}
{location.pathname === "/overview" && <NavLink to="/selector"><CpuChipIcon className="h-5 w-5 inline-block" /> Pick a different device</NavLink>}
{location.pathname === "/settings" && <NavLink to="/overview"><BookmarkIcon className="h-5 w-5 inline-block" /> Return to device overview</NavLink>}
</div>
<div className="w-full text-right text-gray-900 dark:text-gray-300">
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ 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';

const routes = (
<React.StrictMode>
<HashRouter>
<Routes>
<Route path="/" element={<DeviceSelector />} />
<Route path="/" element={<Onboarding />} />
<Route path="/selector" element={<DeviceSelector />} />
<Route path="/overview" element={<Overview />} />
<Route path="/settings" element={<Settings />} />
</Routes>
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/pages/DeviceSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
255 changes: 255 additions & 0 deletions frontend/src/pages/Onboarding.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-screen bg-gray-100 dark:bg-gray-800 flex flex-col">
<Navbar />
<div className="flex-grow items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14">
<div className="space-y-2">
<img
className="mx-auto h-36 w-auto logo-animation"
src={logo}
alt="The October logo, which is a cartoon octopus reading a book."
/>
<h2 className="text-center text-3xl font-extrabold text-gray-900 dark:dark:text-gray-300">
First time setup with October
</h2>
<p className="mt-0 text-center text-sm text-gray-600 dark:text-gray-400">
This should only take a minute of your time
</p>
</div>
<div className="space-y-4">
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-300">
Set your Readwise access token
</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
<p>
You can find your access token at{" "}
<button
className="text-gray-600 dark:text-gray-400 underline"
onClick={() =>
BrowserOpenURL("https://readwise.io/access_token")
}
>
https://readwise.io/access_token
</button>
</p>
</div>
<form
onSubmit={(e) => e.preventDefault()}
className="sm:flex flex-col"
>
<div className="w-full mt-4 sm:flex sm:items-center">
<input
onChange={(e) => 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}
/>
</div>
<div className="w-full mt-4 sm:flex flex-row">
<button
onClick={checkTokenValid}
type="submit"
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:text-sm"
>
Validate
</button>
</div>
</form>
</div>
</div>
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
<div className="shadow overflow-hidden sm:rounded-md">
<div className="px-4 py-5 bg-white dark:bg-slate-700 space-y-6 sm:p-6">
<fieldset>
<legend className="text-base font-medium text-gray-900 dark:text-gray-300">
Highlight Settings
</legend>
<div className="mt-4 space-y-4">
<div className="flex items-start">
<div className="flex items-center h-5">
<input
// TODO: This probably causes the render method to seize up
onInput={(e) => {
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"
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor="storeBought"
className="font-medium text-gray-700 dark:text-gray-300"
>
Upload highlights from store-bought books
</label>
<p className="text-red-500 dark:text-red-400">
WARNING: If you are using the{" "}
<button
className="text-gray-600 dark:text-gray-400 underline"
onClick={() =>
BrowserOpenURL(
"https://help.readwise.io/article/135-how-do-i-import-highlights-from-kobo"
)
}
>
official Readwise integration
</button>{" "}
to sync books from the Kobo store, you should <strong>disable</strong> this option or risk having duplicate highlights!
</p>
</div>
</div>
</div>
<div className="mt-4 space-y-4">
<div className="flex items-start">
<div className="flex items-center h-5">
<input
// TODO: This probably causes the render method to seize up
onInput={(e) =>
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"
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor="comments"
className="font-medium text-gray-700 dark:text-gray-300"
>
Upload covers
</label>
<p className="text-gray-500 dark:text-gray-400">
This will slow down the upload process a bit. It also
requires you to have <button className="text-gray-600 dark:text-gray-400 underline" onClick={() => BrowserOpenURL("https://october.utf9k.net/prerequisites#configuring-calibre-to-sync-high-quality-covers")}>configured Calibre</button> to get the most benefit.
</p>
</div>
</div>
</div>
</fieldset>
</div>
</div>
</div>
<div className="bg-white dark:bg-slate-700 shadow sm:rounded-lg">
<div className="shadow overflow-hidden sm:rounded-md">
<div className="px-4 py-5 bg-white dark:bg-slate-700 space-y-6 sm:p-6">
<fieldset>
<legend className="text-base font-medium text-gray-900 dark:text-gray-300">
All done?
</legend>
<div className="space-y-4">
<div className="flex items-start">
<div className="w-full mt-4 sm:flex flex-row">
<button
onClick={() =>
FormatSystemDetails().then((details) =>
BrowserOpenURL(
`https://github.com/marcus-crane/october/issues/new?body=${encodeURI(
"I have an issue with...\n\n---\n\n" + details
)}`
)
)
}
type="submit"
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:mt-0 sm:text-sm"
>
Having trouble?
</button>
<button
onClick={saveAllSettings}
type="submit"
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:ml-3 sm:text-sm"
>
Complete setup
</button>
</div>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
Loading

0 comments on commit 1d074c3

Please sign in to comment.