Compare commits

...
Author SHA1 Message Date
root-at-skic 6153c7f145 0.10.7: Model | Graph | Source styled like 2D | 3D
Archify gives every toolbar button a 2.75rem minimum height and a painted
::before; the switch's segments are buttons, so they grew to full height.
Segments now keep 1.75rem inside a 2.75rem switch, like 2D | 3D. The high
contrast rule no longer turns a shared model's Sync button blue.
2026-11-08 20:35:48 +02:00
root-at-skic 885977c4aa Merge pull request '0.10.6: Sync sign-in comes back through 127.0.0.1' (#37) from fix/sync-signin-loopback into main 2026-11-08 10:06:32 -08:00
root-at-skic 95c6e60199 0.10.6: Sync sign-in comes back through 127.0.0.1
Browsers open a vscode:// link only after a click on the page. With a live
identity provider session the sign-in ran without one, so the editor kept
waiting. On the desktop the browser now returns to a one-off listener on
127.0.0.1; remote and web editors keep the URI handler.
2026-11-08 20:05:11 +02:00
root-at-skic b54304fef8 Merge pull request '0.10.5: icon-only Sync button that keeps its size and colour' (#36) from fix/sync-button-icon into main 2026-11-08 07:59:32 -08:00
root-at-skic 8407c9a956 0.10.5: icon-only Sync button that keeps its size and colour
On a diagram page it goes last in Archify's toolbar, after Export, instead
of floating over it. Every rule is !important and flex: none, so the page's
button styles, narrow windows and high contrast themes no longer squeeze it
or strip its colour.
2026-11-08 17:45:17 +02:00
root-at-skic c2d33780f9 Merge pull request '0.10.4: a real Sync button on every Archify page' (#35) from feat/sync-page-button into main 2026-11-07 07:07:15 -08:00
root-at-skic 90c7be06de 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.
2026-11-07 01:25:34 +02:00
root-at-skic 7d2d55543e Merge pull request '0.10.3: Sync button in the editor title bar' (#34) from feat/sync-title-button into main 2026-11-06 14:56:31 -08:00
root-at-skic c262bb06a7 0.10.3: Sync button in the editor title bar
Every Archify page and .archify text editor gets a Sync button top right that opens the Sync menu: sign in, share, join, server address. archify.sync.titleButton hides it. The status bar entry stays.
2026-11-07 00:52:07 +02:00
root-at-skic 63f0633daa Merge pull request '0.10.2: leaving Source hides the YAML editor' (#33) from fix/source-pane-hide into main 2026-11-06 14:17:34 -08:00
root-at-skic ef272e4d96 0.10.2: leaving Source hides the YAML editor
Going back from Source to Model, Diagram or View left the editor on the page under the picture when nothing had been edited, since the page was only replaced after a re-render. The pane now hides whenever Source is off.
2026-11-06 23:58:24 +02:00
11 changed files with 167 additions and 52 deletions

No files matched your search

+26
View File
@@ -1,5 +1,31 @@
# Changelog # Changelog
## 0.10.7 — 2026-11-08
- **Model | Graph | Source looks like 2D | 3D.** The selected view is the same inset pill, the same height as the other toolbar controls.
- The Sync button of a shared model is green in high contrast themes too.
## 0.10.6 — 2026-11-08
- **Sync sign-in comes back by itself.** The browser now returns the sign-in to a one-off port on this machine (`127.0.0.1`) instead of a `vscode://` link. Browsers open such links only after a click, so when you were already signed in to the identity provider the editor kept waiting. Remote and web editors still use the link.
## 0.10.5 — 2026-11-08
- **Sync button fixed.** It is now an icon only (a cloud; green with a broadcast icon once the model is shared), the same size on every page and theme. On a diagram it sits in the toolbar after **Export** instead of over it; in narrow windows and high contrast themes it no longer shrinks or loses its colour.
## 0.10.4 — 2026-11-07
- **A real Sync button.** Every Archify page (diagram, model, graph, registry, decision, and in Source) shows a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, set the server address. On a model that is shared it turns green and reads **Shared**.
- Removed the small title-bar icon and the status bar entry shown before anything was shared, with their settings `archify.sync.titleButton` and `archify.sync.statusBar`. A shared model still shows its live state in the status bar.
## 0.10.3 — 2026-11-07
- **Sync button in the title bar.** Every Archify page (and `.archify` text editors) has a **Sync** button top right, next to *Open in Text Editor*: sign in, share a model, join one, set the server address. The status bar entry stays for those who show the status bar. `archify.sync.titleButton: false` hides the button.
## 0.10.2 — 2026-11-07
- Fixed: going back from **Source** to **Model**, **Diagram** or **View** left the YAML editor on the page under the picture when nothing had been edited. It now hides at once.
## 0.10.1 — 2026-11-06 ## 0.10.1 — 2026-11-06
- **Archify Sync is easier to find.** In workspaces with Archify files, **Archify Sync** sits in the status bar. Click it for a menu: sign in, share a model, join one, set the server address, open the settings. It shows the server you are signed in to, and the live state once a model is shared. `archify.sync.statusBar: false` hides it; nothing else changes without a server. - **Archify Sync is easier to find.** In workspaces with Archify files, **Archify Sync** sits in the status bar. Click it for a menu: sign in, share a model, join one, set the server address, open the settings. It shows the server you are signed in to, and the live state once a model is shared. `archify.sync.statusBar: false` hides it; nothing else changes without a server.
+1 -1
View File
@@ -140,7 +140,7 @@ Optional. Without a sync server the extension works exactly as described above a
[Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working. [Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working.
- **Where.** **Archify Sync** in the status bar (in workspaces with Archify files) opens a menu: sign in, share a model, join one, the server address, settings. The same menu is *Archify | Sync…*. Hide the entry with `archify.sync.statusBar: false`. - **Where.** Every Archify page (diagram, model, graph, registry, decision, also in Source) has a Sync button (a cloud) top right; on a diagram it sits after **Export**. It opens the Sync menu: sign in, share the model, join one, the server address, settings. On a shared model it turns green with a broadcast icon. The same menu is *Archify | Sync…* in the Command Palette.
- **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered. - **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered.
- **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log. - **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log.
- **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model. - **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.1", "version": "0.10.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.1", "version": "0.10.7",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.20.4", "@codemirror/autocomplete": "^6.20.4",
+3 -8
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer", "displayName": "Archify Diagram Viewer",
"description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.", "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.",
"version": "0.10.1", "version": "0.10.7",
"publisher": "root-at-skic", "publisher": "root-at-skic",
"license": "MIT", "license": "MIT",
"sponsor": { "sponsor": {
@@ -163,7 +163,7 @@
{ {
"command": "archify.sync.menu", "command": "archify.sync.menu",
"title": "Archify | Sync…", "title": "Archify | Sync…",
"icon": "$(broadcast)" "icon": "$(live-share)"
}, },
{ {
"command": "archify.sync.setServer", "command": "archify.sync.setServer",
@@ -521,12 +521,7 @@
"scope": "window", "scope": "window",
"pattern": "^$|^https?://", "pattern": "^$|^https?://",
"patternErrorMessage": "An address such as https://archify.example.com", "patternErrorMessage": "An address such as https://archify.example.com",
"markdownDescription": "Address of your Archify Sync server, for example `https://archify.example.com`. *Sign in*, *Share Model* and the status bar use it without asking. Set it in the workspace settings so everyone in the project uses the same server. Empty: asked once and remembered. Archify Sync is optional; see the README." "markdownDescription": "Address of your Archify Sync server, for example `https://archify.example.com`. The **Sync** button on Archify pages, *Sign in* and *Share Model* use it without asking. Set it in the workspace settings so everyone in the project uses the same server. Empty: asked once and remembered. Archify Sync is optional; see the README."
},
"archify.sync.statusBar": {
"type": "boolean",
"default": true,
"markdownDescription": "Show **Archify Sync** in the status bar in workspaces with Archify files. Clicking it offers sign in, share, join and the server address. Shared models always show their state there."
} }
} }
}, },
+36 -3
View File
@@ -1,4 +1,5 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import * as http from 'node:http';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SyncApi, SyncApiError } from './api'; import { SyncApi, SyncApiError } from './api';
import { Account, TokenResponse } from './protocol'; import { Account, TokenResponse } from './protocol';
@@ -100,8 +101,13 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
const verifier = crypto.randomBytes(32).toString('base64url'); const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
const state = crypto.randomBytes(16).toString('base64url'); const state = crypto.randomBytes(16).toString('base64url');
const callback = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`)); // On the desktop the browser comes back to a port on this machine: no
const redirectUri = callback.toString(true); // click needed, and it reaches this window. Remote and web editors use
// the extension's URI handler (vscode://…/auth) instead.
const loopback = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Desktop ? await this.listen().catch(() => undefined) : undefined;
const redirectUri = loopback
? `http://127.0.0.1:${loopback.port}/auth`
: (await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`))).toString(true);
const query = new URLSearchParams({ const query = new URLSearchParams({
response_type: 'code', response_type: 'code',
redirect_uri: redirectUri, redirect_uri: redirectUri,
@@ -119,6 +125,7 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
const done = () => { const done = () => {
clearTimeout(timer); clearTimeout(timer);
this.pending.delete(state); this.pending.delete(state);
loopback?.close();
}; };
this.pending.set(state, { this.pending.set(state, {
resolve: (c) => (done(), resolve(c)), resolve: (c) => (done(), resolve(c)),
@@ -137,9 +144,35 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
return session; return session;
} }
/** A one-off HTTP listener on 127.0.0.1 for the browser's way back; closed when the sign-in ends. */
private listen(): Promise<{ port: number; close: () => void }> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
const known = url.pathname === '/auth' && this.pending.has(url.searchParams.get('state') ?? '');
res.writeHead(known ? 200 : 404, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
res.end(
known
? '<!doctype html><meta charset="utf-8"><title>Archify Sync</title><body style="font:15px system-ui;margin:48px auto;max-width:420px"><h2>Signed in</h2><p>You can close this tab and go back to the editor.</p></body>'
: 'Not found',
);
if (known) this.completeSignIn(url.searchParams);
});
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (!address || typeof address === 'string') return reject(new Error('no port'));
resolve({ port: address.port, close: () => server.close() });
});
});
}
/** The browser came back: `…/auth?code=&state=`. */ /** The browser came back: `…/auth?code=&state=`. */
handleAuthUri(uri: vscode.Uri): void { handleAuthUri(uri: vscode.Uri): void {
const query = new URLSearchParams(uri.query); this.completeSignIn(new URLSearchParams(uri.query));
}
private completeSignIn(query: URLSearchParams): void {
const state = query.get('state') ?? ''; const state = query.get('state') ?? '';
const code = query.get('code'); const code = query.get('code');
const pending = this.pending.get(state); const pending = this.pending.get(state);
+1 -15
View File
@@ -41,8 +41,6 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
private cursorTimer: ReturnType<typeof setTimeout> | undefined; private cursorTimer: ReturnType<typeof setTimeout> | undefined;
/** Servers with a session on this machine, for the status bar before any model is shared. */ /** Servers with a session on this machine, for the status bar before any model is shared. */
private signedIn: string[] = []; private signedIn: string[] = [];
/** Whether the workspace has Archify files; elsewhere the entry stays hidden until a model is shared. */
private archifyHere = false;
constructor( constructor(
private readonly context: vscode.ExtensionContext, private readonly context: vscode.ExtensionContext,
@@ -80,7 +78,6 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
async start(): Promise<void> { async start(): Promise<void> {
const found = await vscode.workspace.findFiles(`**/${LINK_FILE}`, SKIP, 50); const found = await vscode.workspace.findFiles(`**/${LINK_FILE}`, SKIP, 50);
await Promise.all(found.map((uri) => this.open(uri, 'open'))); 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(); await this.refreshSignedIn();
} }
@@ -139,19 +136,8 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
private render(): void { private render(): void {
const spaces = [...this.spaces.values()]; const spaces = [...this.spaces.values()];
if (!spaces.length) { if (!spaces.length) {
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(); this.status.hide();
return; void vscode.commands.executeCommand('setContext', 'archify.sync.active', false);
}
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(); this.bridge?.changed();
return; return;
} }
-20
View File
@@ -27,26 +27,8 @@ const COMMANDS = [
*/ */
export function registerSync(context: vscode.ExtensionContext, models: ModelService): vscode.Disposable[] { export function registerSync(context: vscode.ExtensionContext, models: ModelService): vscode.Disposable[] {
let feature: Promise<SyncFeature> | undefined; 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> => const load = (): Promise<SyncFeature> =>
(feature ??= (async () => { (feature ??= (async () => {
showEntry();
// A runtime path, so esbuild leaves the sync bundle out of this one. // A runtime path, so esbuild leaves the sync bundle out of this one.
const bundle = path.join(__dirname, 'sync.js'); const bundle = path.join(__dirname, 'sync.js');
const mod = require(bundle) as typeof import('./index'); const mod = require(bundle) as typeof import('./index');
@@ -63,8 +45,6 @@ export function registerSync(context: vscode.ExtensionContext, models: ModelServ
})(); })();
return [ return [
entry,
vscode.workspace.onDidChangeConfiguration((e) => e.affectsConfiguration('archify.sync.statusBar') && showEntry()),
liveBridge, liveBridge,
...COMMANDS.map((id) => ...COMMANDS.map((id) =>
vscode.commands.registerCommand(id, async (...args: unknown[]) => (await load()).run(id, ...args)), vscode.commands.registerCommand(id, async (...args: unknown[]) => (await load()).run(id, ...args)),
+32
View File
@@ -17,6 +17,8 @@ export type WebviewMessage =
| { type: 'showModel' } | { type: 'showModel' }
| { type: 'liveReady' } | { type: 'liveReady' }
| { type: 'liveAction'; action: LiveAction } | { type: 'liveAction'; action: LiveAction }
| { type: 'syncMenu' }
| { type: 'syncReady' }
| { type: 'error'; message: string }; | { type: 'error'; message: string };
/** Column for opening files next to a webview without covering it. */ /** 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.`); 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( export async function handleWebviewMessage(
message: WebviewMessage, message: WebviewMessage,
context: vscode.Uri, context: vscode.Uri,
@@ -99,6 +123,14 @@ export async function handleWebviewMessage(
case 'error': case 'error':
vscode.window.showErrorMessage(`Archify: ${message.message}`); vscode.window.showErrorMessage(`Archify: ${message.message}`);
break; 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 'hash':
case 'openDiagram': case 'openDiagram':
case 'showGraph': case 'showGraph':
+64 -1
View File
@@ -887,6 +887,66 @@ function bridgeScript(options: PageOptions): string {
onReady(renderDecisionsRail); onReady(renderDecisionsRail);
onReady(describeElements); onReady(describeElements);
onReady(renderModeSwitch); onReady(renderModeSwitch);
// Archify Sync: one icon 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' }); });
// A diagram page has Archify's toolbar at the top right: the button goes
// last in it, after Export. Other pages have nothing there; it floats.
var toolbar = document.querySelector('body > .toolbar');
if (toolbar) {
button.classList.add('in-toolbar');
toolbar.appendChild(button);
} else {
document.body.appendChild(button);
}
showSyncState(false);
post({ type: 'syncReady' });
}
// Not shared: a cloud. Shared (the model has an .archify-sync file): broadcast waves on green.
var SYNC_ICONS = {
off: '<svg viewBox="0 0 16 16" width="16" height="16" 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="16" height="16" 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;
button.title = shared
? 'Archify Sync: this model is shared. Invite people, publish a version, see versions'
: 'Archify Sync: sign in, share this model with others, or join one';
button.setAttribute('aria-label', shared ? 'Archify Sync (shared)' : 'Archify Sync');
}
window.addEventListener('message', function (event) {
var message = event.data || {};
if (message.type === 'syncState') showSyncState(message.shared);
});
// Every rule is !important: the page's own button styles (Archify's toolbar,
// VS Code's high contrast themes) must not resize or recolour it.
var SYNC_BUTTON_STYLE = '#archify-sync-button { all: unset; position: fixed !important; top: 12px !important; right: 28px !important; z-index: 1001 !important;'
+ ' box-sizing: border-box !important; width: 30px !important; height: 30px !important; min-width: 0 !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important;'
+ ' display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 8px !important; cursor: pointer !important;'
+ ' color: #fff !important; background: var(--vscode-button-background, #0e639c) !important; border: 1px solid var(--vscode-contrastBorder, transparent) !important;'
+ ' box-shadow: 0 1px 4px rgba(0,0,0,.35) !important; }'
+ ' #archify-sync-button.in-toolbar { position: relative !important; top: auto !important; right: auto !important; align-self: center !important; flex: none !important; width: 36px !important; height: 36px !important; }'
+ ' body.vscode-high-contrast #archify-sync-button:not(.is-shared) { background: #0e639c !important; }'
+ ' #archify-sync-button svg { display: block !important; width: 16px !important; height: 16px !important; flex: none !important; }'
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb) !important; }'
+ ' #archify-sync-button.is-shared { background: #1f8f5f !important; }'
+ ' #archify-sync-button.is-shared:hover { background: #23a36c !important; }'
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4) !important; outline-offset: 2px !important; }';
onReady(renderSyncButton);
if (config.source) { if (config.source) {
// The host answers with the problem count for the Source badge. // The host answers with the problem count for the Source badge.
post({ type: 'sourceReady' }); post({ type: 'sourceReady' });
@@ -1013,10 +1073,13 @@ const MODEL_STYLE = `
/* The Model | Graph switch: in Archify's toolbar it is the 2D | 3D switch's twin; on the model page it sits top right. */ /* The Model | Graph switch: in Archify's toolbar it is the 2D | 3D switch's twin; on the model page it sits top right. */
const SWITCH_STYLE = ` const SWITCH_STYLE = `
.archify-mode-switch { display: inline-flex; align-items: center; padding: 0 0.25rem; gap: 0; color: var(--text-muted, var(--vscode-descriptionForeground)); position: relative; } .archify-mode-switch { display: inline-flex; align-items: center; padding: 0 0.25rem; gap: 0; color: var(--text-muted, var(--vscode-descriptionForeground)); position: relative; }
.toolbar .archify-mode-switch { height: 2.75rem; box-sizing: border-box; }
.toolbar .archify-mode-switch::before { content: ""; position: absolute; inset: 0.25rem 0; border-radius: 0.6875rem; background: color-mix(in srgb, var(--panel) 78%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 85%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); backdrop-filter: blur(12px); } .toolbar .archify-mode-switch::before { content: ""; position: absolute; inset: 0.25rem 0; border-radius: 0.6875rem; background: color-mix(in srgb, var(--panel) 78%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 85%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); backdrop-filter: blur(12px); }
.toolbar .archify-mode-switch:hover::before { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); } .toolbar .archify-mode-switch:hover::before { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); }
/* Anchored on the id: Archify's .toolbar button rules (one of them with :not(#btn-theme)) would win otherwise. */ /* Anchored on the id: Archify's .toolbar button rules (one of them with :not(#btn-theme)) would win otherwise. */
#archify-mode-switch .archify-mode-seg { position: relative; display: inline-flex; align-items: center; height: 1.75rem; padding: 0 0.7rem; border: 0; border-radius: 0.5rem; background: transparent; color: inherit; font: inherit; font-weight: 550; font-size: 0.8125rem; cursor: pointer; transition: background 0.15s, color 0.15s; } #archify-mode-switch .archify-mode-seg { position: relative; display: inline-flex; align-items: center; height: 1.75rem; min-height: 0; margin: 0; padding: 0 0.7rem; border: 0; border-radius: 0.5rem; background: transparent; color: inherit; font: inherit; font-weight: 550; font-size: 0.8125rem; cursor: pointer; transition: background 0.15s, color 0.15s; }
/* Archify's toolbar buttons are 2.75rem tall with a painted ::before; the segments are not, like 2D | 3D. */
#archify-mode-switch .archify-mode-seg::before, #archify-mode-switch .archify-mode-seg::after { content: none; }
#archify-mode-switch .archify-mode-seg.on[aria-pressed="true"] { color: var(--bg, var(--vscode-editor-background)); background: var(--text, var(--vscode-foreground)); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); } #archify-mode-switch .archify-mode-seg.on[aria-pressed="true"] { color: var(--bg, var(--vscode-editor-background)); background: var(--text, var(--vscode-foreground)); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); }
#archify-mode-switch .archify-mode-seg:hover:not(.on) { color: var(--text, var(--vscode-foreground)); } #archify-mode-switch .archify-mode-seg:hover:not(.on) { color: var(--text, var(--vscode-foreground)); }
#archify-mode-switch .archify-mode-seg:focus-visible { outline: 2px solid var(--frontend-stroke, var(--vscode-focusBorder)); outline-offset: 1px; } #archify-mode-switch .archify-mode-seg:focus-visible { outline: 2px solid var(--frontend-stroke, var(--vscode-focusBorder)); outline-offset: 1px; }
+2 -1
View File
@@ -20,9 +20,10 @@ type HostMessage =
const STYLE = ` const STYLE = `
html.archify-source-on .container > :not(.header):not(#archify-source), 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 #btn-3d,
html.archify-source-on .archify-vscode-notice { display: none !important; } html.archify-source-on .archify-vscode-notice { display: none !important; }
html:not(.archify-source-on) #archify-source { display: none !important; }
.archify-source { display: flex; flex-direction: column; box-sizing: border-box; min-height: 260px; margin: 0.75rem 0 1rem; } .archify-source { display: flex; flex-direction: column; box-sizing: border-box; min-height: 260px; margin: 0.75rem 0 1rem; }
body > .archify-source { max-width: 1400px; margin: 0.75rem auto 1rem; padding: 0 2rem; } body > .archify-source { max-width: 1400px; margin: 0.75rem auto 1rem; padding: 0 2rem; }
.archify-source-editor { flex: 1; min-height: 0; overflow: hidden; border: 1px solid var(--panel-border, var(--vscode-editorWidget-border, rgba(128,128,128,.35))); border-radius: 0.75rem; .archify-source-editor { flex: 1; min-height: 0; overflow: hidden; border: 1px solid var(--panel-border, var(--vscode-editorWidget-border, rgba(128,128,128,.35))); border-radius: 0.75rem;
-1
View File
@@ -493,7 +493,6 @@ export async function run(): Promise<void> {
// No server of anyone's built in: empty until the user sets one. // No server of anyone's built in: empty until the user sets one.
const config = vscode.workspace.getConfiguration('archify.sync'); const config = vscode.workspace.getConfiguration('archify.sync');
assert.equal(config.get('server'), ''); assert.equal(config.get('server'), '');
assert.equal(config.get('statusBar'), true);
// The extension host's module cache: dist/sync.js (Yjs, the WebSocket) is not loaded. // The extension host's module cache: dist/sync.js (Yjs, the WebSocket) is not loaded.
const loaded = Object.keys(require.cache).filter((file) => file.endsWith(`${path.sep}dist${path.sep}sync.js`)); const loaded = Object.keys(require.cache).filter((file) => file.endsWith(`${path.sep}dist${path.sep}sync.js`));
assert.deepEqual(loaded, [], 'the sync bundle is not loaded'); assert.deepEqual(loaded, [], 'the sync bundle is not loaded');