0.10.4: a real Sync button on every Archify page
A labelled Sync button sits top right on every Archify page, also in Source, and opens the Sync menu. On a shared model it turns green and reads Shared. The title-bar icon and the pre-share status bar entry are gone, with their settings.
This commit is contained in:
1 parent
7d2d55543e
commit
90c7be06de
10 files changed
+88
-58
No files matched your search
+1
-15
@@ -41,8 +41,6 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
||||
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,
|
||||
@@ -80,7 +78,6 @@ 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();
|
||||
}
|
||||
|
||||
@@ -139,19 +136,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -27,26 +27,8 @@ const COMMANDS = [
|
||||
*/
|
||||
export function registerSync(context: vscode.ExtensionContext, models: ModelService): vscode.Disposable[] {
|
||||
let feature: Promise<SyncFeature> | undefined;
|
||||
// Until the sync bundle loads, this entry is the way in; then the bundle's own one takes over.
|
||||
const entry = vscode.window.createStatusBarItem('archify.sync', vscode.StatusBarAlignment.Left, 50);
|
||||
entry.name = 'Archify Sync';
|
||||
entry.text = '$(cloud) Archify Sync';
|
||||
entry.tooltip = 'Archify Sync (optional): sign in, share a model with others or join one. Set the server under Settings › Archify › Sync.';
|
||||
entry.command = 'archify.sync.menu';
|
||||
// Only where there are Archify files: the extension also starts for JSON and HTML.
|
||||
let archifyHere = false;
|
||||
const showEntry = () => {
|
||||
const wanted = archifyHere && !feature && vscode.workspace.getConfiguration('archify.sync').get<boolean>('statusBar', true);
|
||||
if (wanted) entry.show();
|
||||
else entry.hide();
|
||||
};
|
||||
void Promise.resolve(vscode.workspace.findFiles('**/*.{archify,archify-registry,archify-decision}', '{**/node_modules/**,**/.git/**}', 1)).then((found) => {
|
||||
archifyHere = found.length > 0;
|
||||
showEntry();
|
||||
});
|
||||
const load = (): Promise<SyncFeature> =>
|
||||
(feature ??= (async () => {
|
||||
showEntry();
|
||||
// A runtime path, so esbuild leaves the sync bundle out of this one.
|
||||
const bundle = path.join(__dirname, 'sync.js');
|
||||
const mod = require(bundle) as typeof import('./index');
|
||||
@@ -63,8 +45,6 @@ export function registerSync(context: vscode.ExtensionContext, models: ModelServ
|
||||
})();
|
||||
|
||||
return [
|
||||
entry,
|
||||
vscode.workspace.onDidChangeConfiguration((e) => e.affectsConfiguration('archify.sync.statusBar') && showEntry()),
|
||||
liveBridge,
|
||||
...COMMANDS.map((id) =>
|
||||
vscode.commands.registerCommand(id, async (...args: unknown[]) => (await load()).run(id, ...args)),
|
||||
|
||||
@@ -17,6 +17,8 @@ export type WebviewMessage =
|
||||
| { type: 'showModel' }
|
||||
| { type: 'liveReady' }
|
||||
| { type: 'liveAction'; action: LiveAction }
|
||||
| { type: 'syncMenu' }
|
||||
| { type: 'syncReady' }
|
||||
| { type: 'error'; message: string };
|
||||
|
||||
/** Column for opening files next to a webview without covering it. */
|
||||
@@ -66,6 +68,28 @@ async function openSource(
|
||||
vscode.window.showWarningMessage(`Archify: source file "${message.path}" was not found in the workspace.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the file's model is shared: an .archify-sync file next to its
|
||||
* main.archify. Read from disk, so the sync bundle stays unloaded.
|
||||
*/
|
||||
async function isShared(file: vscode.Uri): Promise<boolean> {
|
||||
const folder = vscode.workspace.getWorkspaceFolder(file);
|
||||
let dir = vscode.Uri.joinPath(file, '..');
|
||||
for (let depth = 0; depth < 16; depth++) {
|
||||
if (await exists(vscode.Uri.joinPath(dir, '.archify-sync'))) return true;
|
||||
if (await exists(vscode.Uri.joinPath(dir, 'main.archify'))) return false;
|
||||
const parent = vscode.Uri.joinPath(dir, '..');
|
||||
if (!folder || dir.path === folder.uri.path || parent.path === dir.path) return false;
|
||||
dir = parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function postSyncState(file: vscode.Uri, panel: vscode.WebviewPanel): Promise<void> {
|
||||
const shared = file.scheme === 'file' && (await isShared(file));
|
||||
await Promise.resolve(panel.webview.postMessage({ type: 'syncState', shared })).catch(() => undefined);
|
||||
}
|
||||
|
||||
export async function handleWebviewMessage(
|
||||
message: WebviewMessage,
|
||||
context: vscode.Uri,
|
||||
@@ -99,6 +123,14 @@ export async function handleWebviewMessage(
|
||||
case 'error':
|
||||
vscode.window.showErrorMessage(`Archify: ${message.message}`);
|
||||
break;
|
||||
case 'syncReady':
|
||||
await postSyncState(context, panel);
|
||||
break;
|
||||
case 'syncMenu':
|
||||
await vscode.commands.executeCommand('archify.sync.menu');
|
||||
// Share or Stop may have changed it.
|
||||
await postSyncState(context, panel);
|
||||
break;
|
||||
case 'hash':
|
||||
case 'openDiagram':
|
||||
case 'showGraph':
|
||||
|
||||
@@ -887,6 +887,50 @@ function bridgeScript(options: PageOptions): string {
|
||||
onReady(renderDecisionsRail);
|
||||
onReady(describeElements);
|
||||
onReady(renderModeSwitch);
|
||||
|
||||
// Archify Sync: one labelled button, top right, on every page of an Archify
|
||||
// file (diagram, model, graph, registry, decision). It opens the Sync menu.
|
||||
function renderSyncButton() {
|
||||
if (!(config.source || config.model || config.registry || config.decision || config.graph)) return;
|
||||
if (document.getElementById('archify-sync-button')) return;
|
||||
var style = document.createElement('style');
|
||||
style.textContent = SYNC_BUTTON_STYLE;
|
||||
document.head.appendChild(style);
|
||||
var button = document.createElement('button');
|
||||
button.id = 'archify-sync-button';
|
||||
button.type = 'button';
|
||||
button.title = 'Archify Sync: sign in, share this model with others, or join one';
|
||||
button.addEventListener('click', function (event) { event.stopPropagation(); post({ type: 'syncMenu' }); });
|
||||
document.body.appendChild(button);
|
||||
showSyncState(false);
|
||||
post({ type: 'syncReady' });
|
||||
}
|
||||
// Not shared: a cloud, "Sync". Shared (the model has an .archify-sync file): broadcast waves, "Shared".
|
||||
var SYNC_ICONS = {
|
||||
off: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path fill="currentColor" d="M12.5 13H4.25a3.25 3.25 0 0 1-.6-6.44 4.5 4.5 0 0 1 8.7-.81A3.6 3.6 0 0 1 12.5 13Zm-8.25-1h8.25a2.6 2.6 0 0 0 0-5.2h-.5l-.12-.39a3.5 3.5 0 0 0-6.82.66l-.04.4-.4.05A2.25 2.25 0 0 0 4.25 12Z"/></svg>',
|
||||
on: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="8" cy="8" r="1.75" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M5.2 5.2a4 4 0 0 0 0 5.6M10.8 5.2a4 4 0 0 1 0 5.6M3.1 3.1a7 7 0 0 0 0 9.8M12.9 3.1a7 7 0 0 1 0 9.8"/></svg>',
|
||||
};
|
||||
function showSyncState(shared) {
|
||||
var button = document.getElementById('archify-sync-button');
|
||||
if (!button) return;
|
||||
button.classList.toggle('is-shared', !!shared);
|
||||
button.innerHTML = (shared ? SYNC_ICONS.on : SYNC_ICONS.off) + '<span>' + (shared ? 'Shared' : 'Sync') + '</span>';
|
||||
button.title = shared
|
||||
? 'This model is shared with Archify Sync: invite people, publish a version, see versions'
|
||||
: 'Archify Sync: sign in, share this model with others, or join one';
|
||||
}
|
||||
window.addEventListener('message', function (event) {
|
||||
var message = event.data || {};
|
||||
if (message.type === 'syncState') showSyncState(message.shared);
|
||||
});
|
||||
var SYNC_BUTTON_STYLE = '#archify-sync-button { position: fixed; top: 12px; right: 18px; z-index: 1001; display: inline-flex; align-items: center; gap: 6px;'
|
||||
+ ' padding: 6px 12px; border-radius: 8px; cursor: pointer; font: 600 13px/1 var(--vscode-font-family, system-ui, sans-serif);'
|
||||
+ ' color: var(--vscode-button-foreground, #fff); background: var(--vscode-button-background, #0e639c); border: 1px solid var(--vscode-button-border, transparent); }'
|
||||
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb); }'
|
||||
+ ' #archify-sync-button.is-shared { color: #fff; background: #1f8f5f; }'
|
||||
+ ' #archify-sync-button.is-shared:hover { background: #23a36c; }'
|
||||
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4); outline-offset: 2px; }';
|
||||
onReady(renderSyncButton);
|
||||
if (config.source) {
|
||||
// The host answers with the problem count for the Source badge.
|
||||
post({ type: 'sourceReady' });
|
||||
|
||||
@@ -20,7 +20,7 @@ type HostMessage =
|
||||
|
||||
const STYLE = `
|
||||
html.archify-source-on .container > :not(.header):not(#archify-source),
|
||||
html.archify-source-on body > :not(.toolbar):not(.container):not(.archify-model-head):not(.archify-model-switch-row):not(#archify-source):not(script):not(style):not(.archify-modal-backdrop),
|
||||
html.archify-source-on body > :not(.toolbar):not(.container):not(.archify-model-head):not(.archify-model-switch-row):not(#archify-source):not(#archify-sync-button):not(script):not(style):not(.archify-modal-backdrop),
|
||||
html.archify-source-on #btn-3d,
|
||||
html.archify-source-on .archify-vscode-notice { display: none !important; }
|
||||
html:not(.archify-source-on) #archify-source { display: none !important; }
|
||||
|
||||
Reference in new issue
Block a user