@volter/sdk 0.5.206 → 0.5.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/kit/account-client.ts +8 -2
- package/src/kit/api/project-open.ts +14 -0
- package/src/kit/components/ProjectHeader.tsx +2 -2
- package/src/kit/components/status-contributions.tsx +26 -1
- package/src/kit/document-play-extension.ts +26 -0
- package/src/kit/keymap-presets.ts +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@volter/sdk",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.207",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -487,7 +487,7 @@
|
|
|
487
487
|
"@fortawesome/react-fontawesome": "^3.2.0",
|
|
488
488
|
"@storybook/react": "10.6.0",
|
|
489
489
|
"@types/node": "^22.0.0",
|
|
490
|
-
"@volter/project": "0.5.
|
|
490
|
+
"@volter/project": "0.5.207",
|
|
491
491
|
"mediabunny": "1.56.2",
|
|
492
492
|
"postcss": "8.5.28",
|
|
493
493
|
"rrweb": "2.1.6",
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
generationAccountProjection,
|
|
16
16
|
ProviderCredentialTestResultSchema,
|
|
17
17
|
} from '@volter/sdk/account';
|
|
18
|
+
import { LIMITED_VIEW_HEADER } from '@volter/sdk/session/limited-view';
|
|
18
19
|
import { assertEditorServerAnswered } from '@volter/sdk/kit/editor-server-response';
|
|
19
20
|
import { COLLABORATION_REMOTE_SHARE } from '@volter/sdk/kit/editor-session-attribution';
|
|
20
21
|
|
|
@@ -88,9 +89,14 @@ export function subscribeAccount(listener: () => void): () => void {
|
|
|
88
89
|
listeners.add(listener);
|
|
89
90
|
return () => listeners.delete(listener);
|
|
90
91
|
}
|
|
92
|
+
/** A limited view has no account service and says so once (`x-volter-limited-view: unavailable`);
|
|
93
|
+
* asking again every 30 s only fills the page's network log with 503s. */
|
|
94
|
+
let accountUnavailable = false;
|
|
91
95
|
export async function refreshAccount(): Promise<EditorAccountSnapshot> {
|
|
92
96
|
try {
|
|
93
|
-
const
|
|
97
|
+
const response = await fetch('/__editor/account');
|
|
98
|
+
if (response.headers.get(LIMITED_VIEW_HEADER) === 'unavailable') accountUnavailable = true;
|
|
99
|
+
const next = await responseSnapshot(response, 'Account read failed');
|
|
94
100
|
publish(next);
|
|
95
101
|
return next;
|
|
96
102
|
} catch (cause) {
|
|
@@ -328,6 +334,6 @@ export function startAccountActivity(): () => void {
|
|
|
328
334
|
} else {
|
|
329
335
|
void refreshAccount().catch(() => undefined);
|
|
330
336
|
}
|
|
331
|
-
const timer = window.setInterval(() => void refreshAccount().catch(() => undefined), 30_000);
|
|
337
|
+
const timer = window.setInterval(() => { if (!accountUnavailable) void refreshAccount().catch(() => undefined); }, 30_000);
|
|
332
338
|
return () => window.clearInterval(timer);
|
|
333
339
|
}
|
|
@@ -202,6 +202,20 @@ export async function revealInFinder(targetPath: string): Promise<void> {
|
|
|
202
202
|
assertEditorServerResponse(res, `Could not reveal ${targetPath}`);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* The file manager `revealInFinder` opens, named for the menus that offer it: Finder on a Mac,
|
|
207
|
+
* File Explorer on Windows (`explorer /select,`), the desktop's file manager elsewhere (`xdg-open`
|
|
208
|
+
* on the containing folder). `/__editor/reveal` is never shared (`NEVER_SHARE_PATHS`), so the
|
|
209
|
+
* person who can use it is at the host and the browser's platform is the host's — WSL aside,
|
|
210
|
+
* where the label is only a word.
|
|
211
|
+
*/
|
|
212
|
+
export function fileManagerName(): string {
|
|
213
|
+
const platform = typeof navigator === 'undefined' ? '' : navigator.platform;
|
|
214
|
+
if (platform.includes('Mac')) return 'Finder';
|
|
215
|
+
if (platform.startsWith('Win')) return 'File Explorer';
|
|
216
|
+
return 'File Manager';
|
|
217
|
+
}
|
|
218
|
+
|
|
205
219
|
/** Save a viewport thumbnail for the current project — background bookkeeping
|
|
206
220
|
* for the recent-projects list, not a user-facing promise. */
|
|
207
221
|
export async function saveThumbnail(dataUrl: string): Promise<boolean> {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
contributedHeaderItems,
|
|
25
25
|
subscribeContributedChrome,
|
|
26
26
|
} from '@volter/sdk/kit/chrome-registry';
|
|
27
|
-
import { revealInFinder } from '@volter/sdk/kit/editor-api';
|
|
27
|
+
import { fileManagerName, revealInFinder } from '@volter/sdk/kit/editor-api';
|
|
28
28
|
import { getCurrentProject, onProjectChange } from '@volter/sdk/kit/project-manager';
|
|
29
29
|
import {
|
|
30
30
|
activeEditorWorkspace,
|
|
@@ -212,7 +212,7 @@ export function ProjectHeader({
|
|
|
212
212
|
revealInFinder(projectPath);
|
|
213
213
|
}}
|
|
214
214
|
>
|
|
215
|
-
Show in
|
|
215
|
+
Show in {fileManagerName()}
|
|
216
216
|
</MenuItem>
|
|
217
217
|
)}
|
|
218
218
|
<MenuItem
|
|
@@ -62,6 +62,12 @@ import {
|
|
|
62
62
|
subscribeEditorLeaseView,
|
|
63
63
|
} from '../editor-lease-view';
|
|
64
64
|
import { useEditorStore } from '@volter/sdk/kit/editor-runtime';
|
|
65
|
+
import { documentPlayState, subscribeDocumentPlayState } from '@volter/sdk/kit/document-play-extension';
|
|
66
|
+
import {
|
|
67
|
+
openWorkspaceDocuments,
|
|
68
|
+
subscribeWorkspaceDocuments,
|
|
69
|
+
workspaceDocumentRegistryVersion,
|
|
70
|
+
} from '@volter/sdk/kit/workspace-document-registry';
|
|
65
71
|
import { createHmrRegistrationGroup, type HmrRegistrationContext } from '@volter/sdk/kit/hmr-registration-group';
|
|
66
72
|
import { useProjectMounts } from '../project-shape';
|
|
67
73
|
import { getStorageBackend } from '@volter/sdk/kit/storage/index';
|
|
@@ -211,6 +217,19 @@ export function AssetMaterializationStatus() {
|
|
|
211
217
|
);
|
|
212
218
|
}
|
|
213
219
|
|
|
220
|
+
/** The open documents' own Play, as one word: playing if any plays, else paused if any is paused. */
|
|
221
|
+
function openDocumentsPlayState(): 'playing' | 'paused' | 'stopped' {
|
|
222
|
+
let paused = false;
|
|
223
|
+
for (const document of openWorkspaceDocuments()) {
|
|
224
|
+
const state = documentPlayState(document.descriptor.id);
|
|
225
|
+
if (state === 'playing') return 'playing';
|
|
226
|
+
if (state === 'paused') paused = true;
|
|
227
|
+
}
|
|
228
|
+
return paused ? 'paused' : 'stopped';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const stoppedOnServer = (): 'stopped' => 'stopped';
|
|
232
|
+
|
|
214
233
|
/** §4.1 play state ("Play stopped" quiet; running/paused colored). */
|
|
215
234
|
export function PlayStateStatus() {
|
|
216
235
|
// EVERY hook above the first early return. This component rendered one hook
|
|
@@ -222,8 +241,14 @@ export function PlayStateStatus() {
|
|
|
222
241
|
const mounts = useProjectMounts();
|
|
223
242
|
const store = useEditorStore();
|
|
224
243
|
useSyncExternalStore(store.subscribe, store.getShellSnapshot ?? store.getSnapshot);
|
|
244
|
+
// A DOCUMENT'S OWN PLAY is Play too. Cyclotron's Play runs a model's play script through the
|
|
245
|
+
// document Play tool, not the store's game, so this said "Play stopped" while the race ran (the
|
|
246
|
+
// 0.5.206 blind walk). Any open document counts, not only the active one: a source file opened
|
|
247
|
+
// beside a running race does not stop it. The store's game wins when it runs.
|
|
248
|
+
useSyncExternalStore(subscribeWorkspaceDocuments, workspaceDocumentRegistryVersion, workspaceDocumentRegistryVersion);
|
|
249
|
+
const documentPlay = useSyncExternalStore(subscribeDocumentPlayState, openDocumentsPlayState, stoppedOnServer);
|
|
225
250
|
if (!mounts) return null;
|
|
226
|
-
const state = store.playState;
|
|
251
|
+
const state = store.playState === 'stopped' ? documentPlay : store.playState;
|
|
227
252
|
const view = {
|
|
228
253
|
stopped: { label: 'Play stopped', tone: undefined },
|
|
229
254
|
playing: { label: 'Playing', tone: 'success' },
|
|
@@ -208,6 +208,32 @@ export function subscribeDocumentPlayExtensions(listener: () => void): () => voi
|
|
|
208
208
|
registry.listeners.add(listener);
|
|
209
209
|
return () => { registry.listeners.delete(listener); };
|
|
210
210
|
}
|
|
211
|
+
/** Whether a document's own Play runs, and whether it is paused: what a status line says. */
|
|
212
|
+
export function documentPlayState(documentId: string): 'playing' | 'paused' | 'stopped' {
|
|
213
|
+
for (const extension of registry.extensions.values()) {
|
|
214
|
+
if (!extension.playing(documentId)) continue;
|
|
215
|
+
return extension.transport?.clock(documentId).paused ? 'paused' : 'playing';
|
|
216
|
+
}
|
|
217
|
+
return 'stopped';
|
|
218
|
+
}
|
|
219
|
+
/** Every Play start and stop, and every tool's clock (a pause is a clock change). The clock moves
|
|
220
|
+
* every played frame, so this is for a reader whose snapshot changes rarely. */
|
|
221
|
+
export function subscribeDocumentPlayState(listener: () => void): () => void {
|
|
222
|
+
let clocks: (() => void)[] = [];
|
|
223
|
+
const rebind = (): void => {
|
|
224
|
+
for (const stop of clocks) stop();
|
|
225
|
+
clocks = [...registry.extensions.values()].flatMap((extension) =>
|
|
226
|
+
extension.transport ? [extension.transport.subscribeClock(listener)] : []);
|
|
227
|
+
};
|
|
228
|
+
const onRegistry = (): void => { rebind(); listener(); };
|
|
229
|
+
registry.listeners.add(onRegistry);
|
|
230
|
+
rebind();
|
|
231
|
+
return () => {
|
|
232
|
+
registry.listeners.delete(onRegistry);
|
|
233
|
+
for (const stop of clocks) stop();
|
|
234
|
+
clocks = [];
|
|
235
|
+
};
|
|
236
|
+
}
|
|
211
237
|
export function registerDocumentPlayExtension(kind: string, extension: DocumentPlayExtension): () => void {
|
|
212
238
|
if (registry.extensions.has(kind)) throw new Error(`A Play tool already owns ${kind} documents.`);
|
|
213
239
|
registry.extensions.set(kind, extension);
|
|
@@ -349,7 +349,7 @@ export function keymapTable(id: EditorKeymapId): EditorKeymapTable {
|
|
|
349
349
|
// Display
|
|
350
350
|
// ---------------------------------------------------------------------------
|
|
351
351
|
|
|
352
|
-
function isMacPlatform(): boolean {
|
|
352
|
+
export function isMacPlatform(): boolean {
|
|
353
353
|
return typeof navigator !== 'undefined' && navigator.platform.includes('Mac');
|
|
354
354
|
}
|
|
355
355
|
|