Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dde9ffffe | ||
|
|
f6f59fae67 | ||
|
|
0752128789 | ||
|
|
517697197e | ||
|
|
c71296b01a | ||
|
|
86a8adf03b | ||
|
|
6153c7f145 | ||
|
|
885977c4aa | ||
|
|
95c6e60199 | ||
|
|
b54304fef8 | ||
|
|
8407c9a956 | ||
|
|
c2d33780f9 | ||
|
|
90c7be06de | ||
|
|
7d2d55543e | ||
|
|
c262bb06a7 | ||
|
|
63f0633daa | ||
|
|
ef272e4d96 | ||
|
|
f6097b1d00 | ||
|
|
9ccd67b2dd | ||
|
|
d02c331155 |
No files matched your search
@@ -1,5 +1,45 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.10.9 — 2026-11-08
|
||||||
|
|
||||||
|
- The Sync button is square again on the model, registry, decision and Graph pages (0.10.8 squeezed it narrow there).
|
||||||
|
|
||||||
|
## 0.10.8 — 2026-11-08
|
||||||
|
|
||||||
|
- **A model opens as its outline.** On `main.archify` the top-level panels (Shared, Diagrams, Registries, Decisions) start collapsed; open the one you need. What you open or close is kept while the page is open.
|
||||||
|
- **Sync button in line on every page.** On the model, registry and decision pages it sits next to Model | Graph | Source instead of floating above it, and on the Graph page it comes after Export PNG instead of covering it. It takes the height of the controls beside it.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- **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.
|
||||||
|
- **Server address as a setting.** `archify.sync.server` (empty by default, for example `https://archify.example.com`) is used by sign-in, share and the menu without asking. In the workspace settings it gives a whole project the same server. *Archify | Set Sync Server Address…* sets it.
|
||||||
|
|
||||||
## 0.10.0 — 2026-11-06
|
## 0.10.0 — 2026-11-06
|
||||||
|
|
||||||
- **Archify Sync (optional).** Several people edit one model at once through a server you run yourself ([archify-vscode-ext-sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync)); the files stay ordinary files on each disk. Commands: *Sign in to Sync Server…*, *Share Model…*, *Copy Invite Link*, *Join Shared Model…*, *Publish Version…*, *Versions…*, *Stop Syncing Model…*. Sign-in through the browser (PKCE), sessions in VS Code's secret storage, an *Archify Sync* account provider. Edits from editors, pages, Git and agents go out at once; others' edits arrive in open editors with undo intact. Everything is kept on the machine first, so work goes on offline and merges later. Teammates' cursors show as named carets in the Source view and in VS Code's text editors, and stay on their word while others type. The status bar shows who is here; the model page gets a **Shared** section with people, the file each has open, and the latest versions with **Compare** and **Restore**.
|
- **Archify Sync (optional).** Several people edit one model at once through a server you run yourself ([archify-vscode-ext-sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync)); the files stay ordinary files on each disk. Commands: *Sign in to Sync Server…*, *Share Model…*, *Copy Invite Link*, *Join Shared Model…*, *Publish Version…*, *Versions…*, *Stop Syncing Model…*. Sign-in through the browser (PKCE), sessions in VS Code's secret storage, an *Archify Sync* account provider. Edits from editors, pages, Git and agents go out at once; others' edits arrive in open editors with undo intact. Everything is kept on the machine first, so work goes on offline and merges later. Teammates' cursors show as named carets in the Source view and in VS Code's text editors, and stay on their word while others type. The status bar shows who is here; the model page gets a **Shared** section with people, the file each has open, and the latest versions with **Compare** and **Restore**.
|
||||||
|
|||||||
@@ -140,7 +140,9 @@ 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.
|
||||||
|
|
||||||
- **Sign in.** *Archify | Sign in to Sync Server…* asks for the server's address 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.
|
- **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.
|
||||||
|
- **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.
|
||||||
- **Invite and join.** *Archify | Copy Invite Link* copies a link for the model; send it any way you like. Opening it (or *Archify | Join Shared Model…*) signs the person in, or up, and puts the files in an empty folder of their choice.
|
- **Invite and join.** *Archify | Copy Invite Link* copies a link for the model; send it any way you like. Opening it (or *Archify | Join Shared Model…*) signs the person in, or up, and puts the files in an empty folder of their choice.
|
||||||
- **Live.** Edits in any editor, page, Source view, by Git or by an agent reach the others within a moment. Their edits arrive in your open editors with undo intact and are saved to disk; your own unsaved work stays unsaved. The status bar shows **Archify live · 3 here**; the model page gets a **Shared** section with who is here and in which file, and each diagram card says who has it open. In the Source view and in text editors, teammates' cursors show as coloured carets with their names.
|
- **Live.** Edits in any editor, page, Source view, by Git or by an agent reach the others within a moment. Their edits arrive in your open editors with undo intact and are saved to disk; your own unsaved work stays unsaved. The status bar shows **Archify live · 3 here**; the model page gets a **Shared** section with who is here and in which file, and each diagram card says who has it open. In the Source view and in text editors, teammates' cursors show as coloured carets with their names.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "archify-vscode-ext",
|
"name": "archify-vscode-ext",
|
||||||
"version": "0.10.0",
|
"version": "0.10.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "archify-vscode-ext",
|
"name": "archify-vscode-ext",
|
||||||
"version": "0.10.0",
|
"version": "0.10.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.4",
|
"@codemirror/autocomplete": "^6.20.4",
|
||||||
|
|||||||
+15
-6
@@ -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.0",
|
"version": "0.10.9",
|
||||||
"publisher": "root-at-skic",
|
"publisher": "root-at-skic",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"sponsor": {
|
"sponsor": {
|
||||||
@@ -163,7 +163,12 @@
|
|||||||
{
|
{
|
||||||
"command": "archify.sync.menu",
|
"command": "archify.sync.menu",
|
||||||
"title": "Archify | Sync…",
|
"title": "Archify | Sync…",
|
||||||
"icon": "$(broadcast)"
|
"icon": "$(live-share)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "archify.sync.setServer",
|
||||||
|
"title": "Archify | Set Sync Server Address…",
|
||||||
|
"icon": "$(server)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
@@ -223,10 +228,6 @@
|
|||||||
{
|
{
|
||||||
"command": "archify.sync.stop",
|
"command": "archify.sync.stop",
|
||||||
"when": "archify.sync.active"
|
"when": "archify.sync.active"
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "archify.sync.menu",
|
|
||||||
"when": "archify.sync.active"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"editor/title": [
|
"editor/title": [
|
||||||
@@ -513,6 +514,14 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"description": "Node.js executable (18 or newer) used to run the renderer. Empty means the runtime bundled with VS Code."
|
"description": "Node.js executable (18 or newer) used to run the renderer. Empty means the runtime bundled with VS Code."
|
||||||
|
},
|
||||||
|
"archify.sync.server": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"scope": "window",
|
||||||
|
"pattern": "^$|^https?://",
|
||||||
|
"patternErrorMessage": "An address such as https://archify.example.com",
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+36
-3
@@ -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);
|
||||||
|
|||||||
+61
-5
@@ -23,6 +23,7 @@ export const SYNC_COMMANDS = [
|
|||||||
'archify.sync.versions',
|
'archify.sync.versions',
|
||||||
'archify.sync.stop',
|
'archify.sync.stop',
|
||||||
'archify.sync.menu',
|
'archify.sync.menu',
|
||||||
|
'archify.sync.setServer',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const SKIP = '{**/node_modules/**,**/.git/**}';
|
const SKIP = '{**/node_modules/**,**/.git/**}';
|
||||||
@@ -38,6 +39,8 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
private readonly versionCache = new Map<string, { at: number; versions: Promise<Version[]> }>();
|
private readonly versionCache = new Map<string, { at: number; versions: Promise<Version[]> }>();
|
||||||
private readonly editorCursors: EditorCursors;
|
private readonly editorCursors: EditorCursors;
|
||||||
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. */
|
||||||
|
private signedIn: string[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly context: vscode.ExtensionContext,
|
private readonly context: vscode.ExtensionContext,
|
||||||
@@ -62,8 +65,9 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
vscode.workspace.registerTextDocumentContentProvider(VERSION_SCHEME, this.content),
|
vscode.workspace.registerTextDocumentContentProvider(VERSION_SCHEME, this.content),
|
||||||
this.auth.onDidChangeSessions(() => {
|
this.auth.onDidChangeSessions(() => {
|
||||||
for (const space of this.spaces.values()) space.reconnect();
|
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.editorCursors = new EditorCursors({ cursors: (file) => this.cursors(file), setCursor: (file, selection) => this.setCursorIn(file, selection) });
|
||||||
this.disposables.push(this.editorCursors);
|
this.disposables.push(this.editorCursors);
|
||||||
@@ -74,6 +78,11 @@ 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')));
|
||||||
|
await this.refreshSignedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refreshSignedIn(): Promise<void> {
|
||||||
|
this.signedIn = await this.auth.servers();
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +138,7 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
if (!spaces.length) {
|
if (!spaces.length) {
|
||||||
this.status.hide();
|
this.status.hide();
|
||||||
void vscode.commands.executeCommand('setContext', 'archify.sync.active', false);
|
void vscode.commands.executeCommand('setContext', 'archify.sync.active', false);
|
||||||
|
this.bridge?.changed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void vscode.commands.executeCommand('setContext', 'archify.sync.active', true);
|
void vscode.commands.executeCommand('setContext', 'archify.sync.active', true);
|
||||||
@@ -300,6 +310,9 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
case 'archify.sync.menu':
|
case 'archify.sync.menu':
|
||||||
await this.menu();
|
await this.menu();
|
||||||
break;
|
break;
|
||||||
|
case 'archify.sync.setServer':
|
||||||
|
await this.setServer();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.report(error);
|
this.report(error);
|
||||||
@@ -319,8 +332,13 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
else if (uri.path === '/join') await this.run('archify.sync.join', uri.toString(true));
|
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> {
|
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({
|
const value = await vscode.window.showInputBox({
|
||||||
title: 'Archify | Sync server',
|
title: 'Archify | Sync server',
|
||||||
prompt: 'The address of the Archify Sync server',
|
prompt: 'The address of the Archify Sync server',
|
||||||
@@ -350,6 +368,16 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
return true;
|
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> {
|
private async signInCommand(): Promise<void> {
|
||||||
const server = await this.askServer();
|
const server = await this.askServer();
|
||||||
if (!server || !(await this.checkServer(server))) return;
|
if (!server || !(await this.checkServer(server))) return;
|
||||||
@@ -531,7 +559,10 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
private async menu(): Promise<void> {
|
private async menu(): Promise<void> {
|
||||||
const spaces = [...this.spaces.values()];
|
const spaces = [...this.spaces.values()];
|
||||||
const signedOut = spaces.find((s) => s.state === 'signed-out');
|
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) {
|
if (signedOut) {
|
||||||
items.push({
|
items.push({
|
||||||
label: `$(account) Sign in to ${new URL(signedOut.link.server).host}`,
|
label: `$(account) Sign in to ${new URL(signedOut.link.server).host}`,
|
||||||
@@ -539,16 +570,35 @@ export class SyncFeature implements vscode.Disposable, LiveProvider {
|
|||||||
if (await this.checkServer(signedOut.link.server)) await this.auth.signIn(signedOut.link.server);
|
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(
|
items.push(
|
||||||
|
{ 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: '$(person-add) Copy invite link…', run: () => this.invite(undefined) },
|
||||||
{ label: '$(tag) Publish version…', run: () => this.publish(undefined) },
|
{ label: '$(tag) Publish version…', run: () => this.publish(undefined) },
|
||||||
{ label: '$(history) Versions…', run: () => this.run('archify.sync.versions') },
|
{ 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: '$(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' });
|
const pick = await vscode.window.showQuickPick(items, { title: 'Archify Sync' });
|
||||||
await pick?.run();
|
await pick?.run?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
@@ -560,6 +610,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). */
|
/** The file behind the active tab when it is a custom editor (the Archify pages). */
|
||||||
function activeTabUri(): vscode.Uri | undefined {
|
function activeTabUri(): vscode.Uri | undefined {
|
||||||
const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input;
|
const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const COMMANDS = [
|
|||||||
'archify.sync.versions',
|
'archify.sync.versions',
|
||||||
'archify.sync.stop',
|
'archify.sync.stop',
|
||||||
'archify.sync.menu',
|
'archify.sync.menu',
|
||||||
|
'archify.sync.setServer',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
+81
-5
@@ -208,6 +208,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// Collapsible panels of the model page; open/closed is kept in the webview state.
|
// Collapsible panels of the model page; open/closed is kept in the webview state.
|
||||||
|
// The model page's top-level panels start closed: a model opens as its outline.
|
||||||
function panelStates() { return (vscode.getState() || {}).modelPanels || {}; }
|
function panelStates() { return (vscode.getState() || {}).modelPanels || {}; }
|
||||||
function panel(key, className, title, count, defaultOpen) {
|
function panel(key, className, title, count, defaultOpen) {
|
||||||
// count: a number or a short text such as "3 files".
|
// count: a number or a short text such as "3 files".
|
||||||
@@ -624,7 +625,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
live.hidden = true;
|
live.hidden = true;
|
||||||
body.appendChild(live);
|
body.appendChild(live);
|
||||||
var count = model.groups.reduce(function (n, g) { return n + g.diagrams.length; }, 0);
|
var count = model.groups.reduce(function (n, g) { return n + g.diagrams.length; }, 0);
|
||||||
var diagramsPanel = panel('diagrams', 'archify-model-diagrams', 'Diagrams', count, true);
|
var diagramsPanel = panel('diagrams', 'archify-model-diagrams', 'Diagrams', count, false);
|
||||||
body.appendChild(diagramsPanel);
|
body.appendChild(diagramsPanel);
|
||||||
if (!count) diagramsPanel.appendChild(el('p', 'archify-model-empty', 'No diagrams yet. Add .archify files next to main.archify or in subfolders.'));
|
if (!count) diagramsPanel.appendChild(el('p', 'archify-model-empty', 'No diagrams yet. Add .archify files next to main.archify or in subfolders.'));
|
||||||
model.groups.forEach(function (group) {
|
model.groups.forEach(function (group) {
|
||||||
@@ -658,7 +659,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
byFile[element.file].push(element);
|
byFile[element.file].push(element);
|
||||||
});
|
});
|
||||||
files.sort();
|
files.sort();
|
||||||
var registryPanel = panel('registry', 'archify-model-registry', 'Registries', files.length + (files.length === 1 ? ' file' : ' files'), true);
|
var registryPanel = panel('registry', 'archify-model-registry', 'Registries', files.length + (files.length === 1 ? ' file' : ' files'), false);
|
||||||
body.appendChild(registryPanel);
|
body.appendChild(registryPanel);
|
||||||
files.forEach(function (file) {
|
files.forEach(function (file) {
|
||||||
var elements = byFile[file].slice().sort(function (a, b) { return a.line - b.line; });
|
var elements = byFile[file].slice().sort(function (a, b) { return a.line - b.line; });
|
||||||
@@ -676,7 +677,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
}
|
}
|
||||||
// Decisions: one list, live ones first, superseded and rejected dimmed and last.
|
// Decisions: one list, live ones first, superseded and rejected dimmed and last.
|
||||||
if (model.decisions && model.decisions.length) {
|
if (model.decisions && model.decisions.length) {
|
||||||
var decisionsPanel = panel('decisions', 'archify-model-decisions', 'Decisions', model.decisions.length, true);
|
var decisionsPanel = panel('decisions', 'archify-model-decisions', 'Decisions', model.decisions.length, false);
|
||||||
body.appendChild(decisionsPanel);
|
body.appendChild(decisionsPanel);
|
||||||
var decisionList = el('ul', 'archify-model-list archify-decision-list');
|
var decisionList = el('ul', 'archify-model-list archify-decision-list');
|
||||||
model.decisions.forEach(function (row) { decisionList.appendChild(decisionRowItem(row)); });
|
model.decisions.forEach(function (row) { decisionList.appendChild(decisionRowItem(row)); });
|
||||||
@@ -723,7 +724,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
if (!info) { holder.hidden = true; return; }
|
if (!info) { holder.hidden = true; return; }
|
||||||
holder.hidden = false;
|
holder.hidden = false;
|
||||||
var others = info.people.filter(function (p) { return !p.me; });
|
var others = info.people.filter(function (p) { return !p.me; });
|
||||||
var livePanel = panel('live', 'archify-model-live is-' + info.state, 'Shared \u00b7 ' + info.name, others.length ? others.length + 1 + ' here' : info.host, true);
|
var livePanel = panel('live', 'archify-model-live is-' + info.state, 'Shared \u00b7 ' + info.name, others.length ? others.length + 1 + ' here' : info.host, false);
|
||||||
var status = el('div', 'archify-live-status');
|
var status = el('div', 'archify-live-status');
|
||||||
status.appendChild(el('span', 'archify-live-dot'));
|
status.appendChild(el('span', 'archify-live-dot'));
|
||||||
status.appendChild(el('span', 'archify-live-state', (LIVE_STATE[info.state] || info.state) + (info.state === 'online' ? ' on ' + info.host : '')));
|
status.appendChild(el('span', 'archify-live-state', (LIVE_STATE[info.state] || info.state) + (info.state === 'online' ? ' on ' + info.host : '')));
|
||||||
@@ -887,6 +888,78 @@ 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' }); });
|
||||||
|
// Last in the row that holds the view switch: Archify's toolbar on a
|
||||||
|
// diagram (after Export), the Graph page's header actions (after Export,
|
||||||
|
// built a moment later by graph.js), next to the switch on the other
|
||||||
|
// pages. It floats only while there is no such row.
|
||||||
|
document.body.appendChild(button);
|
||||||
|
function place() {
|
||||||
|
var modeSwitch = document.getElementById('archify-mode-switch');
|
||||||
|
var row = document.querySelector('.ag-actions') || (modeSwitch && modeSwitch.parentElement) || document.querySelector('body > .toolbar');
|
||||||
|
if (!row || row === document.body) return false;
|
||||||
|
button.classList.add('in-toolbar');
|
||||||
|
row.classList.add('archify-sync-row');
|
||||||
|
row.appendChild(button);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!place() && window.MutationObserver) {
|
||||||
|
var watch = new MutationObserver(function () { if (place()) watch.disconnect(); });
|
||||||
|
watch.observe(document.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
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-model-switch-row.archify-sync-row { display: flex !important; align-items: center !important; gap: 0.5rem !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; }'
|
||||||
|
+ ' .archify-model-switch-row #archify-sync-button.in-toolbar { width: 33px !important; height: 33px !important; }'
|
||||||
|
+ ' .ag-actions #archify-sync-button.in-toolbar { width: 29px !important; height: 29px !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 +1086,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; }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -489,7 +489,10 @@ export async function run(): Promise<void> {
|
|||||||
|
|
||||||
await check('without a shared model, sync stays unloaded and its commands are there', async () => {
|
await check('without a shared model, sync stays unloaded and its commands are there', async () => {
|
||||||
const commands = await vscode.commands.getCommands(true);
|
const commands = await vscode.commands.getCommands(true);
|
||||||
for (const id of ['archify.sync.signIn', 'archify.sync.share', 'archify.sync.join']) assert.ok(commands.includes(id), `${id} is registered`);
|
for (const id of ['archify.sync.signIn', 'archify.sync.share', 'archify.sync.join', 'archify.sync.menu', 'archify.sync.setServer']) assert.ok(commands.includes(id), `${id} is registered`);
|
||||||
|
// No server of anyone's built in: empty until the user sets one.
|
||||||
|
const config = vscode.workspace.getConfiguration('archify.sync');
|
||||||
|
assert.equal(config.get('server'), '');
|
||||||
// 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');
|
||||||
|
|||||||
Reference in new issue
Block a user