mirror of
https://github.com/silvanocerza/github-gitless-sync.git
synced 2026-07-22 05:41:36 +00:00
Add dialog to upload all files to GitHub
This commit is contained in:
parent
9b5f974782
commit
81b7f2b235
4 changed files with 234 additions and 4 deletions
14
src/main.ts
14
src/main.ts
|
|
@ -6,6 +6,7 @@ import MetadataStore from "./metadata-store";
|
|||
import EventsListener from "./events/listener";
|
||||
import EventsConsumer from "./events/consumer";
|
||||
import { type Event } from "./events/types";
|
||||
import { UploadDialog } from "./views/upload-all-files-dialog/view";
|
||||
|
||||
export default class GitHubSyncPlugin extends Plugin {
|
||||
settings: GitHubSyncSettings;
|
||||
|
|
@ -81,7 +82,7 @@ export default class GitHubSyncPlugin extends Plugin {
|
|||
name: "Upload all files to GitHub",
|
||||
repeatable: false,
|
||||
icon: "arrow-up-from-line",
|
||||
callback: async () => await this.uploadAllFiles(),
|
||||
callback: async () => await this.openUploadAllFilesDialog(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -135,8 +136,13 @@ export default class GitHubSyncPlugin extends Plugin {
|
|||
this.updateStatusBarItem();
|
||||
}
|
||||
|
||||
private async uploadAllFiles() {
|
||||
// TODO
|
||||
/**
|
||||
* Opens dialog to upload all file in the tracked folder.
|
||||
* This doesn't take into account the state of the files and uploads
|
||||
* ALL files whether they've been modified or not.
|
||||
*/
|
||||
private async openUploadAllFilesDialog() {
|
||||
new UploadDialog(this).open();
|
||||
this.updateStatusBarItem();
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +271,7 @@ export default class GitHubSyncPlugin extends Plugin {
|
|||
this.uploadAllFilesRibbonIcon = this.addRibbonIcon(
|
||||
"arrow-up-from-line",
|
||||
"Upload all files to GitHub",
|
||||
async () => await this.uploadAllFiles(),
|
||||
async () => await this.openUploadAllFilesDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
10
src/views/hooks.ts
Normal file
10
src/views/hooks.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import GitHubSyncPlugin from "src/main";
|
||||
|
||||
export const PluginContext = createContext<GitHubSyncPlugin | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const usePlugin = (): GitHubSyncPlugin | undefined => {
|
||||
return useContext(PluginContext);
|
||||
};
|
||||
181
src/views/upload-all-files-dialog/component.tsx
Normal file
181
src/views/upload-all-files-dialog/component.tsx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { usePlugin } from "../hooks";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
|
||||
const UploadDialogQuestion = ({
|
||||
fileCount,
|
||||
onCancel,
|
||||
startUpload: onUpload,
|
||||
}: {
|
||||
fileCount: number;
|
||||
onCancel: () => void;
|
||||
startUpload: () => Promise<void>;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<h1 style={{ width: "fit-content" }}>Upload files to GitHub</h1>
|
||||
<p style={{ width: "fit-content" }}>
|
||||
Do you want to upload{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{fileCount}</span> files? This
|
||||
might take a while.
|
||||
</p>
|
||||
<div className="modal-button-container">
|
||||
<button onClick={onCancel}>Cancel</button>
|
||||
<button
|
||||
onClick={async () => await onUpload()}
|
||||
style={{
|
||||
color: "var(--text-on-accent)",
|
||||
backgroundColor: "var(--interactive-accent)",
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadDialogUploading = ({
|
||||
completedCount,
|
||||
fileCount,
|
||||
onCancel,
|
||||
}: {
|
||||
completedCount: number;
|
||||
fileCount: number;
|
||||
onCancel: () => void;
|
||||
}) => {
|
||||
const progress = (completedCount * 100) / fileCount;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={{ width: "fit-content" }}>
|
||||
{progress === 100 ? "All files uploaded" : "Uploading…"}
|
||||
</h1>
|
||||
<div className="setting-progress-bar">
|
||||
<div
|
||||
className="setting-progress-bar-inner"
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
Uploaded {completedCount} file{completedCount === 1 ? "" : "s"} of{" "}
|
||||
{fileCount}
|
||||
</p>
|
||||
<div className="modal-button-container">
|
||||
{/*
|
||||
We use onCancel for both cases as it won't have any effect other
|
||||
than closing the modal if upload is done
|
||||
*/}
|
||||
<button onClick={onCancel}>{progress === 100 ? "Ok" : "Cancel"}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadDialogContent = ({ onCancel }: { onCancel: () => void }) => {
|
||||
const plugin = usePlugin();
|
||||
if (!plugin) {
|
||||
// Unlikely to happen, makes TS happy though
|
||||
throw new Error("Plugin is not initialized");
|
||||
}
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [completedCount, setCompletedCount] = useState(0);
|
||||
|
||||
const files = useMemo(
|
||||
// This is not the most efficient way to get all files in the
|
||||
// folder to sync but it's quick to code.
|
||||
// I'll enhance it in the future.
|
||||
() =>
|
||||
plugin?.app.vault
|
||||
.getFiles()
|
||||
.filter((file) =>
|
||||
file.path.startsWith(plugin.settings.localContentDir),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
// Stop the upload when the component is unmounted, otherwise
|
||||
// we keep uploading even when the modal is closed.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const startUploading = async () => {
|
||||
setUploading(true);
|
||||
// We upload files sequentially to avoid conflicts.
|
||||
// GitHub rejects commits if they're made in fast succession, thus
|
||||
// forcing us to retry the failed upload.
|
||||
// So parallelization is not an option.
|
||||
for (const file of files) {
|
||||
console.log(`Uploading ${file.path}`);
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
await plugin.client.uploadFile(
|
||||
plugin.settings.githubOwner,
|
||||
plugin.settings.githubRepo,
|
||||
plugin.settings.githubBranch,
|
||||
file.path,
|
||||
);
|
||||
// Reset dirty state
|
||||
plugin.metadataStore.data[file.path].dirty = false;
|
||||
|
||||
// Gets the new SHA of the file
|
||||
const sha = await plugin.client.getFileSha(
|
||||
plugin.settings.githubOwner,
|
||||
plugin.settings.githubRepo,
|
||||
plugin.settings.githubBranch,
|
||||
file.path,
|
||||
);
|
||||
plugin.metadataStore.data[file.path].sha = sha;
|
||||
await plugin.metadataStore.save();
|
||||
|
||||
if (abortController.signal.aborted) {
|
||||
// Check abort state only after the uploaded file metadata
|
||||
// is updated, otherwise we risk having outdated SHAs
|
||||
return;
|
||||
}
|
||||
setCompletedCount((count) => count + 1);
|
||||
console.log(`Uploaded ${file.path}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
{!uploading && (
|
||||
<UploadDialogQuestion
|
||||
fileCount={files.length}
|
||||
onCancel={onCancel}
|
||||
startUpload={startUploading}
|
||||
/>
|
||||
)}
|
||||
{uploading && (
|
||||
<UploadDialogUploading
|
||||
completedCount={completedCount}
|
||||
fileCount={files.length}
|
||||
onCancel={() => {
|
||||
abortController.abort();
|
||||
onCancel();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadDialogContent;
|
||||
33
src/views/upload-all-files-dialog/view.tsx
Normal file
33
src/views/upload-all-files-dialog/view.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { Modal } from "obsidian";
|
||||
import { Root, createRoot } from "react-dom/client";
|
||||
import GitHubSyncPlugin from "src/main";
|
||||
import { PluginContext } from "../hooks";
|
||||
import UploadDialogContent from "./component";
|
||||
|
||||
/**
|
||||
* Dialog shown when user tries to upload all local files to GitHub.
|
||||
*/
|
||||
export class UploadDialog extends Modal {
|
||||
root: Root | null = null;
|
||||
|
||||
constructor(private plugin: GitHubSyncPlugin) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.root = createRoot(this.modalEl);
|
||||
this.root.render(
|
||||
<PluginContext.Provider value={this.plugin}>
|
||||
<UploadDialogContent onCancel={() => this.close()} />
|
||||
</PluginContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
// It's important to unmount the component so upload stops
|
||||
// in case it's running
|
||||
this.root?.unmount();
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue