feat(k1): add live cameras and reliable spatial following
This commit is contained in:
@@ -328,6 +328,7 @@ function SpatialWorkspace({
|
||||
{sourceUrl.trim() && pointCloudVisible ? (
|
||||
<RerunViewport
|
||||
sourceUrl={sourceUrl}
|
||||
followLive={state?.sourceMode === "live"}
|
||||
onStatusChange={onStatusChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
/>
|
||||
@@ -349,6 +350,7 @@ function SpatialWorkspace({
|
||||
<ObservationSourcePicker
|
||||
sources={observationSources}
|
||||
visibleSourceIds={observationLayout.visibleSourceIds}
|
||||
pendingSourceIds={observationLayout.pendingSourceIds}
|
||||
onToggle={observationLayout.toggleSource}
|
||||
/>
|
||||
{pointCloudSource?.capabilities.fullscreen && pointCloudVisible && sourceUrl.trim() ? (
|
||||
@@ -434,7 +436,7 @@ function SpatialWorkspace({
|
||||
onMaximizedChange={(maximized) =>
|
||||
observationLayout.setFloatingMaximized(source.id, maximized)}
|
||||
onActivate={() => observationLayout.activateFloatingSource(source.id)}
|
||||
onClose={() => observationLayout.hideSource(source.id)}
|
||||
onClose={() => void observationLayout.hideSource(source.id)}
|
||||
/>
|
||||
)) : null}
|
||||
</div>
|
||||
@@ -454,12 +456,41 @@ function SpatialWorkspace({
|
||||
function CameraSourceCard({
|
||||
source,
|
||||
focused,
|
||||
visible,
|
||||
pending,
|
||||
selectedPeer,
|
||||
onToggle,
|
||||
onFocus,
|
||||
}: {
|
||||
source: ObservationSourceDescriptor;
|
||||
focused: boolean;
|
||||
visible: boolean;
|
||||
pending: boolean;
|
||||
selectedPeer: boolean;
|
||||
onToggle: () => void;
|
||||
onFocus: () => void;
|
||||
}) {
|
||||
const deliveryActive = Boolean(
|
||||
(source.delivery || source.previewUrl) &&
|
||||
(!source.activation || source.activation.selected),
|
||||
);
|
||||
const restartAvailable = Boolean(
|
||||
source.activation?.selected &&
|
||||
source.activation.controllable &&
|
||||
(source.availability === "error" || !source.delivery),
|
||||
);
|
||||
const activationLabel = pending
|
||||
? "Переключение…"
|
||||
: restartAvailable
|
||||
? "Повторить подключение"
|
||||
: source.activation?.selected && visible
|
||||
? "Закрыть канал"
|
||||
: source.activation?.selected
|
||||
? "Показать канал"
|
||||
: selectedPeer
|
||||
? "Переключить"
|
||||
: "Открыть канал";
|
||||
|
||||
return (
|
||||
<article className="camera-slot" data-focused={focused ? "true" : undefined}>
|
||||
<header>
|
||||
@@ -469,7 +500,17 @@ function CameraSourceCard({
|
||||
<i data-availability={source.availability} aria-hidden="true" />
|
||||
{observationSourceStatusLabel(source)}
|
||||
</span>
|
||||
{source.capabilities.fullscreen ? (
|
||||
{source.activation ? (
|
||||
<button
|
||||
type="button"
|
||||
className="camera-slot__activation"
|
||||
disabled={pending || (!source.activation.selected && !source.activation.controllable)}
|
||||
onClick={onToggle}
|
||||
>
|
||||
{activationLabel}
|
||||
</button>
|
||||
) : null}
|
||||
{source.capabilities.fullscreen && deliveryActive ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={focused ? `Свернуть ${source.label}` : `Развернуть ${source.label}`}
|
||||
@@ -510,6 +551,15 @@ function CamerasWorkspace({ definition, state, observationLayout }: WorkspaceRen
|
||||
key={source.id}
|
||||
source={source}
|
||||
focused={focusedSource?.id === source.id}
|
||||
visible={observationLayout.visibleSourceIds.has(source.id)}
|
||||
pending={observationLayout.pendingSourceIds.has(source.id)}
|
||||
selectedPeer={Boolean(
|
||||
source.activation && sources.some((candidate) =>
|
||||
candidate.id !== source.id &&
|
||||
candidate.activation?.groupId === source.activation?.groupId &&
|
||||
candidate.activation?.selected === true),
|
||||
)}
|
||||
onToggle={() => void observationLayout.toggleSource(source.id)}
|
||||
onFocus={() => observationLayout.setFocusedSourceId(
|
||||
focusedSource?.id === source.id ? null : source.id,
|
||||
)}
|
||||
@@ -523,8 +573,11 @@ function CamerasWorkspace({ definition, state, observationLayout }: WorkspaceRen
|
||||
)}
|
||||
</div>
|
||||
<ObservationTimeline
|
||||
active={sources.some((source) => source.availability === "streaming")}
|
||||
sourceCount={sources.length}
|
||||
active={sources.some((source) =>
|
||||
source.availability === "streaming" &&
|
||||
(!source.activation || source.activation.selected))}
|
||||
sourceCount={sources.filter((source) =>
|
||||
!source.activation || source.activation.selected).length}
|
||||
mode={timeline?.mode}
|
||||
seekable={timeline?.seekable}
|
||||
synchronization={timeline?.synchronization}
|
||||
|
||||
Reference in New Issue
Block a user