Skip to content

Commit 9f4cd90

Browse files
committed
feat(pendingStatus): Pending status view.
1 parent 583f8e1 commit 9f4cd90

18 files changed

+1817
-42
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"@typescript-eslint/parser": "5.29.0",
8484
"acorn": "^8.7.1",
8585
"aggregate-error": "^4.0.0",
86+
"antd": "^5.11.4",
8687
"assert": "^2.0.0",
8788
"aws-crt": "^1.12.1",
8889
"buffer": "^6.0.3",

pnpm-lock.yaml

Lines changed: 994 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/baseTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ export interface InvioPluginSettings {
100100
currLogLevel?: string;
101101
autoRunEveryMilliseconds?: number;
102102
initRunAfterMilliseconds?: number;
103+
104+
autoCheckEveryMilliseconds?: number;
105+
initCheckAfterMilliseconds?: number;
103106
agreeToUploadExtraMetadata?: boolean;
104107
concurrency?: number;
105108
syncConfigDir?: boolean;
@@ -217,4 +220,5 @@ export const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX =
217220
"sync_plans_hist_exported_on_";
218221
export const DEFAULT_LOG_HISTORY_FILE_PREFIX = "log_hist_exported_on_";
219222

220-
export type SyncTriggerSourceType = "manual" | "auto" | "dry" | "autoOnceInit" | "force";
223+
// pre - only check pending files but no more actions
224+
export type SyncTriggerSourceType = "manual" | "auto" | "dry" | "autoOnceInit" | "force" | "pre";

src/components/CheckSettingsModal.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { App, Modal, Setting } from "obsidian";
2+
import type InvioPlugin from "../main"; // unavoidable
3+
import type { TransItemType } from "../i18n";
4+
import svg from '../utils/svg';
5+
import { InvioSettingTab } from '../settings'
6+
export class CheckSettingsModal extends Modal {
7+
readonly plugin: InvioPlugin;
8+
constructor(app: App, plugin: InvioPlugin) {
9+
super(app);
10+
this.plugin = plugin;
11+
}
12+
13+
info(msg: string) {
14+
let { contentEl } = this;
15+
let container = contentEl.querySelector('.loading-logs');
16+
if (!container) {
17+
container = contentEl.createDiv('loading-logs');
18+
}
19+
const logItem = container.createDiv('loading-log-item');
20+
logItem.innerText = msg;
21+
}
22+
23+
onOpen() {
24+
let { contentEl } = this;
25+
const t = (x: TransItemType, vars?: any) => {
26+
return this.plugin.i18n.t(x, vars);
27+
};
28+
29+
contentEl.createEl("h2", {
30+
text: t('settings_check_modal_title')
31+
});
32+
33+
new Setting(contentEl)
34+
.setDesc(t('settings_check_modal_desc'))
35+
36+
new Setting(contentEl)
37+
.setName(t("settings_autocheck"))
38+
.setDesc(t("settings_autocheck_desc"))
39+
.addDropdown((dropdown) => {
40+
dropdown.addOption("-1", t("settings_autocheck_notset"));
41+
dropdown.addOption(`${1000 * 60 * 1}`, t("settings_autocheck_1min"));
42+
dropdown.addOption(`${1000 * 60 * 5}`, t("settings_autocheck_5min"));
43+
dropdown.addOption(`${1000 * 60 * 10}`, t("settings_autocheck_10min"));
44+
dropdown.addOption(`${1000 * 60 * 30}`, t("settings_autocheck_30min"));
45+
46+
47+
dropdown
48+
.setValue(`${this.plugin.settings.autoCheckEveryMilliseconds}`)
49+
.onChange(async (val: string) => {
50+
const realVal = parseInt(val);
51+
this.plugin.settings.autoCheckEveryMilliseconds = realVal;
52+
await this.plugin.saveSettings();
53+
if (
54+
(realVal === undefined || realVal === null || realVal <= 0) &&
55+
this.plugin.autoCheckIntervalID !== undefined
56+
) {
57+
// clear
58+
window.clearInterval(this.plugin.autoCheckIntervalID);
59+
this.plugin.autoCheckIntervalID = undefined;
60+
} else if (
61+
realVal !== undefined &&
62+
realVal !== null &&
63+
realVal > 0
64+
) {
65+
const intervalID = window.setInterval(() => {
66+
this.plugin.pendingView();
67+
}, realVal);
68+
this.plugin.autoCheckIntervalID = intervalID;
69+
this.plugin.registerInterval(intervalID);
70+
}
71+
});
72+
});
73+
74+
new Setting(contentEl)
75+
.setName(t("settings_checkoncestartup"))
76+
.setDesc(t("settings_checkoncestartup_desc"))
77+
.addDropdown((dropdown) => {
78+
dropdown.addOption("-1", t("settings_checkoncestartup_notset"));
79+
dropdown.addOption(
80+
`${1000 * 1 * 1}`,
81+
t("settings_checkoncestartup_1sec")
82+
);
83+
dropdown.addOption(
84+
`${1000 * 10 * 1}`,
85+
t("settings_checkoncestartup_10sec")
86+
);
87+
dropdown.addOption(
88+
`${1000 * 30 * 1}`,
89+
t("settings_checkoncestartup_30sec")
90+
);
91+
dropdown
92+
.setValue(`${this.plugin.settings.initCheckAfterMilliseconds}`)
93+
.onChange(async (val: string) => {
94+
const realVal = parseInt(val);
95+
this.plugin.settings.initCheckAfterMilliseconds = realVal;
96+
await this.plugin.saveSettings();
97+
});
98+
});
99+
100+
new Setting(contentEl)
101+
.setName(t('settings_check_modal_more'))
102+
.setDesc(t('settings_check_modal_more_desc'))
103+
.addButton(async (button) => {
104+
button.setButtonText(t('settings_check_modal_more_btn'));
105+
button.onClick(async () => {
106+
this.close();
107+
(this.plugin.app as any).setting.open()
108+
});
109+
})
110+
111+
new Setting(contentEl)
112+
.addButton((button) => {
113+
button.setButtonText('OK');
114+
button.onClick(async () => {
115+
this.close();
116+
});
117+
})
118+
}
119+
120+
onClose() {
121+
let { contentEl } = this;
122+
contentEl.empty();
123+
}
124+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.header {
2+
text-align: center;
3+
width: 100%;
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
}
8+
9+
.sub-header {
10+
margin: 8px;
11+
font-weight: bold;
12+
}
13+
14+
.header > .icon {
15+
margin-right: 6px;
16+
}
17+
18+
.header > .settings {
19+
position: absolute;
20+
right: 14px;
21+
width: 18px;
22+
height: 18px;
23+
cursor: pointer;
24+
margin-top: 4px;
25+
}
26+
27+
.empty-report {
28+
display: flex;
29+
flex-direction: column;
30+
margin-top: 12px;
31+
justify-content: center;
32+
align-items: center;
33+
margin-bottom: 18px;
34+
opacity: 0.75;
35+
}
36+
37+
.empty-report > .icon {
38+
width: 32px;
39+
height: 32px;
40+
opacity: 0.1;
41+
}
42+
43+
.empty-report > span {
44+
margin-top: 22px;
45+
}
46+
47+
48+
.icon {
49+
width: 16px;
50+
height: 16px;
51+
}
52+
53+
.actions {
54+
display: flex;
55+
justify-content: center;
56+
margin-top: 22px;
57+
}
58+
59+
.actions > button {
60+
color: white;
61+
}

src/components/PendingStatsView.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import * as React from "react";
2+
import { Tree, Button } from 'antd';
3+
import type { DataNode, TreeProps } from 'antd/es/tree';
4+
5+
import { throttle } from 'lodash';
6+
import classnames from 'classnames';
7+
import useStore, { LogType } from './pendingStore';
8+
import styles from './PendingStatsView.module.css';
9+
import { AlertTriangle, CheckCircle, ArrowDownUp, Activity, LineChart, Cog, Siren, FileType, ScrollText, Info, AlertCircle, XCircle, ChevronRight, Terminal, RedoDot, UploadCloud, DownloadCloud } from 'lucide-react';
10+
import { log } from '../moreOnLog'
11+
import { Utils } from '../utils/utils';
12+
import { Notice } from "obsidian";
13+
import Logo from './InvioLogo';
14+
import InvioPlugin from "src/main";
15+
import { CheckSettingsModal } from './CheckSettingsModal';
16+
17+
export const PendingStatsViewComponent = (props: { plugin: InvioPlugin }) => {
18+
const { record, toLocalSelected, toRemoteSelected, getToLocalFileList, getToRemoteFileList, updateSelectedToLocalFileList, updateSelectedToRemoteFileList } = useStore();
19+
const toLocalTouched = getToLocalFileList();
20+
console.log('toLocaltouched - ', toLocalTouched)
21+
22+
const toRemoteTouched = getToRemoteFileList();
23+
console.log('toRemoteTouched - ', toRemoteTouched)
24+
const selectedKeys = [];
25+
if (!(toLocalTouched.length > 0) && !(toRemoteTouched.length > 0)) {
26+
return <>
27+
<h4 className={styles['header']}><Logo className={styles['icon']} />Changed Files</h4>
28+
<div className={styles['emptyReport']}>
29+
<ScrollText className={styles['icon']} />
30+
<span>No file changed</span>
31+
</div>
32+
</>
33+
}
34+
35+
const treeToLocalData: DataNode[] = toLocalTouched.map((item: any) => {
36+
item.title = item.key
37+
item.key = item.key
38+
return item;
39+
})
40+
const treeToRemoteData: DataNode[] = toRemoteTouched.map((item: any) => {
41+
item.title = item.key
42+
item.key = item.key
43+
return item;
44+
})
45+
46+
const onSelect = (selectedKeys: any, info: any, type: `ToLocal` | `ToRemote`) => {
47+
console.log('selected', selectedKeys, info);
48+
const key = selectedKeys[0]
49+
props.plugin.viewFileDiff(key, type === 'ToLocal' ? 'RemoteToLocal' : 'LocalToRemote')
50+
setTimeout(() => {
51+
selectedKeys = []
52+
}, 3000)
53+
};
54+
55+
const onToLocalSelect: TreeProps['onSelect'] = (selectedKeys: any, info: any) => {
56+
onSelect(selectedKeys, info, 'ToLocal')
57+
}
58+
59+
const onToRemoteSelect: TreeProps['onSelect'] = (selectedKeys: any, info: any) => {
60+
onSelect(selectedKeys, info, 'ToRemote')
61+
}
62+
63+
const onToLocalCheck: TreeProps['onCheck'] = (checkedKeys: string[], info: any) => {
64+
console.log('onLocalCheck', checkedKeys, info);
65+
updateSelectedToLocalFileList(checkedKeys.filter(key => toLocalTouched.find(t => t.key === key)))
66+
};
67+
68+
const onToRemoteCheck: TreeProps['onCheck'] = (checkedKeys: string[], info: any) => {
69+
console.log('onToRemoteCheck', checkedKeys, info);
70+
updateSelectedToRemoteFileList(checkedKeys.filter(key => toRemoteTouched.find(t => t.key === key)))
71+
};
72+
73+
const startSync = async () => {
74+
console.log('start syncing...', [...toLocalSelected, ...toRemoteSelected])
75+
await props.plugin.syncRun('manual', [...toLocalSelected, ...toRemoteSelected])
76+
}
77+
78+
const openSettings = () => {
79+
console.log('open settings');
80+
const modal = new CheckSettingsModal(props.plugin.app, props.plugin);
81+
modal.open();
82+
}
83+
84+
return (
85+
<>
86+
<h4 className={styles['header']}>
87+
<Logo className={styles['icon']} />
88+
Touched Files Status
89+
<Cog className={styles['settings']} onClick={openSettings} />
90+
</h4>
91+
{
92+
treeToLocalData?.length > 0 ?
93+
<>
94+
<div className={styles['subHeader']}>Online Changed Files</div>
95+
<Tree
96+
checkable
97+
showLine
98+
defaultExpandAll
99+
multiple={false}
100+
rootStyle={{
101+
background: 'black',
102+
color: 'white',
103+
paddingTop: '18px',
104+
paddingBottom: '18px',
105+
}}
106+
selectedKeys={[]}
107+
onSelect={onToLocalSelect}
108+
onCheck={onToLocalCheck}
109+
treeData={treeToLocalData}
110+
/>
111+
</> :
112+
null
113+
}
114+
115+
<div className={styles['subHeader']}>Local Changed Files</div>
116+
<Tree
117+
checkable
118+
showLine
119+
defaultExpandAll
120+
multiple={false}
121+
rootStyle={{
122+
background: 'black',
123+
color: 'white',
124+
paddingTop: '18px',
125+
paddingBottom: '18px',
126+
}}
127+
selectedKeys={[]}
128+
onSelect={onToRemoteSelect}
129+
onCheck={onToRemoteCheck}
130+
treeData={treeToRemoteData}
131+
/>
132+
<div className={styles['actions']}>
133+
<Button onClick={startSync}>Sync</Button>
134+
</div>
135+
</>
136+
);
137+
}

0 commit comments

Comments
 (0)