feat(control-station): add atomic recorded-session playback

This commit is contained in:
DCCONSTRUCTIONS
2026-07-17 17:51:24 +03:00
parent 007ff9fff2
commit e94c64eebd
35 changed files with 8907 additions and 302 deletions
@@ -0,0 +1,233 @@
.observation-header-tools {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.65rem;
}
.workspace-layout-feedback {
display: inline-flex;
max-width: 15rem;
align-items: center;
gap: 0.45rem;
color: var(--nodedc-text-muted);
font-size: var(--nodedc-font-size-xs);
line-height: 1.2;
}
.workspace-layout-feedback[data-error="true"] {
color: rgb(var(--nodedc-danger-rgb));
}
.observation-session-select__trigger {
display: inline-flex;
min-width: 15rem;
height: 2.75rem;
align-items: center;
justify-content: flex-start;
gap: 0.55rem;
border: 0;
border-radius: 999px;
background: rgb(255 255 255 / 0.055);
color: var(--nodedc-text-secondary);
padding: 0 0.85rem;
font: inherit;
font-size: var(--nodedc-font-size-sm);
font-weight: var(--nodedc-font-weight-strong);
cursor: pointer;
}
.observation-session-select__trigger:hover,
.observation-session-select__trigger[data-active="true"] {
background: rgb(255 255 255 / 0.095);
color: var(--nodedc-text-primary);
}
.observation-session-select__trigger > span {
min-width: 0;
flex: 1 1 auto;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
}
.observation-session-select__trigger > small {
display: grid;
min-width: 1.35rem;
height: 1.35rem;
place-items: center;
border-radius: 999px;
background: rgb(255 255 255 / 0.08);
color: var(--nodedc-text-muted);
font-size: var(--nodedc-font-size-xs);
}
.observation-session-menu {
overflow: hidden;
font-family: var(--nodedc-font-family);
}
.observation-session-menu__content,
.observation-session-menu__list {
display: grid;
}
.observation-session-menu__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.85rem 0.9rem 0.6rem;
}
.observation-session-menu__head span,
.observation-session-menu__head strong {
display: block;
}
.observation-session-menu__head strong {
margin-top: 0.3rem;
color: var(--nodedc-text-primary);
font-size: var(--nodedc-font-size-md);
}
.observation-session-menu__head button {
display: grid;
width: 2rem;
height: 2rem;
place-items: center;
border: 0;
border-radius: 50%;
background: rgb(255 255 255 / 0.055);
color: var(--nodedc-text-secondary);
cursor: pointer;
}
.observation-session-menu__head button:hover {
background: rgb(255 255 255 / 0.1);
color: var(--nodedc-text-primary);
}
.observation-session-menu__list {
max-height: min(26rem, 60vh);
gap: 0.2rem;
overflow: auto;
padding: 0.35rem;
}
.observation-session-option {
grid-template-columns: auto minmax(0, 1fr) auto;
}
.observation-session-option i {
display: block;
width: 0.45rem;
height: 0.45rem;
border-radius: 50%;
background: var(--nodedc-text-muted);
}
.observation-session-option i[data-session-visual-state="ready"],
.observation-session-option i[data-session-visual-state="processing"] {
background: rgb(var(--nodedc-success-rgb));
}
.observation-session-option i[data-session-visual-state="processing"] {
animation: observation-session-processing 1.1s ease-in-out infinite;
}
.observation-session-option i[data-session-visual-state="error"] {
background: var(--nodedc-text-muted);
opacity: 0.48;
}
@keyframes observation-session-processing {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.observation-session-option i[data-session-visual-state="processing"] {
animation: none;
opacity: 0.72;
}
}
.observation-session-option__state {
max-width: 9.5rem;
overflow: hidden;
color: var(--nodedc-text-muted);
font-size: var(--nodedc-font-size-xs);
font-weight: var(--nodedc-font-weight-medium);
text-overflow: ellipsis;
white-space: nowrap;
}
.observation-session-menu__empty {
display: grid;
min-height: 9rem;
place-items: center;
align-content: center;
gap: 0.5rem;
padding: 1rem;
color: var(--nodedc-text-muted);
text-align: center;
}
.observation-session-menu__empty strong {
color: var(--nodedc-text-secondary);
font-size: var(--nodedc-font-size-sm);
}
.observation-session-menu__empty span {
max-width: 24rem;
font-size: var(--nodedc-font-size-xs);
line-height: 1.45;
}
.observation-session-menu__error {
display: flex;
align-items: flex-start;
gap: 0.45rem;
padding: 0.65rem 0.9rem 0.8rem;
color: rgb(var(--nodedc-danger-rgb));
font-size: var(--nodedc-font-size-xs);
line-height: 1.4;
}
.observation-session-menu__error > span {
min-width: 0;
flex: 1 1 auto;
}
.observation-session-menu__error button {
flex: 0 0 auto;
border: 0;
border-radius: 999px;
background: rgb(255 255 255 / 0.08);
color: var(--nodedc-text-primary);
padding: 0.4rem 0.65rem;
font: inherit;
font-weight: var(--nodedc-font-weight-strong);
cursor: pointer;
}
@media (max-width: 860px) {
.workspace-layout-feedback {
display: none;
}
.observation-session-select__trigger {
min-width: 2.75rem;
width: 2.75rem;
padding: 0;
justify-content: center;
}
.observation-session-select__trigger > span,
.observation-session-select__trigger > small,
.observation-session-select__trigger > svg:last-child {
display: none;
}
}
+100 -16
View File
@@ -191,6 +191,32 @@ i[data-availability="error"] {
background: #050608;
}
.recorded-media-player {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #070809;
}
.recorded-media-player:not([data-state="ready"]) .observation-media__asset {
visibility: hidden;
}
.recorded-media-player__notice {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 18px;
color: rgba(247, 248, 244, 0.72);
background: #070809;
font: inherit;
font-size: 11px;
text-align: center;
pointer-events: none;
}
.mse-fmp4-player {
position: relative;
width: 100%;
@@ -316,11 +342,8 @@ i[data-availability="error"] {
}
.observation-timeline {
display: grid;
display: block;
min-width: 0;
grid-template-columns: auto auto minmax(0, 1fr) auto auto;
align-items: center;
gap: 0.6rem;
border: 0;
border-radius: 0.9rem;
background: rgb(9 10 13 / 0.78);
@@ -328,22 +351,73 @@ i[data-availability="error"] {
backdrop-filter: blur(16px);
}
.observation-timeline__track {
height: 0.3rem;
overflow: hidden;
border-radius: 999px;
background: rgb(255 255 255 / 0.08);
.observation-timeline[data-accumulation="true"] {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: center;
gap: 0.8rem;
}
.observation-timeline__track span {
display: block;
height: 100%;
border-radius: inherit;
.observation-timeline__playback {
display: grid;
min-width: 0;
grid-template-columns: auto auto minmax(0, 1fr) auto auto;
align-items: center;
gap: 0.6rem;
}
.observation-timeline__accumulation {
display: grid;
min-width: 0;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 0.7rem;
padding: 0 0.45rem;
}
.observation-timeline__accumulation span,
.observation-timeline__accumulation code {
color: var(--nodedc-text-muted);
font-size: 0.55rem;
white-space: nowrap;
}
.observation-timeline__accumulation code {
min-width: 2.65rem;
color: var(--nodedc-text-primary);
text-align: right;
}
.observation-timeline__track {
width: 100%;
height: 0.3rem;
appearance: none;
border: 0;
border-radius: 999px;
background: rgb(255 255 255 / 0.08);
cursor: pointer;
}
.observation-timeline__track::-webkit-slider-thumb {
width: 0.72rem;
height: 0.72rem;
appearance: none;
border: 0;
border-radius: 50%;
background: var(--nodedc-text-primary);
}
.observation-timeline__track[data-disabled="true"] {
.observation-timeline__track::-moz-range-thumb {
width: 0.72rem;
height: 0.72rem;
border: 0;
border-radius: 50%;
background: var(--nodedc-text-primary);
}
.observation-timeline__track:disabled {
opacity: 0.46;
cursor: default;
}
.observation-timeline__meta {
@@ -364,11 +438,17 @@ i[data-availability="error"] {
align-items: center;
gap: 0.38rem;
color: var(--nodedc-text-muted);
border: 0;
background: transparent;
padding: 0.3rem 0.45rem;
font-size: 0.52rem;
font-weight: 820;
}
.observation-timeline__follow:not(:disabled) {
cursor: pointer;
}
.observation-timeline__follow::before {
width: 0.4rem;
height: 0.4rem;
@@ -514,11 +594,15 @@ i[data-availability="error"] {
left: 0.6rem;
}
.observation-timeline {
.observation-timeline[data-accumulation="true"] {
grid-template-columns: minmax(0, 1fr);
}
.observation-timeline__playback {
grid-template-columns: auto minmax(0, 1fr) auto;
}
.observation-timeline > .nodedc-button:first-child,
.observation-timeline__playback > .nodedc-button:first-child,
.observation-timeline__meta {
display: none;
}
@@ -104,7 +104,6 @@
}
@media (max-width: 760px) {
.station-label,
.control-station .nodedc-header__profile-button {
display: none;
}
-10
View File
@@ -1,13 +1,3 @@
.station-label {
overflow: hidden;
color: var(--nodedc-text-muted);
font-size: 0.61rem;
font-weight: 820;
letter-spacing: 0.13em;
text-overflow: ellipsis;
white-space: nowrap;
}
.api-dot {
width: 0.48rem;
height: 0.48rem;
+24 -6
View File
@@ -45,17 +45,34 @@
min-height: 0;
}
.rerun-viewport__canvas[data-presented="false"] {
visibility: hidden;
}
.recorded-session-preloaders {
position: fixed;
left: -10000px;
top: -10000px;
width: 1px;
height: 1px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.rerun-viewport {
--rerun-tab-bar-height: 24px;
--rerun-native-chrome-height: 72px;
overflow: hidden;
}
/* Rerun WebViewer 0.34.1 draws its selected recording tab inside WASM.
The native panels are already disabled; crop only that fixed tab row. */
/* Rerun WebViewer 0.34.1 keeps three fixed 24px canvas rows even after its
panels are overridden: the native top row, recording tab and view tab.
They are drawn inside WASM and cannot be styled independently, so crop the
fixed native chrome while keeping the actual 3D viewport full-height. */
.rerun-viewport__canvas {
top: calc(-1 * var(--rerun-tab-bar-height));
top: calc(-1 * var(--rerun-native-chrome-height));
bottom: auto;
height: calc(100% + var(--rerun-tab-bar-height));
height: calc(100% + var(--rerun-native-chrome-height));
}
.rerun-viewport__canvas canvas {
@@ -65,8 +82,9 @@
}
.rerun-viewport:is([data-status="loading"], [data-status="error"])
.rerun-viewport__canvas > div {
.rerun-viewport__canvas {
visibility: hidden;
pointer-events: none;
}
.rerun-viewport__notice {