0.10.1: Archify Sync in the status bar, server address as a setting

In workspaces with Archify files, Archify Sync sits in the status bar and
opens a menu: sign in, share, join, server address, settings. The new
archify.sync.server setting (empty by default) is used without asking,
and in workspace settings gives a project one server.
archify.sync.statusBar hides the entry.
This commit is contained in:
root-at-skic committed 2026-11-06 23:21:56 +02:00
1 parent d02c331155
commit 9ccd67b2dd
7 files changed
+135 -19

No files matched your search

+80 -10
View File
@@ -23,6 +23,7 @@ export const SYNC_COMMANDS = [
'archify.sync.versions',
'archify.sync.stop',
'archify.sync.menu',
'archify.sync.setServer',
] as const;
const SKIP = '{**/node_modules/**,**/.git/**}';
@@ -38,6 +39,10 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
private readonly versionCache = new Map<string, { at: number; versions: Promise<Version[]> }>();
private readonly editorCursors: EditorCursors;
private cursorTimer: ReturnType<typeof setTimeout> | undefined;
/** Servers with a session on this machine, for the status bar before any model is shared. */
private signedIn: string[] = [];
/** Whether the workspace has Archify files; elsewhere the entry stays hidden until a model is shared. */
private archifyHere = false;
constructor(
private readonly context: vscode.ExtensionContext,
@@ -62,8 +67,9 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
vscode.workspace.registerTextDocumentContentProvider(VERSION_SCHEME, this.content),
this.auth.onDidChangeSessions(() => {
for (const space of this.spaces.values()) space.reconnect();
this.render();
void this.refreshSignedIn();
}),
vscode.workspace.onDidChangeConfiguration((e) => e.affectsConfiguration('archify.sync') && this.render()),
);
this.editorCursors = new EditorCursors({ cursors: (file) => this.cursors(file), setCursor: (file, selection) => this.setCursorIn(file, selection) });
this.disposables.push(this.editorCursors);
@@ -74,6 +80,12 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
async start(): Promise<void> {
const found = await vscode.workspace.findFiles(`**/${LINK_FILE}`, SKIP, 50);
await Promise.all(found.map((uri) => this.open(uri, 'open')));
this.archifyHere = (await vscode.workspace.findFiles('**/*.{archify,archify-registry,archify-decision}', SKIP, 1)).length > 0;
await this.refreshSignedIn();
}
private async refreshSignedIn(): Promise<void> {
this.signedIn = await this.auth.servers();
this.render();
}
@@ -127,8 +139,20 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
private render(): void {
const spaces = [...this.spaces.values()];
if (!spaces.length) {
this.status.hide();
void vscode.commands.executeCommand('setContext', 'archify.sync.active', false);
// Nothing shared here yet: the way in, as before the sync bundle loaded.
if (!this.archifyHere || !vscode.workspace.getConfiguration('archify.sync').get<boolean>('statusBar', true)) {
this.status.hide();
return;
}
const hosts = this.signedIn.map((server) => new URL(server).host);
this.status.text = hosts.length ? `$(cloud) Archify Sync · ${hosts.join(', ')}` : '$(cloud) Archify Sync';
this.status.tooltip = hosts.length
? `Signed in to ${hosts.join(', ')}. Click to share a model, join one or sign out.`
: 'Archify Sync (optional): sign in, share a model with others or join one. Set the server under Settings › Archify › Sync.';
this.status.backgroundColor = undefined;
this.status.show();
this.bridge?.changed();
return;
}
void vscode.commands.executeCommand('setContext', 'archify.sync.active', true);
@@ -300,6 +324,9 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
case 'archify.sync.menu':
await this.menu();
break;
case 'archify.sync.setServer':
await this.setServer();
break;
}
} catch (error) {
this.report(error);
@@ -319,8 +346,13 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
else if (uri.path === '/join') await this.run('archify.sync.join', uri.toString(true));
}
/** The server from the archify.sync.server setting, or asked for (and remembered). */
private async askServer(): Promise<string | undefined> {
const last = this.context.globalState.get<string>('archifySync.lastServer');
return configuredServer() ?? this.promptServer();
}
private async promptServer(): Promise<string | undefined> {
const last = configuredServer() ?? this.context.globalState.get<string>('archifySync.lastServer');
const value = await vscode.window.showInputBox({
title: 'Archify | Sync server',
prompt: 'The address of the Archify Sync server',
@@ -350,6 +382,16 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
return true;
}
/** Writes archify.sync.server: in the workspace when the workspace sets it, otherwise for the user. */
private async setServer(): Promise<void> {
const server = await this.promptServer();
if (!server) return;
const config = vscode.workspace.getConfiguration('archify.sync');
const inWorkspace = config.inspect<string>('server')?.workspaceValue !== undefined;
await config.update('server', server, inWorkspace ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(`Archify Sync: server set to ${new URL(server).host}${inWorkspace ? ' for this workspace' : ''}.`);
}
private async signInCommand(): Promise<void> {
const server = await this.askServer();
if (!server || !(await this.checkServer(server))) return;
@@ -531,7 +573,10 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
private async menu(): Promise<void> {
const spaces = [...this.spaces.values()];
const signedOut = spaces.find((s) => s.state === 'signed-out');
const items: (vscode.QuickPickItem & { run: () => Promise<void> })[] = [];
const servers = await this.auth.servers();
const configured = configuredServer();
type Item = vscode.QuickPickItem & { run?: () => Promise<void> };
const items: Item[] = [];
if (signedOut) {
items.push({
label: `$(account) Sign in to ${new URL(signedOut.link.server).host}`,
@@ -539,16 +584,35 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
if (await this.checkServer(signedOut.link.server)) await this.auth.signIn(signedOut.link.server);
},
});
} else if (!servers.length) {
items.push({
label: '$(account) Sign in…',
description: configured ? new URL(configured).host : 'asks for the server address',
run: () => this.signInCommand(),
});
}
items.push(
{ label: '$(person-add) Copy invite link…', run: () => this.invite(undefined) },
{ label: '$(tag) Publish version…', run: () => this.publish(undefined) },
{ label: '$(history) Versions…', run: () => this.run('archify.sync.versions') },
{ label: '$(output) Show log', run: async () => this.log.show() },
{ label: '$(debug-disconnect) Stop syncing a model…', run: () => this.stop(undefined) },
{ label: '$(live-share) Share a model…', run: () => this.share(undefined) },
{ label: '$(link) Join a shared model…', description: 'with an invite link', run: () => this.join(undefined) },
);
if (spaces.length) {
items.push(
{ label: 'Shared models', kind: vscode.QuickPickItemKind.Separator },
{ label: '$(person-add) Copy invite link…', run: () => this.invite(undefined) },
{ label: '$(tag) Publish version…', run: () => this.publish(undefined) },
{ label: '$(history) Versions…', run: () => this.run('archify.sync.versions') },
{ label: '$(debug-disconnect) Stop syncing a model…', run: () => this.stop(undefined) },
);
}
items.push(
{ label: 'Settings', kind: vscode.QuickPickItemKind.Separator },
{ label: '$(server) Server address…', description: configured ? new URL(configured).host : 'not set', run: () => this.setServer() },
{ label: '$(settings-gear) Open Sync settings', run: async () => void vscode.commands.executeCommand('workbench.action.openSettings', 'archify.sync') },
{ label: '$(output) Show log', run: async () => this.log.show() },
);
if (servers.length) items.push({ label: '$(sign-out) Sign out…', description: servers.map((s) => new URL(s).host).join(', '), run: () => this.signOut() });
const pick = await vscode.window.showQuickPick(items, { title: 'Archify Sync' });
await pick?.run();
await pick?.run?.();
}
dispose(): void {
@@ -560,6 +624,12 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
}
}
/** archify.sync.server when it holds a usable address. */
function configuredServer(): string | undefined {
const value = vscode.workspace.getConfiguration('archify.sync').get<string>('server', '').trim();
return value ? checkServerUrl(value).url : undefined;
}
/** The file behind the active tab when it is a custom editor (the Archify pages). */
function activeTabUri(): vscode.Uri | undefined {
const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input;