From ee5a5f4bfb280787369c39079e58eec2ea0d296a Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 21 Jan 2025 22:12:44 +0100 Subject: [PATCH] First draft onboarding component --- src/views/onboarding/component.tsx | 458 +++++++++++++++++++++++++++++ src/views/onboarding/view.tsx | 37 +++ 2 files changed, 495 insertions(+) create mode 100644 src/views/onboarding/component.tsx create mode 100644 src/views/onboarding/view.tsx diff --git a/src/views/onboarding/component.tsx b/src/views/onboarding/component.tsx new file mode 100644 index 0000000..9f8f157 --- /dev/null +++ b/src/views/onboarding/component.tsx @@ -0,0 +1,458 @@ +import { useState, useEffect } from "react"; + +type Step = + | "welcome" + | "repo" + | "token" + | "folders" + | "sync" + | "interface" + | "first_sync" + | "done"; + +const WelcomeStepComponent = () => { + return ( +
+

First setup

+

+ This plugin is still in beta so not all features are available yet. If + you find any issues report that. +

+

+ You'll be guided through some steps to get you started. +

+
+ ); +}; + +const RepoStepComponent = ({ + setIsValid, +}: { + setIsValid: (valid: boolean) => void; +}) => { + const [owner, setOwner] = useState(""); + const [repo, setRepo] = useState(""); + const [branch, setBranch] = useState(""); + + useEffect(() => { + setIsValid( + owner.trim() !== "" && repo.trim() !== "" && branch.trim() !== "", + ); + }, [owner, repo, branch]); + + return ( +
+

+ First we need the repository owner, name and branch. +

+

+ You must use a repository that you have read and write access to. If + your vault already has content I suggest creating a new private + repository to sync. +

+
+ setOwner(e.target.value)} + /> + setRepo(e.target.value)} + /> + setBranch(e.target.value)} + /> +
+
+ ); +}; + +const TokenStepComponent = ({ + setIsValid, +}: { + setIsValid: (valid: boolean) => void; +}) => { + const [token, setToken] = useState(""); + + useEffect(() => { + setIsValid(token.trim() !== ""); + }, [token]); + + return ( +
+

+ Now we need a GitHub Personal Access Token. +

+

+ The token is used to get, create and update the files between your vault + and your GitHub repository. Click{" "} + here to + create a new token. For more information about the tokens see{" "} + + the official GitHub documentation. + +

+

+ The token must have the Contents Read and Write permissions for the same + repository you set in the previous step. +

+ setToken(e.target.value)} + /> +
+ ); +}; + +const FoldersStepComponent = () => { + return ( +
+

+ Optionally you can sync only part of your remote repository or your + vault. +

+

+ Leave blank to sync the whole repository and the whole vault. +

+
+ + +
+
+ ); +}; + +const SyncSettingsStepComponent = () => { + const [syncMode, setSyncMode] = useState<"manual" | "interval">("manual"); + const [syncOnStart, setSyncOnStart] = useState(false); + const [onConflict, setOnConflict] = useState<"ignore" | "ask" | "overwrite">( + "overwrite", + ); + + return ( +
+

+ You can sync your vault manually or every minute. You can change the + interval at a later time. +

+ +

Optionally you can sync every time you open Obsidian.

+
setSyncOnStart(!syncOnStart)} + > + +
+

In case of conflicts you can choose how to handle them.

+

+ This is not yet implemented so remote files will always overwrite local + files in case of conflicts. +

+ +
+ ); +}; + +const FirstSyncStepComponent = ({ + setIsValid, +}: { + setIsValid: (valid: boolean) => void; +}) => { + setIsValid(true); + return ( +
+

+ Now we can sync your vault with your repository. This might take a few + minutes depending on the size of your vault. +

+
+
+ Syncing vault... +
+
+
+
+
+
+
+
Show failure or success message here
+
+
+
+ ); +}; + +const OnBoardingComponent = () => { + const [step, setStep] = useState("welcome"); + + // It starts as true because the first step is just a welcome message + // so the button is already enabled. + const [isValid, setIsValid] = useState(true); + + const previousStep = () => { + setStep((step) => { + switch (step) { + case "welcome": + return "welcome"; + case "repo": + // Reset the valid state to true since we come back to welcome + setIsValid(true); + return "welcome"; + case "token": + return "repo"; + case "folders": + return "token"; + case "sync": + return "folders"; + case "interface": + return "sync"; + case "first_sync": + return "interface"; + case "done": + return "done"; + } + }); + }; + + const nextStep = () => { + setStep((step) => { + switch (step) { + case "welcome": + return "repo"; + case "repo": + return "token"; + case "token": + return "folders"; + case "folders": + return "sync"; + case "sync": + return "interface"; + case "interface": + return "first_sync"; + case "first_sync": + return "done"; + case "done": + return "done"; + } + }); + }; + + const currentStepComponent = () => { + switch (step) { + case "welcome": + return ; + case "repo": + return ; + case "token": + return ; + case "folders": + return ; + case "sync": + return ; + case "first_sync": + return ; + // case "done": + // return ; + } + }; + return ( +
+
+ + {currentStepComponent()} + +
+
+ ); +}; + +export default OnBoardingComponent; diff --git a/src/views/onboarding/view.tsx b/src/views/onboarding/view.tsx new file mode 100644 index 0000000..20edde1 --- /dev/null +++ b/src/views/onboarding/view.tsx @@ -0,0 +1,37 @@ +import { Modal } from "obsidian"; +import { Root, createRoot } from "react-dom/client"; +import GitHubSyncPlugin from "src/main"; +import { PluginContext } from "src/views/hooks"; +import OnboardingDialogComponent from "./component"; + +export class OnboardingDialog extends Modal { + root: Root | null = null; + + constructor(private plugin: GitHubSyncPlugin) { + super(plugin.app); + } + + onOpen() { + this.root = createRoot(this.modalEl); + // Make the dialog look like the settings modal + this.modalEl.addClass("mod-settings"); + this.modalEl.addClass("mod-sidebar-layout"); + // Make the dialog slightly smaller than Obsidian settings + this.modalEl.setCssProps({ + width: "60vw", + height: "50vh", + }); + + this.root.render( + + + , + ); + } + + onClose() { + const { contentEl } = this; + this.root?.unmount(); + contentEl.empty(); + } +}