mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-05 15:21:54 +00:00
### Fixed
- No unexpected error (about a replicator) during early stage of initialisation. ### New features - Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes. - We can switch between multiple Remote Databases in the settings dialogue.
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
DEFAULT_SETTINGS,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "../../../lib/src/common/types.ts";
|
||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||
@@ -21,6 +22,13 @@ import {
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types.ts";
|
||||
import { SetupManager, UserMode } from "../SetupManager.ts";
|
||||
import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts";
|
||||
import { activateRemoteConfiguration } from "../../../lib/src/serviceFeatures/remoteConfig.ts";
|
||||
import { ConnectionStringParser } from "../../../lib/src/common/ConnectionString.ts";
|
||||
import type { RemoteConfiguration } from "../../../lib/src/common/models/setting.type.ts";
|
||||
import SetupRemote from "../SetupWizard/dialogs/SetupRemote.svelte";
|
||||
import SetupRemoteCouchDB from "../SetupWizard/dialogs/SetupRemoteCouchDB.svelte";
|
||||
import SetupRemoteBucket from "../SetupWizard/dialogs/SetupRemoteBucket.svelte";
|
||||
import SetupRemoteP2P from "../SetupWizard/dialogs/SetupRemoteP2P.svelte";
|
||||
|
||||
function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings {
|
||||
const workObj = { ...editingSettings } as ObsidianLiveSyncSettings;
|
||||
@@ -39,17 +47,31 @@ const toggleActiveSyncClass = (el: HTMLElement, isActive: () => boolean) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
function createRemoteConfigurationId(): string {
|
||||
return `remote-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
function cloneRemoteConfigurations(
|
||||
configs: Record<string, RemoteConfiguration> | undefined
|
||||
): Record<string, RemoteConfiguration> {
|
||||
return Object.fromEntries(Object.entries(configs || {}).map(([id, config]) => [id, { ...config }]));
|
||||
}
|
||||
|
||||
function serializeRemoteConfiguration(settings: ObsidianLiveSyncSettings): string {
|
||||
if (settings.remoteType === REMOTE_MINIO) {
|
||||
return ConnectionStringParser.serialize({ type: "s3", settings });
|
||||
}
|
||||
if (settings.remoteType === REMOTE_P2P) {
|
||||
return ConnectionStringParser.serialize({ type: "p2p", settings });
|
||||
}
|
||||
return ConnectionStringParser.serialize({ type: "couchdb", settings });
|
||||
}
|
||||
|
||||
export function paneRemoteConfig(
|
||||
this: ObsidianLiveSyncSettingTab,
|
||||
paneEl: HTMLElement,
|
||||
{ addPanel, addPane }: PageFunctions
|
||||
): void {
|
||||
const remoteNameMap = {
|
||||
[REMOTE_COUCHDB]: $msg("obsidianLiveSyncSettingTab.optionCouchDB"),
|
||||
[REMOTE_MINIO]: $msg("obsidianLiveSyncSettingTab.optionMinioS3R2"),
|
||||
[REMOTE_P2P]: "Only Peer-to-Peer",
|
||||
} as const;
|
||||
|
||||
{
|
||||
/* E2EE */
|
||||
const E2EEInitialProps = {
|
||||
@@ -91,24 +113,268 @@ export function paneRemoteConfig(
|
||||
});
|
||||
}
|
||||
{
|
||||
// TODO: very WIP. need to refactor the UI.
|
||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleRemoteServer"), () => {}).then((paneEl) => {
|
||||
const setting = new Setting(paneEl).setName($msg("Active Remote Configuration"));
|
||||
const actions = new Setting(paneEl).setName("Remote Databases");
|
||||
// actions.addButton((button) =>
|
||||
// button
|
||||
// .setButtonText("Change Remote and Setup")
|
||||
// .setCta()
|
||||
// .onClick(async () => {
|
||||
// const setupManager = this.core.getModule(SetupManager);
|
||||
// const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
// await setupManager.onSelectServer(originalSettings, UserMode.Update);
|
||||
// })
|
||||
// );
|
||||
|
||||
const el = setting.controlEl.createDiv({});
|
||||
el.setText(`${remoteNameMap[this.editingSettings.remoteType] || " - "}`);
|
||||
setting.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Change Remote and Setup")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.core.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onSelectServer(originalSettings, UserMode.Update);
|
||||
})
|
||||
// Connection List
|
||||
const listContainer = paneEl.createDiv({ cls: "sls-remote-list" });
|
||||
const syncRemoteConfigurationBuffers = () => {
|
||||
const currentConfigs = cloneRemoteConfigurations(this.core.settings.remoteConfigurations);
|
||||
this.editingSettings.remoteConfigurations = currentConfigs;
|
||||
this.editingSettings.activeConfigurationId = this.core.settings.activeConfigurationId;
|
||||
if (this.initialSettings) {
|
||||
this.initialSettings.remoteConfigurations = cloneRemoteConfigurations(currentConfigs);
|
||||
this.initialSettings.activeConfigurationId = this.core.settings.activeConfigurationId;
|
||||
}
|
||||
};
|
||||
const persistRemoteConfigurations = async (synchroniseActiveRemote: boolean = false) => {
|
||||
await this.services.setting.updateSettings((currentSettings) => {
|
||||
currentSettings.remoteConfigurations = cloneRemoteConfigurations(
|
||||
this.editingSettings.remoteConfigurations
|
||||
);
|
||||
currentSettings.activeConfigurationId = this.editingSettings.activeConfigurationId;
|
||||
if (synchroniseActiveRemote && currentSettings.activeConfigurationId) {
|
||||
const activated = activateRemoteConfiguration(
|
||||
currentSettings,
|
||||
currentSettings.activeConfigurationId
|
||||
);
|
||||
if (activated) {
|
||||
return activated;
|
||||
}
|
||||
}
|
||||
return currentSettings;
|
||||
}, true);
|
||||
|
||||
if (synchroniseActiveRemote) {
|
||||
await this.saveAllDirtySettings();
|
||||
}
|
||||
|
||||
syncRemoteConfigurationBuffers();
|
||||
this.requestUpdate();
|
||||
};
|
||||
const runRemoteSetup = async (
|
||||
baseSettings: ObsidianLiveSyncSettings,
|
||||
remoteType?: typeof REMOTE_COUCHDB | typeof REMOTE_MINIO | typeof REMOTE_P2P
|
||||
): Promise<ObsidianLiveSyncSettings | false> => {
|
||||
const setupManager = this.core.getModule(SetupManager);
|
||||
const dialogManager = setupManager.dialogManager;
|
||||
let targetRemoteType = remoteType;
|
||||
|
||||
if (targetRemoteType === undefined) {
|
||||
const method = await dialogManager.openWithExplicitCancel(SetupRemote);
|
||||
if (method === "cancelled") {
|
||||
return false;
|
||||
}
|
||||
targetRemoteType =
|
||||
method === "bucket" ? REMOTE_MINIO : method === "p2p" ? REMOTE_P2P : REMOTE_COUCHDB;
|
||||
}
|
||||
|
||||
if (targetRemoteType === REMOTE_MINIO) {
|
||||
const bucketConf = await dialogManager.openWithExplicitCancel(SetupRemoteBucket, baseSettings);
|
||||
if (bucketConf === "cancelled" || typeof bucketConf !== "object") {
|
||||
return false;
|
||||
}
|
||||
return { ...baseSettings, ...bucketConf, remoteType: REMOTE_MINIO };
|
||||
}
|
||||
|
||||
if (targetRemoteType === REMOTE_P2P) {
|
||||
const p2pConf = await dialogManager.openWithExplicitCancel(SetupRemoteP2P, baseSettings);
|
||||
if (p2pConf === "cancelled" || typeof p2pConf !== "object") {
|
||||
return false;
|
||||
}
|
||||
return { ...baseSettings, ...p2pConf, remoteType: REMOTE_P2P };
|
||||
}
|
||||
|
||||
const couchConf = await dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, baseSettings);
|
||||
if (couchConf === "cancelled" || typeof couchConf !== "object") {
|
||||
return false;
|
||||
}
|
||||
return { ...baseSettings, ...couchConf, remoteType: REMOTE_COUCHDB };
|
||||
};
|
||||
const createBaseRemoteSettings = (): ObsidianLiveSyncSettings => ({
|
||||
...DEFAULT_SETTINGS,
|
||||
...getSettingsFromEditingSettings(this.editingSettings),
|
||||
});
|
||||
const createNewRemoteSettings = (): ObsidianLiveSyncSettings => ({
|
||||
...DEFAULT_SETTINGS,
|
||||
encrypt: this.editingSettings.encrypt,
|
||||
usePathObfuscation: this.editingSettings.usePathObfuscation,
|
||||
passphrase: this.editingSettings.passphrase,
|
||||
configPassphraseStore: this.editingSettings.configPassphraseStore,
|
||||
});
|
||||
const addRemoteConfiguration = async () => {
|
||||
const name = await this.services.UI.confirm.askString("Remote name", "Display name", "New Remote");
|
||||
if (name === false) {
|
||||
return;
|
||||
}
|
||||
const nextSettings = await runRemoteSetup(createNewRemoteSettings());
|
||||
if (!nextSettings) {
|
||||
return;
|
||||
}
|
||||
const id = createRemoteConfigurationId();
|
||||
const configs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations);
|
||||
configs[id] = {
|
||||
id,
|
||||
name: name.trim() || "New Remote",
|
||||
uri: serializeRemoteConfiguration(nextSettings),
|
||||
isEncrypted: nextSettings.encrypt,
|
||||
};
|
||||
this.editingSettings.remoteConfigurations = configs;
|
||||
if (!this.editingSettings.activeConfigurationId) {
|
||||
this.editingSettings.activeConfigurationId = id;
|
||||
}
|
||||
await persistRemoteConfigurations(this.editingSettings.activeConfigurationId === id);
|
||||
refreshList();
|
||||
};
|
||||
actions.addButton((button) =>
|
||||
button.setButtonText("Add New Connection").onClick(async () => {
|
||||
await addRemoteConfiguration();
|
||||
})
|
||||
);
|
||||
const refreshList = () => {
|
||||
listContainer.empty();
|
||||
const configs = this.editingSettings.remoteConfigurations || {};
|
||||
for (const config of Object.values(configs)) {
|
||||
const row = new Setting(listContainer)
|
||||
.setName(config.name)
|
||||
.setDesc(config.uri.split("@").pop() || ""); // Show host part for privacy
|
||||
|
||||
if (config.id === this.editingSettings.activeConfigurationId) {
|
||||
row.nameEl.addClass("sls-active-remote-name");
|
||||
row.nameEl.appendText(" (Active)");
|
||||
}
|
||||
|
||||
row.addButton((btn) =>
|
||||
btn.setButtonText("Configure").onClick(async () => {
|
||||
const parsed = ConnectionStringParser.parse(config.uri);
|
||||
const workSettings = createBaseRemoteSettings();
|
||||
if (parsed.type === "couchdb") {
|
||||
workSettings.remoteType = REMOTE_COUCHDB;
|
||||
} else if (parsed.type === "s3") {
|
||||
workSettings.remoteType = REMOTE_MINIO;
|
||||
} else {
|
||||
workSettings.remoteType = REMOTE_P2P;
|
||||
}
|
||||
Object.assign(workSettings, parsed.settings);
|
||||
|
||||
const nextSettings = await runRemoteSetup(workSettings, workSettings.remoteType);
|
||||
if (!nextSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations);
|
||||
nextConfigs[config.id] = {
|
||||
...config,
|
||||
uri: serializeRemoteConfiguration(nextSettings),
|
||||
isEncrypted: nextSettings.encrypt,
|
||||
};
|
||||
this.editingSettings.remoteConfigurations = nextConfigs;
|
||||
await persistRemoteConfigurations(config.id === this.editingSettings.activeConfigurationId);
|
||||
refreshList();
|
||||
})
|
||||
);
|
||||
row.addButton((btn) =>
|
||||
btn.setButtonText("Rename").onClick(async () => {
|
||||
const nextName = await this.services.UI.confirm.askString(
|
||||
"Remote name",
|
||||
"Display name",
|
||||
config.name
|
||||
);
|
||||
if (nextName === false) {
|
||||
return;
|
||||
}
|
||||
const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations);
|
||||
nextConfigs[config.id] = {
|
||||
...config,
|
||||
name: nextName.trim() || config.name,
|
||||
};
|
||||
this.editingSettings.remoteConfigurations = nextConfigs;
|
||||
await persistRemoteConfigurations();
|
||||
refreshList();
|
||||
})
|
||||
);
|
||||
row.addButton((btn) =>
|
||||
btn.setButtonText("Duplicate").onClick(async () => {
|
||||
const nextName = await this.services.UI.confirm.askString(
|
||||
"Duplicate remote",
|
||||
"Display name",
|
||||
`${config.name} (Copy)`
|
||||
);
|
||||
if (nextName === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextId = createRemoteConfigurationId();
|
||||
const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations);
|
||||
nextConfigs[nextId] = {
|
||||
...config,
|
||||
id: nextId,
|
||||
name: nextName.trim() || `${config.name} (Copy)`,
|
||||
};
|
||||
this.editingSettings.remoteConfigurations = nextConfigs;
|
||||
await persistRemoteConfigurations();
|
||||
refreshList();
|
||||
})
|
||||
);
|
||||
row.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Delete")
|
||||
.setWarning()
|
||||
.onClick(async () => {
|
||||
const confirmed = await this.services.UI.confirm.askYesNoDialog(
|
||||
`Delete remote configuration '${config.name}'?`,
|
||||
{ title: "Delete Remote Configuration", defaultOption: "No" }
|
||||
);
|
||||
if (confirmed !== "yes") {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextConfigs = cloneRemoteConfigurations(
|
||||
this.editingSettings.remoteConfigurations
|
||||
);
|
||||
delete nextConfigs[config.id];
|
||||
this.editingSettings.remoteConfigurations = nextConfigs;
|
||||
|
||||
let syncActiveRemote = false;
|
||||
if (this.editingSettings.activeConfigurationId === config.id) {
|
||||
const nextActiveId = Object.keys(nextConfigs)[0] || "";
|
||||
this.editingSettings.activeConfigurationId = nextActiveId;
|
||||
syncActiveRemote = nextActiveId !== "";
|
||||
}
|
||||
|
||||
await persistRemoteConfigurations(syncActiveRemote);
|
||||
refreshList();
|
||||
})
|
||||
);
|
||||
|
||||
row.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Activate")
|
||||
.setDisabled(config.id === this.editingSettings.activeConfigurationId)
|
||||
.onClick(async () => {
|
||||
this.editingSettings.activeConfigurationId = config.id;
|
||||
await persistRemoteConfigurations(true);
|
||||
refreshList();
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
refreshList();
|
||||
});
|
||||
}
|
||||
{
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
const initialProps = {
|
||||
info: getCouchDBConfigSummary(this.editingSettings),
|
||||
};
|
||||
@@ -143,7 +409,8 @@ export function paneRemoteConfig(
|
||||
);
|
||||
});
|
||||
}
|
||||
{
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
const initialProps = {
|
||||
info: getBucketConfigSummary(this.editingSettings),
|
||||
};
|
||||
@@ -178,7 +445,8 @@ export function paneRemoteConfig(
|
||||
);
|
||||
});
|
||||
}
|
||||
{
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
const getDevicePeerId = () => this.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || "";
|
||||
const initialProps = {
|
||||
info: getP2PConfigSummary(this.editingSettings, {
|
||||
|
||||
Reference in New Issue
Block a user