Address review: playable PLAY/NEXT, visible rename editor, accurate copy

- PLAY and NEXT continue into the following scenes, so they stay
  enabled while any scene in the project has a shot, not only the
  selected one.
- Renaming a shot drops the number and ellipsis so the editor is no
  longer hidden; qa-scene-controls checks a long rename stays visible.
- PLAY, NEXT, EDIT DETAILS and SHARE SCENE tooltips describe what the
  buttons do, translated in every shipped language.
- The primary actions and the file row keep a hover state.
- Docs: the EXPORT rename in DIRECTOR-SHARING and the playback button
  states in CURRENT-STATE are accurate.
- Test the voice "authoring" alias through set_panel_open.
This commit is contained in:
Said Turksever committed 2026-10-29 18:39:53 +00:00
1 parent 8de75b172a
commit 5a80facf8d
14 files changed
+129 -34

No files matched your search

+8 -6
View File
@@ -1176,12 +1176,14 @@ The panel is titled Authoring and is laid out in four labelled steps: SCENE
(picker on its own row, NEW SCENE, DELETE), SHOTS (+ CAPTURE SHOT, UPDATE SHOT,
the numbered shot list and a select/rename hint), PREVIEW (PLAY, STOP, NEXT) and
SAVE & SHARE (EXPORT, IMPORT, RUN LOG, then the EDIT DETAILS / SHARE SCENE row).
Every button in the panel's step rows has a tooltip, and the status line is a
polite live region. Outside
playback a button that would do nothing is disabled: DELETE and CAPTURE SHOT
without a scene, UPDATE SHOT without a selected shot, PLAY and NEXT on a scene
with no shots. During playback everything but STOP stays disabled as before.
PLAY uses the `scene|PLAY` catalog entry, apart from the radio's PLAY.
Every button in the step rows has a tooltip, and the status line is a polite
live region. Outside playback a button that would do nothing is disabled:
DELETE and CAPTURE SHOT without a scene, UPDATE SHOT without a selected shot,
PLAY and NEXT when no scene in the project has a shot (both continue into the
following scenes). During playback the authoring and file actions stay disabled
and STOP is enabled, as before; RUN LOG follows the last run, and EDIT DETAILS /
SHARE SCENE keep their own checks. PLAY uses the `scene|PLAY` catalog entry,
apart from the radio's PLAY.
Director clock listeners, interaction observers and scene release failures no
longer leave the director inconsistent, and a phase error is kept when cleanup
+1 -1
View File
@@ -42,7 +42,7 @@ field definitions. This is a basic draft editor, not a node graph or path editor
SHARE SCENE exports only the selected scene. Download scene JSON preserves its
camera, timing, layers, anchors, manifests, interactions and attribution, but
contains no asset bytes. The recipient needs the referenced source registrations
and files. Existing EXPORT PRESETS continues to export the entire project.
and files. EXPORT (formerly EXPORT PRESETS) continues to export the entire project.
For a bundle, select the declared pack files or a folder, then Download asset
bundle. Folder paths match a manifest path either directly or after removing the
+33
View File
@@ -106,6 +106,39 @@ try {
),
),
);
// A long rename keeps its editor visible inside the shot row.
await page.click('.scene-shot-label', { count: 2 });
await page.waitForSelector('.scene-shot-rename', { visible: true });
await page.type(
'.scene-shot-rename',
' with a much longer title that runs well past the shot row',
);
await page.keyboard.press('End');
check(
'A long rename keeps the editor visible inside its shot row',
await page.evaluate(() => {
// The label clips with an ellipsis, which hides an editor that
// overflows it, so its content must fit its width.
const input = document.querySelector('.scene-shot-rename');
const label = input?.closest('.scene-shot-label');
const box = input?.getBoundingClientRect();
const style = input && getComputedStyle(input);
return (
!!box &&
box.width > 120 &&
box.height > 10 &&
label.scrollWidth <= label.clientWidth + 1 &&
style.visibility === 'visible' &&
style.borderTopWidth !== '0px' &&
document.activeElement === input
);
}),
);
await page.keyboard.press('Escape');
await page.waitForFunction(
() => !document.querySelector('.scene-shot-rename'),
);
await page.click('#scene-capture-btn');
await page.waitForFunction(
() => document.querySelectorAll('.scene-shot-row').length === 2,
+4 -4
View File
@@ -3013,13 +3013,13 @@
"Replace the selected shot with the current view": "Sustituye el plano seleccionado por la vista actual",
"Click a shot to select it, double-click to rename.": "Haz clic en un plano para seleccionarlo y doble clic para cambiarle el nombre.",
"scene|PLAY": "REPROD.",
"Play the whole scene from its first shot": "Reproduce toda la escena desde su primer plano",
"Stop playback (Esc)": "Detén la reproducción (Esc)",
"Play only the shot after the selected one": "Reproduce solo el plano posterior al seleccionado",
"SAVE & SHARE": "GUARDAR Y COMPARTIR",
"Download every scene as a JSON file you can import later": "Descarga todas las escenas como un archivo JSON que puedes importar más tarde",
"Load scenes from an exported JSON file": "Carga escenas desde un archivo JSON exportado",
"Download the timing log of the last play": "Descarga el registro de tiempos de la última reproducción",
"Edit the scene title and description": "Edita el título y la descripción de la escena",
"Share this scene as a link or file": "Comparte esta escena como enlace o archivo"
"Play from this scene through the rest of the project": "Reproduce desde esta escena hasta el resto del proyecto",
"Play the shot after the selected one, moving on to the next scene when needed": "Reproduce el plano posterior al seleccionado y pasa a la siguiente escena si hace falta",
"Edit anchors, data packs and shot details": "Edita anclas, paquetes de datos y detalles de los planos",
"Download this scene as JSON or an asset bundle": "Descarga esta escena como JSON o como paquete de recursos"
}
+4 -4
View File
@@ -3082,13 +3082,13 @@
"Replace the selected shot with the current view": "Заменить выбранный кадр текущим видом",
"Click a shot to select it, double-click to rename.": "Щёлкните кадр, чтобы выбрать его, дважды — чтобы переименовать.",
"scene|PLAY": "ПУСК",
"Play the whole scene from its first shot": "Воспроизвести всю сцену с первого кадра",
"Stop playback (Esc)": "Остановить воспроизведение (Esc)",
"Play only the shot after the selected one": "Воспроизвести только кадр после выбранного",
"SAVE & SHARE": "СОХРАНИТЬ И ПОДЕЛИТЬСЯ",
"Download every scene as a JSON file you can import later": "Скачать все сцены файлом JSON для последующего импорта",
"Load scenes from an exported JSON file": "Загрузить сцены из экспортированного файла JSON",
"Download the timing log of the last play": "Скачать журнал таймингов последнего воспроизведения",
"Edit the scene title and description": "Изменить название и описание сцены",
"Share this scene as a link or file": "Поделиться сценой ссылкой или файлом"
"Play from this scene through the rest of the project": "Воспроизвести с этой сцены и далее по всему проекту",
"Play the shot after the selected one, moving on to the next scene when needed": "Воспроизвести кадр после выбранного, при необходимости перейдя к следующей сцене",
"Edit anchors, data packs and shot details": "Изменить якоря, пакеты данных и параметры кадров",
"Download this scene as JSON or an asset bundle": "Скачать эту сцену в формате JSON или архивом ресурсов"
}
+4 -4
View File
@@ -2875,13 +2875,13 @@
"Replace the selected shot with the current view": "用当前视角替换所选镜头",
"Click a shot to select it, double-click to rename.": "单击镜头即可选中,双击可重命名。",
"scene|PLAY": "播放",
"Play the whole scene from its first shot": "从第一个镜头开始播放整个场景",
"Stop playback (Esc)": "停止播放(Esc)",
"Play only the shot after the selected one": "仅播放所选镜头的下一个镜头",
"SAVE & SHARE": "保存与分享",
"Download every scene as a JSON file you can import later": "将所有场景下载为 JSON 文件,以便日后导入",
"Load scenes from an exported JSON file": "从导出的 JSON 文件加载场景",
"Download the timing log of the last play": "下载上次播放的计时日志",
"Edit the scene title and description": "编辑场景标题和描述",
"Share this scene as a link or file": "以链接或文件形式分享此场景"
"Play from this scene through the rest of the project": "从此场景开始播放项目中的其余场景",
"Play the shot after the selected one, moving on to the next scene when needed": "播放所选镜头的下一个镜头,必要时进入下一个场景",
"Edit anchors, data packs and shot details": "编辑锚点、数据包和镜头详情",
"Download this scene as JSON or an asset bundle": "将此场景下载为 JSON 或资源包"
}
+4 -4
View File
@@ -2875,13 +2875,13 @@
"Replace the selected shot with the current view": "用當前視角替換所選鏡頭",
"Click a shot to select it, double-click to rename.": "單擊鏡頭即可選中,雙擊可重命名。",
"scene|PLAY": "播放",
"Play the whole scene from its first shot": "從第一個鏡頭開始播放整個場景",
"Stop playback (Esc)": "停止播放(Esc)",
"Play only the shot after the selected one": "僅播放所選鏡頭的下一個鏡頭",
"SAVE & SHARE": "保存與分享",
"Download every scene as a JSON file you can import later": "將所有場景下載為 JSON 文件,以便日後導入",
"Load scenes from an exported JSON file": "從導出的 JSON 文件加載場景",
"Download the timing log of the last play": "下載上次播放的計時日誌",
"Edit the scene title and description": "編輯場景標題和描述",
"Share this scene as a link or file": "以鏈接或文件形式分享此場景"
"Play from this scene through the rest of the project": "從此場景開始播放項目中的其餘場景",
"Play the shot after the selected one, moving on to the next scene when needed": "播放所選鏡頭的下一個鏡頭,必要時進入下一個場景",
"Edit anchors, data packs and shot details": "編輯錨點、數據包和鏡頭詳情",
"Download this scene as JSON or an asset bundle": "將此場景下載為 JSON 或資源包"
}
+5 -1
View File
@@ -220,7 +220,11 @@ export class SceneControls {
const shots = scene?.shots ?? [];
presentSceneButtons(this.elements, running, state.hasRun, {
hasScene: !!scene,
shotCount: shots.length,
// PLAY and NEXT run on into the following scenes.
playableShots: (state.scenes ?? []).reduce(
(total, item) => total + (item.shots?.length ?? 0),
0,
),
hasSelectedShot: shots.some((shot) => shot.id === state.selectedShotId),
});
this.elements.panel?.classList.toggle('has-shots', shots.length > 0);
+9
View File
@@ -184,7 +184,16 @@ test('Authoring disables idle actions that would do nothing', () => {
assert.equal(f.elements.update.disabled, true);
assert.equal(f.elements.start.disabled, false);
// PLAY and NEXT run on into the following scenes, so an empty selected
// scene beside one with shots keeps them available.
f.state.scenes[0].shots = [];
f.state.scenes.push({ id: 'scene-b', title: 'B', shots: [{ id: 'b1' }] });
f.owner.setButtons(false);
assert.equal(f.elements.start.disabled, false);
assert.equal(f.elements.next.disabled, false);
assert.equal(f.elements.update.disabled, true);
f.state.scenes.pop();
f.owner.setButtons(false);
assert.equal(f.elements.start.disabled, true);
assert.equal(f.elements.next.disabled, true);
+8 -6
View File
@@ -173,22 +173,24 @@ export function presentSceneSelection(element, selectedShotId) {
}
/**
* Everything but STOP is off while a scene plays. Otherwise a button is off
* when it would do nothing: DELETE and CAPTURE SHOT without a scene, UPDATE
* SHOT without a selected shot, PLAY and NEXT on a scene with no shots.
* While a scene plays, the authoring and file actions here are off and STOP
* is on. Otherwise a button is off when it would do nothing: DELETE and
* CAPTURE SHOT without a scene, UPDATE SHOT without a selected shot, PLAY and
* NEXT when no scene in the project has a shot (both continue into the
* following scenes).
*/
export function presentSceneButtons(
elements,
running,
hasRun,
{ hasScene = true, shotCount = 1, hasSelectedShot = true } = {},
{ hasScene = true, playableShots = 1, hasSelectedShot = true } = {},
) {
const idleOff = {
delete: !hasScene,
capture: !hasScene,
update: !hasSelectedShot,
start: shotCount === 0,
next: shotCount === 0,
start: playableShots === 0,
next: playableShots === 0,
};
for (const name of [
'start',
+6 -2
View File
@@ -181,8 +181,12 @@ export function mountSceneSharing(
bar.dataset.directorAuthoring = '';
bar.className = 'scene-controls';
const entries = [
[t('EDIT DETAILS'), t('Edit the scene title and description'), edit],
[t('SHARE SCENE'), t('Share this scene as a link or file'), share],
[t('EDIT DETAILS'), t('Edit anchors, data packs and shot details'), edit],
[
t('SHARE SCENE'),
t('Download this scene as JSON or an asset bundle'),
share,
],
];
const removers = [];
for (const [text, title, fn] of entries) {
+18
View File
@@ -223,6 +223,14 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
background: transparent;
}
/* Keep the shared hover on the restyled actions. */
#scene-panel .scene-btn-primary:hover:not(:disabled),
#scene-panel .scene-file-controls .scene-btn:hover:not(:disabled) {
color: var(--accent);
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.scene-shot-hint {
margin: 0 0 8px;
font-size: 11px;
@@ -311,6 +319,16 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
color: var(--text-primary);
}
/* While renaming, the label holds only the editor at full width. */
.scene-shot-label:has(.scene-shot-rename) {
overflow: visible;
white-space: normal;
}
.scene-shot-label:has(.scene-shot-rename)::before {
content: none;
}
/* Shots are numbered 01, 02… in playback order. */
.scene-shot-label::before {
content: counter(scene-shot, decimal-leading-zero);
+2 -2
View File
@@ -166,9 +166,9 @@
<p class="scene-shot-hint" data-i18n>Click a shot to select it, double-click to rename.</p>
<p class="scene-step-heading" data-i18n>PREVIEW</p>
<div class="scene-controls">
<button id="scene-start-btn" type="button" class="gev-action-button scene-btn scene-btn-primary" title="Play the whole scene from its first shot" data-i18n-title data-i18n-runtime>PLAY</button>
<button id="scene-start-btn" type="button" class="gev-action-button scene-btn scene-btn-primary" title="Play from this scene through the rest of the project" data-i18n-title data-i18n-runtime>PLAY</button>
<button id="scene-stop-btn" type="button" class="gev-action-button gev-action-button--danger scene-btn scene-btn-danger" title="Stop playback (Esc)" data-i18n data-i18n-title>STOP</button>
<button id="scene-next-btn" type="button" class="gev-action-button scene-btn" title="Play only the shot after the selected one" data-i18n data-i18n-title>NEXT</button>
<button id="scene-next-btn" type="button" class="gev-action-button scene-btn" title="Play the shot after the selected one, moving on to the next scene when needed" data-i18n data-i18n-title>NEXT</button>
</div>
<p class="scene-step-heading" data-i18n>SAVE &amp; SHARE</p>
<div class="scene-controls scene-file-controls">
+23
View File
@@ -3931,3 +3931,26 @@ test('analyst-only lookup distinguishes duplicate names and uses canonical aircr
assert.equal(plane.ok, true);
assert.equal(plane.latitude, 36, 'canonical ID resolves the current position, not old coordinates');
});
test('set_panel_open accepts the Authoring name for the scene panel', async () => {
const calls = [];
const runner = createGevActionRunner({
viewer: {
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: {
moveEnd: { addEventListener() {} },
positionWC: Cesium.Cartesian3.fromDegrees(-97.7, 30.2, 1000),
},
},
styleManager: {
setPanelCollapsed: (...args) => calls.push(args),
},
dataManager: { layers: new Map(), isEnabled: () => false, getAll: () => [] },
});
for (const panel of ['authoring', 'Authoring', 'scenes']) {
const result = await runner('set_panel_open', { panelId: panel, open: true });
assert.equal(result.panelId, 'scene-panel');
}
assert.deepEqual(calls.at(-1), ['scene-panel', false, { explicit: true }]);
});