-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ Rewrite UI using a hint of typescript, and add select folder
- Loading branch information
Showing
19 changed files
with
1,011 additions
and
347 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import * as React from "react" | ||
|
||
import FileBrowser from "./FileBrowser" | ||
import Footer from "./Footer" | ||
import {QueryClient, QueryClientProvider} from "react-query"; | ||
import { ReactQueryDevtools } from 'react-query/devtools' | ||
import Auth from "./Auth"; | ||
|
||
// @ts-ignore:next-line | ||
const OctoPrint = window.OctoPrint | ||
const PLUGIN_ID = "onedrive_backup" | ||
|
||
const queryClient = new QueryClient({ | ||
defaultOptions: { | ||
queries: { | ||
refetchOnWindowFocus: false, | ||
} | ||
} | ||
}) | ||
|
||
export default function Index() { | ||
return ( | ||
<QueryClientProvider client={queryClient}> | ||
<ReactQueryDevtools initialIsOpen={false}/> | ||
<App /> | ||
</QueryClientProvider> | ||
) | ||
} | ||
|
||
function App () { | ||
// TODO error boundary | ||
return ( | ||
<> | ||
<h5>OneDrive Backup Plugin</h5> | ||
|
||
<Auth /> | ||
|
||
<hr /> | ||
|
||
<FileBrowser /> | ||
|
||
<Footer /> | ||
</> | ||
) | ||
} |
106 changes: 106 additions & 0 deletions
106
octoprint_onedrive_backup/static/src/components/Auth.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import * as React from "react" | ||
import {useQuery} from "react-query"; | ||
import useSocket from "../hooks/useSocket"; | ||
|
||
// @ts-ignore:next-line | ||
const OctoPrint = window.OctoPrint | ||
const PLUGIN_ID = "onedrive_backup" | ||
|
||
interface AuthData { | ||
url: string, | ||
code: string | ||
} | ||
|
||
interface PluginSocketMessage { | ||
data: { | ||
plugin: string; | ||
data: { | ||
type: string; | ||
content: object; | ||
} | ||
}; | ||
} | ||
|
||
interface AuthProps { | ||
accounts: string[]; | ||
} | ||
|
||
export default function Auth () { | ||
const [authSuccess, setAuthSuccess] = React.useState<boolean>(false) | ||
const [authLoading, setAuthLoading] = React.useState<boolean>(false) | ||
|
||
const {data, isLoading, error, refetch} = useQuery( | ||
"accounts", | ||
() => { | ||
return OctoPrint.simpleApiGet(PLUGIN_ID) | ||
} | ||
) | ||
|
||
const hasAccount = Boolean(data?.accounts.length) | ||
const addingAccount = data?.flow | ||
|
||
const accountsList = hasAccount ? data.accounts.map(account => ( | ||
<li key={account}>{account}</li> | ||
)) : [] | ||
|
||
useSocket("plugin", (message) => { | ||
const plugin = message.data.plugin | ||
if (plugin === "onedrive_backup") { | ||
const type = message.data.data.type | ||
if (type === "auth_done") { | ||
setAuthSuccess(true) | ||
// Rerun query to make new data show up | ||
refetch() | ||
} | ||
} | ||
}) | ||
|
||
const addAccount = () => { | ||
setAuthLoading(true) | ||
OctoPrint.simpleApiCommand(PLUGIN_ID, "startAuth").done((data) => { | ||
// The parameters are also passed through `data` here, but refetching the original query is less code | ||
refetch() | ||
setAuthLoading(false) | ||
}) | ||
} | ||
|
||
const loading = isLoading || authLoading | ||
|
||
return ( | ||
<> | ||
{hasAccount | ||
? <> | ||
<p>Account registered:</p> | ||
<ul>{accountsList}</ul> | ||
</> | ||
: <p> | ||
No Microsoft accounts registered, add one below | ||
</p> | ||
} | ||
|
||
<div className={"row-fluid"} > | ||
<button className={"btn btn-success"} onClick={addAccount}> | ||
<i className={"fas fa-fw " + (loading ? "fa-spin fa-spinner" : hasAccount ? "fa-user-edit" : "fa-user-plus" )} /> | ||
{" "}{hasAccount ? "Change Account" : "Add account"} | ||
</button> | ||
</div> | ||
|
||
{addingAccount && <div className={"row-fluid"}> | ||
<p style={{marginTop: "10px"}}> | ||
Head to <a href={data.flow.verification_uri} target={"_blank"} rel={"noreferrer"}>{data.flow.verification_uri}</a> and enter code | ||
{" "}<code>{data.flow.user_code}</code> to connect your Microsoft account | ||
</p> | ||
</div> | ||
} | ||
|
||
{authSuccess && <div className={"alert alert-success"}> | ||
<p> | ||
<strong>Success! </strong> | ||
Your account has been successfully added to the plugin. | ||
Make sure to configure the path to upload your backups to below. | ||
</p> | ||
</div> | ||
} | ||
</> | ||
) | ||
} |
133 changes: 133 additions & 0 deletions
133
octoprint_onedrive_backup/static/src/components/FileBrowser.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import * as React from "react" | ||
import { Alert } from "./bootstrap/" | ||
import {useQuery} from "react-query"; | ||
|
||
//@ts-ignore:next-line | ||
const OctoPrint = window.OctoPrint | ||
|
||
export default function FileBrowser () { | ||
const [active, setActive] = React.useState<boolean>(false) | ||
const [currentFolder, setCurrentFolder] = React.useState<string>("root") | ||
|
||
const [history, setHistory] = React.useState<string[]>([]) | ||
const [historyPos, setHistoryPos] = React.useState<number>(0) | ||
|
||
const fetchFiles = (itemId: string = "root") => { | ||
if (itemId === "root") { | ||
return OctoPrint.simpleApiCommand("onedrive_backup", "folders") | ||
} else { | ||
return OctoPrint.simpleApiCommand("onedrive_backup", "foldersById", { id: itemId }) | ||
} | ||
} | ||
|
||
const {data, isLoading, error: queryError} = useQuery( | ||
["folders", currentFolder], | ||
() => fetchFiles(currentFolder), | ||
{ | ||
enabled: active, | ||
} | ||
) | ||
|
||
// TODO - make sure no duplicate network requests, with the auth component | ||
const {data: configData, isLoading: configDataLoading, refetch: refetchConfig} = useQuery( | ||
"accounts", | ||
() => { | ||
return OctoPrint.simpleApiGet("onedrive_backup") | ||
} | ||
) | ||
|
||
const handleSelectFolder = (target) => { | ||
setHistory(prevState => prevState.concat([currentFolder])) | ||
setCurrentFolder(target) | ||
setHistoryPos(prevState => prevState + 1) | ||
} | ||
|
||
const handleBack = () => { | ||
if (history.length >= 0 && historyPos > 0) { | ||
const newHistoryPos = historyPos - 1 | ||
const newFolder = history[newHistoryPos] | ||
setHistoryPos(newHistoryPos) | ||
setCurrentFolder(newFolder) | ||
setHistory(prevState => prevState.slice(0, newHistoryPos)) | ||
} | ||
} | ||
|
||
const handleActivateFolder = (folder) => { | ||
OctoPrint.simpleApiCommand("onedrive_backup", "setFolder", { id: folder.id, path: folder.path }).done( | ||
() => refetchConfig() | ||
) | ||
} | ||
|
||
const files = data?.folders ? data.folders.map(folder => ( | ||
<tr key={folder.id}> | ||
<td> | ||
{folder.childCount > 0 ? | ||
<a onClick={() => handleSelectFolder(folder.id)} style={{ cursor: "pointer" }}> | ||
<i className={"far fa-folder-open"}/> {folder.name} | ||
</a> | ||
: <span><i className={"far fa-folder-open"}/> {folder.name}</span> | ||
} | ||
</td> | ||
<td> | ||
<button className={"btn btn-primary btn-mini"} onClick={() => handleActivateFolder(folder)}> | ||
Set upload destination | ||
</button> | ||
</td> | ||
</tr> | ||
)) : [] | ||
|
||
const hasError = queryError || data?.error | ||
const loading = isLoading || configDataLoading | ||
|
||
return ( | ||
<> | ||
{ | ||
configDataLoading | ||
? <span><i className={"fas fa-spin fa-spinner"} /> Loading...</span> | ||
: <span>Currently configured upload destination: {configData?.folder.path ? <code>{configData.folder.path}</code> : "None"}</span> | ||
} | ||
{hasError && | ||
<Alert variant={"error"}> | ||
<i className={"fas fa-times text-error"} /><strong> Error:</strong> | ||
{typeof data.error === "string" ? data.error : "Unknown error. Check octoprint.log for details."} | ||
</Alert>} | ||
{active | ||
? ( | ||
<table className={"table"}> | ||
<thead> | ||
<tr> | ||
<th>Folder name {loading && <i className={"fas fa-spin fa-spinner"} />} </th> | ||
<th>Actions</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{historyPos > 0 && | ||
<tr> | ||
<td> | ||
<span onClick={handleBack} style={{ cursor: "pointer" }}><i className={"fas fa-arrow-left"} /> Back</span> | ||
</td> | ||
<td/> | ||
</tr>} | ||
{files.length ? files : ( | ||
<> | ||
{!isLoading && !hasError && ( | ||
<tr> | ||
<td> | ||
<i className={"fas fa-times"} /> No sub-folders found | ||
</td> | ||
</tr> | ||
)} | ||
</> | ||
)} | ||
</tbody> | ||
</table> | ||
) : | ||
<div className={"row-fluid"}> | ||
<button className={"btn btn-primary"} onClick={() => setActive(true)}> | ||
<i className={"fa-fw " + (isLoading ? "fas fa-spin fa-spinner" : "far fa-folder-open")}/> Change folder | ||
</button> | ||
</div> | ||
} | ||
</> | ||
) | ||
} |
Oops, something went wrong.