Refine device and connection settings UI

This commit is contained in:
ichaly 2026-06-04 23:24:37 +08:00
parent 0440e48a2a
commit 4408b1f981
3 changed files with 107 additions and 60 deletions

View file

@ -1,4 +1,4 @@
import { App, Notice, Plugin, PluginSettingTab, requestUrl, TFile, TFolder } from "obsidian";
import { App, Notice, Platform, Plugin, PluginSettingTab, requestUrl, TFile, TFolder } from "obsidian";
import { createApp, reactive, type App as VueApp } from "vue";
import { createRandomId, createVaultId, normalizeKey } from "./core/ids";
import SettingsApp from "./settings/SettingsApp.vue";
@ -6,6 +6,8 @@ import "./styles.scss";
import { syncOnce, type LocalSyncState, type VaultIO } from "./sync/engine";
import { S3ObjectStore } from "./store/s3";
declare const require: ((id: string) => unknown) | undefined;
interface ObsyncSettings {
endpoint: string;
bucket: string;
@ -160,14 +162,14 @@ export default class ObsyncPlugin extends Plugin {
this.settings.vaultKey = normalizeKey(this.app.vault.getName());
}
if (!this.settings.deviceName) {
this.settings.deviceName = "This device";
}
if (!this.settings.deviceId) {
this.settings.deviceId = createRandomId("dev");
}
if (!this.settings.deviceName || this.settings.deviceName === "This device") {
this.settings.deviceName = getCurrentDeviceName(this.settings.deviceId);
}
if (!this.settings.syncState) {
this.settings.syncState = { files: {} };
}
@ -215,6 +217,38 @@ export default class ObsyncPlugin extends Plugin {
}
}
function getCurrentDeviceName(deviceId: string): string {
try {
if (typeof require !== "function") {
return getPlatformDeviceName(deviceId);
}
const os = require("node:os") as { hostname?: () => string };
return os.hostname?.().trim() || getPlatformDeviceName(deviceId);
} catch {
return getPlatformDeviceName(deviceId);
}
}
function getPlatformDeviceName(deviceId: string): string {
const suffix = getDeviceNameSuffix(deviceId);
if (Platform.isIosApp) {
return `${Platform.isTablet ? "iPad" : "iPhone"} ${suffix}`;
}
if (Platform.isAndroidApp) {
return `${Platform.isTablet ? "Android Tablet" : "Android Phone"} ${suffix}`;
}
return `Desktop ${suffix}`;
}
function getDeviceNameSuffix(deviceId: string): string {
const compactId = deviceId.replace(/^dev_/, "");
return compactId.slice(-4) || "0000";
}
function parseConnectionConfig(configText: string): ConnectionConfig {
let parsed: unknown;
@ -374,16 +408,17 @@ class ObsyncSettingTab extends PluginSettingTab {
await this.plugin.updateSettings(update);
Object.assign(connectionConfig, this.plugin.getConnectionConfig());
},
onCopyVaultId: async () => {
await navigator.clipboard.writeText(this.plugin.settings.vaultId);
new Notice("Vault ID 已复制。");
onCopyDeviceId: async () => {
await navigator.clipboard.writeText(this.plugin.settings.deviceId);
new Notice("设备 ID 已复制。");
},
onCopyConnectionConfig: async () => {
await navigator.clipboard.writeText(JSON.stringify(this.plugin.getConnectionConfig(), null, 2));
new Notice("连接配置已复制。");
},
onImportConnectionConfig: async (configText: string) => {
onPasteConnectionConfig: async () => {
try {
const configText = await navigator.clipboard.readText();
await this.plugin.importConnectionConfig(configText);
Object.assign(settings, this.plugin.settings);
Object.assign(connectionConfig, this.plugin.getConnectionConfig());

View file

@ -25,14 +25,13 @@ const props = defineProps<{
const emit = defineEmits<{
update: [update: Partial<ObsyncSettings>];
copyVaultId: [];
copyDeviceId: [];
copyConnectionConfig: [];
importConnectionConfig: [configText: string];
pasteConnectionConfig: [];
}>();
const showSecretAccessKey = ref(false);
const secretAccessKeyButton = ref<HTMLButtonElement | null>(null);
const importConfigText = ref("");
watchEffect(() => {
const button = secretAccessKeyButton.value;
@ -186,7 +185,6 @@ watchEffect(() => {
</div>
<div class="obsync-readonly">
<code>{{ props.settings.vaultId }}</code>
<button type="button" @click="emit('copyVaultId')">复制</button>
</div>
</div>
</section>
@ -194,7 +192,12 @@ watchEffect(() => {
<section class="obsync-card">
<div class="obsync-row">
<div class="obsync-row-copy">
<h3>设备名称</h3>
<div class="obsync-row-title">
<h3>设备名称</h3>
<button type="button" class="obsync-link-button" @click="emit('copyDeviceId')">
复制设备 ID
</button>
</div>
<p>用于冲突文件名和界面展示</p>
</div>
<div class="obsync-control">
@ -205,16 +208,6 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<h3>设备 ID</h3>
<p>首次启用插件时随机生成</p>
</div>
<div class="obsync-readonly">
<code>{{ props.settings.deviceId }}</code>
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<h3>自动同步</h3>
@ -238,24 +231,17 @@ watchEffect(() => {
<p>复制到另一台设备导入这里不包含 Access Key Secret</p>
</div>
<pre>{{ JSON.stringify(props.connectionConfig, null, 2) }}</pre>
<pre class="obsync-config-display">{{ JSON.stringify(props.connectionConfig, null, 2) }}</pre>
<button type="button" class="obsync-primary" @click="emit('copyConnectionConfig')">
复制连接配置
</button>
<div class="obsync-action-row">
<button type="button" class="obsync-primary" @click="emit('copyConnectionConfig')">
复制连接配置
</button>
<textarea
v-model="importConfigText"
placeholder="粘贴另一台设备复制的连接配置 JSON"
></textarea>
<button
type="button"
class="obsync-secondary"
@click="emit('importConnectionConfig', importConfigText)"
>
导入连接配置
</button>
<button type="button" class="obsync-secondary" @click="emit('pasteConnectionConfig')">
粘贴并导入
</button>
</div>
</div>
</section>
</main>

View file

@ -44,12 +44,20 @@
}
.obsync-row {
@apply flex items-center justify-between gap-5 px-5 py-4;
border-bottom: 1px solid var(--background-modifier-border);
@apply relative flex items-center justify-between gap-5 px-5 py-4;
}
.obsync-row:last-child {
@apply border-b-0;
.obsync-row::after {
content: "";
position: absolute;
inset-inline: 1.25rem;
bottom: 0;
height: 1px;
background: var(--background-modifier-border);
}
.obsync-row:last-child::after {
display: none;
}
.obsync-row-copy {
@ -73,6 +81,23 @@
}
}
.obsync-row-title {
@apply flex items-center justify-between gap-3;
}
.obsync-link-button {
@apply shrink-0 cursor-pointer rounded px-2 py-1 text-xs font-semibold leading-4 transition;
background: transparent;
border: 0;
box-shadow: none;
color: var(--text-muted);
}
.obsync-link-button:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.obsync-control {
@apply w-60 shrink-0;
}
@ -157,7 +182,6 @@
color: var(--text-normal);
}
.obsync-readonly button,
.obsync-primary {
@apply shrink-0 cursor-pointer rounded-md border-0 px-3 py-2 text-sm font-semibold leading-5 transition;
background: var(--interactive-accent);
@ -171,13 +195,11 @@
color: var(--text-normal);
}
.obsync-readonly button:hover,
.obsync-primary:hover,
.obsync-secondary:hover {
@apply opacity-90;
}
.obsync-readonly button:active,
.obsync-primary:active,
.obsync-secondary:active {
@apply translate-y-px;
@ -218,28 +240,25 @@
@apply mb-3;
}
.obsync-row-block pre {
@apply mb-3 max-h-60 overflow-auto rounded-md p-3 text-xs leading-5;
.obsync-config-display {
@apply min-h-48 rounded-md p-3 font-mono text-xs leading-5;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
color: var(--text-normal);
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.obsync-row-block textarea {
@apply mt-3 h-28 w-full resize-y rounded-md p-3 text-xs leading-5 outline-none transition;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
color: var(--text-normal);
.obsync-action-row {
@apply mt-3 flex gap-2;
}
.obsync-row-block textarea:focus {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px var(--background-modifier-border-focus);
.obsync-action-row button {
@apply flex-1;
}
.obsync-row-block textarea::placeholder {
color: var(--text-faint);
.obsync-action-row .obsync-secondary {
@apply mt-0;
}
@container (max-width: 640px) {
@ -255,10 +274,18 @@
@apply block px-4 py-4;
}
.obsync-row::after {
inset-inline: 1rem;
}
.obsync-row-copy {
@apply mb-2;
}
.obsync-row-title {
@apply items-start;
}
.obsync-control,
.obsync-control-wide,
.obsync-readonly {
@ -273,7 +300,6 @@
@apply text-left;
}
.obsync-readonly button,
.obsync-primary,
.obsync-secondary {
@apply w-full;