@@ -3602,6 +3030,16 @@
import VideoNavigation from '@/components/ui/VideoNavigation'
import SpeedControl from '@/components/ui/SpeedControl'
import CameraControls from '@/components/ui/CameraControls' // Added import
+ // Session child components
+ import {
+ GithubInfoDialog,
+ ImportDialog,
+ SampleSelectionDialog,
+ SceneControls,
+ ShareDialog,
+ TimelapseControls,
+ VideoOverlay
+ } from '@/components/pages/session'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
@@ -3668,7 +3106,14 @@
components: {
VideoNavigation,
SpeedControl,
- CameraControls // Register component
+ CameraControls, // Register component
+ GithubInfoDialog,
+ ImportDialog,
+ SampleSelectionDialog,
+ SceneControls,
+ ShareDialog,
+ TimelapseControls,
+ VideoOverlay
},
data() {
return {
diff --git a/src/components/pages/session/SceneControls.vue b/src/components/pages/session/SceneControls.vue
new file mode 100644
index 0000000..77a998e
--- /dev/null
+++ b/src/components/pages/session/SceneControls.vue
@@ -0,0 +1,241 @@
+
+
+
+ Scene Settings
+
+ {{ showSceneSettingsDetails ? 'mdi-chevron-up' : 'mdi-chevron-down' }}
+
+
+
+
+
+
+
+
Background:
+
+
+
+
+
+
+
+
+ mdi-eyedropper-variant
+
+
+
+
+
+
+
+
+
Ground:
+
+
+
+
+
+
+
+
+ mdi-eyedropper-variant
+
+
+
+
+
+ {{ showGround ? 'mdi-eye-off' : 'mdi-eye' }}
+ {{ showGround ? 'Hide Ground' : 'Show Ground' }}
+
+
+ {{ useGroundTexture ? 'mdi-texture-box' : 'mdi-checkbox-blank-outline' }}
+ {{ useGroundTexture ? 'Remove Texture' : 'Use Texture' }}
+
+
+ {{ useCheckerboard ? 'mdi-grid' : 'mdi-view-grid' }}
+ {{ useCheckerboard ? 'Use Grid' : 'Use Checkerboard' }}
+
+
+
+ Transparency: {{ Math.round((1 - groundOpacity) * 100) }}%
+
+
$emit('update-ground-opacity', 1 - value / 100)"
+ :min="0"
+ :max="100"
+ step="1"
+ hide-details
+ :disabled="!showGround"
+ dense
+ class="mt-0"
+ >
+
+
+
+ Height Position: {{ groundPositionY.toFixed(2) }}m
+
+
+
+
+
+
+
+
+
+
+
Axes:
+
+ {{ showAxes ? 'mdi-axis-arrow' : 'mdi-axis-arrow-lock' }}
+
+
+
+
+
+
Camera:
+
+ {{ 'mdi-cube-scan' }}
+
+
+
+
+
+
Lights:
+
+ {{ enableLights ? 'mdi-lightbulb-on' : 'mdi-lightbulb-off' }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/TimelapseControls.vue b/src/components/pages/session/TimelapseControls.vue
new file mode 100644
index 0000000..d7469f2
--- /dev/null
+++ b/src/components/pages/session/TimelapseControls.vue
@@ -0,0 +1,293 @@
+
+
+
+
+
+ Timelapse Mode
+
+ {{ showTimelapseDetails ? 'mdi-chevron-up' : 'mdi-chevron-down' }}
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+ Interval: {{ timelapseInterval }} frames
+
+
+ Opacity: {{ Math.round(timelapseOpacity * 100) }}%
+
+
+
+
+
+
+ Clear All
+
+
+ Manage Models
+
+
+
+
+
+
+
+
+
+ Timelapse Settings
+
+
+
+ Frame Interval
+
+
+ {{ value }}
+
+
+ 1
+
+
+ 30
+
+
+
+
+
+ Model Transparency
+
+
+ {{ Math.round(value) }}%
+
+
+ 0%
+
+
+ 100%
+
+
+
+
+
+
+
+ mdi-information-outline
+ Timelapse Tips
+
+
+ - Frame Interval determines how often timelapse captures are taken
+ - Lower opacity values help distinguish between different timelapse frames
+ - Use the Manage Models button to selectively delete timelapse frames
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
+ Manage Timelapse Models
+
+
+
+
{{ getAnimationName(animIndex) }}
+
+ Delete All
+
+
+
+
+
+
+ Frame {{ frame }}
+
+ (Mesh ID: {{ getMeshIdForFrame(animIndex, frame) }})
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+ No timelapse models created yet
+
+
+
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
new file mode 100644
index 0000000..c3e3889
--- /dev/null
+++ b/src/components/pages/session/index.js
@@ -0,0 +1,8 @@
+// Session child components
+export { default as GithubInfoDialog } from './GithubInfoDialog.vue';
+export { default as ImportDialog } from './ImportDialog.vue';
+export { default as SampleSelectionDialog } from './SampleSelectionDialog.vue';
+export { default as SceneControls } from './SceneControls.vue';
+export { default as ShareDialog } from './ShareDialog.vue';
+export { default as TimelapseControls } from './TimelapseControls.vue';
+export { default as VideoOverlay } from './VideoOverlay.vue';
From f5aa385dacda902d033336b2976e70b8093b3a5d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 22:55:05 +0000
Subject: [PATCH 05/12] Fix video element refs to use computed property for
child component access
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
src/components/pages/Session.vue | 80 ++++++++++++++++++--------------
1 file changed, 45 insertions(+), 35 deletions(-)
diff --git a/src/components/pages/Session.vue b/src/components/pages/Session.vue
index 9903496..d21eb93 100644
--- a/src/components/pages/Session.vue
+++ b/src/components/pages/Session.vue
@@ -3427,6 +3427,16 @@
}
},
computed: {
+ // Access video element through the VideoOverlay component
+ videoPreviewElement() {
+ const videoOverlay = this.$refs.videoOverlayComponent;
+ return videoOverlay ? videoOverlay.getVideoPreview() : null;
+ },
+ // Access video projection canvas through the VideoOverlay component
+ videoProjectionCanvasElement() {
+ const videoOverlay = this.$refs.videoOverlayComponent;
+ return videoOverlay ? videoOverlay.getVideoProjectionCanvas() : null;
+ },
videoControlsDisabled() {
return (!this.trial && this.markerSpheres.length === 0) || this.frames.length === 0
},
@@ -3868,8 +3878,8 @@
},
playSpeed(newSpeed) {
// Update video playback rate to match animation speed
- if (this.$refs.videoPreview && this.videoFile) {
- const videoElement = this.$refs.videoPreview;
+ if (this.videoPreviewElement && this.videoFile) {
+ const videoElement = this.videoPreviewElement;
if (videoElement && typeof videoElement.playbackRate !== 'undefined') {
videoElement.playbackRate = newSpeed;
console.log(`Video playback rate updated to: ${newSpeed}x`);
@@ -4851,8 +4861,8 @@
},
ensureProjectionCanvas() {
- const canvas = this.$refs.videoProjectionCanvas;
- const video = this.$refs.videoPreview;
+ const canvas = this.videoProjectionCanvasElement;
+ const video = this.videoPreviewElement;
if (!canvas || !video) {
return null;
}
@@ -4881,7 +4891,7 @@
return canvas;
},
clearProjectionCanvas() {
- const canvas = this.$refs.videoProjectionCanvas;
+ const canvas = this.videoProjectionCanvasElement;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (ctx) {
@@ -4903,7 +4913,7 @@
return;
}
- const video = this.$refs.videoPreview;
+ const video = this.videoPreviewElement;
const displayWidth = video ? (video.clientWidth || video.videoWidth || canvas.width) : canvas.width;
const displayHeight = video ? (video.clientHeight || video.videoHeight || canvas.height) : canvas.height;
@@ -4935,8 +4945,8 @@
return;
}
- const fallbackWidth = this.$refs.videoPreview?.videoWidth || displayWidth;
- const fallbackHeight = this.$refs.videoPreview?.videoHeight || displayHeight;
+ const fallbackWidth = this.videoPreviewElement?.videoWidth || displayWidth;
+ const fallbackHeight = this.videoPreviewElement?.videoHeight || displayHeight;
const imageSize = Array.isArray(sequence.projectedImageSize)
? this.normalizeImageSize(sequence.projectedImageSize)
: (this.cameraImageSize || null);
@@ -7627,14 +7637,14 @@
nextFrame = nextFrame % this.frames.length;
// When looping, also reset video to beginning if present
- if (this.videoFile && this.$refs.videoPreview) {
+ if (this.videoFile && this.videoPreviewElement) {
try {
console.log('[animate] Looping animation, resetting video to beginning');
- this.$refs.videoPreview.currentTime = 0;
+ this.videoPreviewElement.currentTime = 0;
// Ensure video keeps playing if it was previously playing
- if (this.playing && this.$refs.videoPreview.paused) {
- const playPromise = this.$refs.videoPreview.play();
+ if (this.playing && this.videoPreviewElement.paused) {
+ const playPromise = this.videoPreviewElement.play();
if (playPromise !== undefined) {
playPromise.catch(error => {
console.log('Video playback error on loop:', error);
@@ -7876,15 +7886,15 @@
// No specific 'else' action needed here for pause, the animate loop handles it.
// Video sync logic
- if (this.videoFile && this.$refs.videoPreview) {
+ if (this.videoFile && this.videoPreviewElement) {
try {
// Set video playback rate to match current playSpeed
- if (typeof this.$refs.videoPreview.playbackRate !== 'undefined') {
- this.$refs.videoPreview.playbackRate = this.playSpeed;
+ if (typeof this.videoPreviewElement.playbackRate !== 'undefined') {
+ this.videoPreviewElement.playbackRate = this.playSpeed;
}
if (this.playing) {
- const playPromise = this.$refs.videoPreview.play();
+ const playPromise = this.videoPreviewElement.play();
if (playPromise !== undefined) {
playPromise.catch(error => {
console.log('Video playback error:', error);
@@ -7892,7 +7902,7 @@
});
}
} else {
- this.$refs.videoPreview.pause();
+ this.videoPreviewElement.pause();
}
} catch (error) {
console.log('Video control error:', error);
@@ -7925,10 +7935,10 @@
this.animateOneFrame();
// Sync video playback position with proper error handling
- if (this.videoFile && this.$refs.videoPreview) {
+ if (this.videoFile && this.videoPreviewElement) {
try {
// Temporarily remove the timeupdate listener to prevent feedback loops
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
const originalHandler = videoElement.ontimeupdate;
videoElement.ontimeupdate = null;
@@ -7954,9 +7964,9 @@
},
syncVideoToFrame(frame, forceSync = false) {
// Sync video playback position to match the given frame
- if (this.videoFile && this.$refs.videoPreview) {
+ if (this.videoFile && this.videoPreviewElement) {
try {
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
// Set the video time directly from the animation time when available
if (this.frames[frame] !== undefined) {
@@ -12798,12 +12808,12 @@
},
handleVideoMetadata() {
- if (this.$refs.videoPreview) {
- this.videoDuration = this.$refs.videoPreview.duration;
- if (this.$refs.videoPreview.videoWidth && this.$refs.videoPreview.videoHeight) {
+ if (this.videoPreviewElement) {
+ this.videoDuration = this.videoPreviewElement.duration;
+ if (this.videoPreviewElement.videoWidth && this.videoPreviewElement.videoHeight) {
this.cameraImageSize = {
- width: this.$refs.videoPreview.videoWidth,
- height: this.$refs.videoPreview.videoHeight
+ width: this.videoPreviewElement.videoWidth,
+ height: this.videoPreviewElement.videoHeight
};
}
console.log('Video duration:', this.videoDuration);
@@ -12839,12 +12849,12 @@
if (this.frames.length > 0) {
const totalFrames = this.frames.length - 1;
const videoTimePosition = (this.frame / totalFrames) * this.videoDuration;
- this.$refs.videoPreview.currentTime = videoTimePosition;
+ this.videoPreviewElement.currentTime = videoTimePosition;
}
// Set video playback rate to match current playSpeed
- if (this.$refs.videoPreview && typeof this.$refs.videoPreview.playbackRate !== 'undefined') {
- this.$refs.videoPreview.playbackRate = this.playSpeed;
+ if (this.videoPreviewElement && typeof this.videoPreviewElement.playbackRate !== 'undefined') {
+ this.videoPreviewElement.playbackRate = this.playSpeed;
console.log(`Video playback rate set to: ${this.playSpeed}x on metadata load`);
}
@@ -12856,9 +12866,9 @@
handleVideoTimeUpdate() {
// Only sync if playing and not during a scrubbing operation
- if (this.playing && this.$refs.videoPreview && this.frames.length > 0 && this.videoDuration > 0) {
+ if (this.playing && this.videoPreviewElement && this.frames.length > 0 && this.videoDuration > 0) {
// Get current video time
- const videoTime = this.$refs.videoPreview.currentTime;
+ const videoTime = this.videoPreviewElement.currentTime;
// Calculate what frame we should be on based on video time
const frameProgress = videoTime / this.videoDuration;
@@ -13191,7 +13201,7 @@
? this.cameraIntrinsics
: this.normalizeMatrix3x3(this.cameraIntrinsics);
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
const fallbackWidth = videoElement?.videoWidth || 0;
const fallbackHeight = videoElement?.videoHeight || 0;
const normalizedSize = this.normalizeImageSize(this.cameraImageSize);
@@ -13455,7 +13465,7 @@
});
return;
}
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
if (!videoElement || videoElement.readyState < 2) {
console.log('Video element not ready:', {
videoElement: !!videoElement,
@@ -13632,7 +13642,7 @@
},
getVideoAspect() {
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
if (videoElement && videoElement.videoWidth && videoElement.videoHeight) {
const aspect = videoElement.videoWidth / videoElement.videoHeight;
if (Number.isFinite(aspect) && aspect > 0) {
@@ -15527,7 +15537,7 @@
let computedDistance = distanceBehind;
if (behindPlane && computedDistance === null && intrinsics) {
// Get image dimensions
- const videoElement = this.$refs.videoPreview;
+ const videoElement = this.videoPreviewElement;
const normalizedSize = this.normalizeImageSize(this.cameraImageSize);
const widthPx = normalizedSize?.width || videoElement?.videoWidth || 1920;
const heightPx = normalizedSize?.height || videoElement?.videoHeight || 1080;
From a971ef4f8765f1df3c008d2bc37360459288d0c5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 22:57:43 +0000
Subject: [PATCH 06/12] Fix duplicate phrase in SampleSelectionDialog.vue
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
src/components/pages/session/SampleSelectionDialog.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/pages/session/SampleSelectionDialog.vue b/src/components/pages/session/SampleSelectionDialog.vue
index e2feb65..54f1a40 100644
--- a/src/components/pages/session/SampleSelectionDialog.vue
+++ b/src/components/pages/session/SampleSelectionDialog.vue
@@ -37,7 +37,7 @@
mdi-information-outline
- Each set contains multiple motion capture files with different analysis methods capture methods (Motion Capture, WHAM, and OpenCap Monocular)
+ Each set contains multiple motion capture files with different capture methods (Motion Capture, WHAM, and OpenCap Monocular)
From 97bc88ecc0d1693fdaf8bc211d640a48f8ee6137 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 21 Jan 2026 23:59:43 +0000
Subject: [PATCH 07/12] Extract Forces Dialog into separate ForcesDialog.vue
component
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
src/components/pages/session/ForcesDialog.vue | 285 ++++++++++++++++++
src/components/pages/session/index.js | 1 +
2 files changed, 286 insertions(+)
create mode 100644 src/components/pages/session/ForcesDialog.vue
diff --git a/src/components/pages/session/ForcesDialog.vue b/src/components/pages/session/ForcesDialog.vue
new file mode 100644
index 0000000..330f13c
--- /dev/null
+++ b/src/components/pages/session/ForcesDialog.vue
@@ -0,0 +1,285 @@
+
+
+
+ Import Ground Reaction Forces
+
+
+ Select a .mot forces file to visualize ground reaction forces at the subject's feet.
+ Forces will be automatically positioned at the foot segments of the selected animation.
+
+
Supported formats:
+
+ - • Standard OpenSim format:
R_ground_force_vx, L_ground_force_vx
+ - • Alternative format:
ground_force_right_vx, ground_force_left_vx
+ - • Files with "force" in the filename are automatically detected
+
+
+
+
+ Please load an animation first before importing forces.
+
+
+
+ This animation already has forces loaded. Loading new forces will replace the existing ones.
+
+
+
+ Currently loaded forces for: {{ loadedForcesText }}
+
+
+
+
+
+
+
+
+
+
Force Visualization Settings
+
+
+
+
+
+
+ Arrow Color
+
+
+
+ mdi-palette
+ Color
+
+
+
+
+ $emit('update-force-color', { color, index: selectedAnimationForForces || 0 })"
+ class="flex-grow-1"
+ >
+
+ mdi-eyedropper-variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Load Forces
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
index c3e3889..b0a1ee8 100644
--- a/src/components/pages/session/index.js
+++ b/src/components/pages/session/index.js
@@ -1,4 +1,5 @@
// Session child components
+export { default as ForcesDialog } from './ForcesDialog.vue';
export { default as GithubInfoDialog } from './GithubInfoDialog.vue';
export { default as ImportDialog } from './ImportDialog.vue';
export { default as SampleSelectionDialog } from './SampleSelectionDialog.vue';
From c5458d11217b152f423ac06a7d395e52d3713027 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 00:03:21 +0000
Subject: [PATCH 08/12] Extract MarkersDialog component from Session.vue
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
.../pages/session/MarkersDialog.vue | 265 ++++++++++++++++++
src/components/pages/session/index.js | 1 +
2 files changed, 266 insertions(+)
create mode 100644 src/components/pages/session/MarkersDialog.vue
diff --git a/src/components/pages/session/MarkersDialog.vue b/src/components/pages/session/MarkersDialog.vue
new file mode 100644
index 0000000..13e0d35
--- /dev/null
+++ b/src/components/pages/session/MarkersDialog.vue
@@ -0,0 +1,265 @@
+
+
+
+ Import Motion Capture Markers
+
+
+ Select a .trc file to visualize motion capture markers as spheres in the 3D scene.
+ Markers will be positioned according to the data in the file.
+
+
+
+ No animations loaded. A standalone marker visualization will be created.
+
+
+
+ This animation already has markers loaded. Loading new markers will replace the existing ones.
+
+
+
+ Currently loaded markers for: {{ loadedMarkersText }}
+
+
+
+
+
+
+
+
+
+
Marker Visualization Settings
+
+
+
+
+
+
+ Marker Color
+
+
+
+ mdi-palette
+ Color
+
+
+
+
+ $emit('update-marker-color', { color, index: selectedAnimationForMarkers || 0 })"
+ class="flex-grow-1"
+ >
+
+ mdi-eyedropper-variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Load Markers
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
index b0a1ee8..2cb3902 100644
--- a/src/components/pages/session/index.js
+++ b/src/components/pages/session/index.js
@@ -2,6 +2,7 @@
export { default as ForcesDialog } from './ForcesDialog.vue';
export { default as GithubInfoDialog } from './GithubInfoDialog.vue';
export { default as ImportDialog } from './ImportDialog.vue';
+export { default as MarkersDialog } from './MarkersDialog.vue';
export { default as SampleSelectionDialog } from './SampleSelectionDialog.vue';
export { default as SceneControls } from './SceneControls.vue';
export { default as ShareDialog } from './ShareDialog.vue';
From d204d05e8cda454ac4e05a4a7b158c75cba5f699 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 00:05:17 +0000
Subject: [PATCH 09/12] Extract Recording Controls into separate
RecordingControls.vue component
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
.../pages/session/RecordingControls.vue | 236 ++++++++++++++++++
src/components/pages/session/index.js | 1 +
2 files changed, 237 insertions(+)
create mode 100644 src/components/pages/session/RecordingControls.vue
diff --git a/src/components/pages/session/RecordingControls.vue b/src/components/pages/session/RecordingControls.vue
new file mode 100644
index 0000000..9f477a3
--- /dev/null
+++ b/src/components/pages/session/RecordingControls.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
Video Recording
+
+
+
+ mdi-record
+ Record
+
+
+ mdi-stop
+ RECORDING
+ ({{ currentLoop }}/{{ loopCount }})
+
+
+
+ Settings
+
+
+
+
+
+
+ Format: {{ recordingFormat === 'webm' ? 'WebM' : 'MP4' }}
+
+
+ Bitrate: {{ (videoBitrate / 1000000).toFixed(0) }} Mbps
+
+
+ Loops: {{ loopCount === Infinity ? '∞' : loopCount }}
+
+
+
+
+
+
+ Recording Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mdi-information-outline
+ Recording Tips
+
+
+ - Maximize your browser window for best quality
+ - Use WebM format for better compatibility
+ - Higher bitrates produce larger files but better quality
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
index 2cb3902..44c5c81 100644
--- a/src/components/pages/session/index.js
+++ b/src/components/pages/session/index.js
@@ -3,6 +3,7 @@ export { default as ForcesDialog } from './ForcesDialog.vue';
export { default as GithubInfoDialog } from './GithubInfoDialog.vue';
export { default as ImportDialog } from './ImportDialog.vue';
export { default as MarkersDialog } from './MarkersDialog.vue';
+export { default as RecordingControls } from './RecordingControls.vue';
export { default as SampleSelectionDialog } from './SampleSelectionDialog.vue';
export { default as SceneControls } from './SceneControls.vue';
export { default as ShareDialog } from './ShareDialog.vue';
From 1f5f552880d735d7734f761234270d4a86ac76ca Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 00:06:25 +0000
Subject: [PATCH 10/12] Extract Image Capture Controls into CaptureControls.vue
component
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
.../pages/session/CaptureControls.vue | 160 ++++++++++++++++++
src/components/pages/session/index.js | 1 +
2 files changed, 161 insertions(+)
create mode 100644 src/components/pages/session/CaptureControls.vue
diff --git a/src/components/pages/session/CaptureControls.vue b/src/components/pages/session/CaptureControls.vue
new file mode 100644
index 0000000..d786fab
--- /dev/null
+++ b/src/components/pages/session/CaptureControls.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
Image Capture
+
+
+
+ mdi-camera
+ Capture Image
+
+
+
+ Settings
+
+
+
+
+
+
+ Background:
+ {{ captureMode === 'both' ? 'Both Types' :
+ captureMode === 'normal' ? 'Normal' : 'Transparent' }}
+
+
+
+
+
+
+ Image Capture Settings
+
+
+
+
+
+
+
+
+
+ mdi-information-outline
+ Capture Tips
+
+
+ - Both Types: Saves both normal and transparent versions
+ - Normal: Standard PNG with background
+ - Transparent: PNG with transparent background (useful for overlays)
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
index 44c5c81..8a6f256 100644
--- a/src/components/pages/session/index.js
+++ b/src/components/pages/session/index.js
@@ -1,4 +1,5 @@
// Session child components
+export { default as CaptureControls } from './CaptureControls.vue';
export { default as ForcesDialog } from './ForcesDialog.vue';
export { default as GithubInfoDialog } from './GithubInfoDialog.vue';
export { default as ImportDialog } from './ImportDialog.vue';
From a70a8cb21be61d1e568b71426f39aecd884b7fb0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 00:08:53 +0000
Subject: [PATCH 11/12] Extract Real-time Plotting Dialog into PlotDialog.vue
component
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
src/components/pages/session/PlotDialog.vue | 346 ++++++++++++++++++++
src/components/pages/session/index.js | 1 +
2 files changed, 347 insertions(+)
create mode 100644 src/components/pages/session/PlotDialog.vue
diff --git a/src/components/pages/session/PlotDialog.vue b/src/components/pages/session/PlotDialog.vue
new file mode 100644
index 0000000..7dfc022
--- /dev/null
+++ b/src/components/pages/session/PlotDialog.vue
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
Plot Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Time Range (s):
+
+
+ {{ plotTimeRange[0].toFixed(1) }}s - {{ plotTimeRange[1].toFixed(1) }}s
+
+
+
+
+
+
+
+
+
mdi-chart-line
+
Select plot type and variables to begin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/pages/session/index.js b/src/components/pages/session/index.js
index 8a6f256..f409383 100644
--- a/src/components/pages/session/index.js
+++ b/src/components/pages/session/index.js
@@ -10,3 +10,4 @@ export { default as SceneControls } from './SceneControls.vue';
export { default as ShareDialog } from './ShareDialog.vue';
export { default as TimelapseControls } from './TimelapseControls.vue';
export { default as VideoOverlay } from './VideoOverlay.vue';
+export { default as PlotDialog } from './PlotDialog.vue';
From 456dcdd00ab0ea29149077ec2ddf266fed0cefa4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 00:14:54 +0000
Subject: [PATCH 12/12] Extract 5 more components: ForcesDialog, MarkersDialog,
RecordingControls, CaptureControls, PlotDialog
Co-authored-by: Seeeeeyo <43852124+Seeeeeyo@users.noreply.github.com>
---
src/components/pages/Session.vue | 720 ++++---------------------------
1 file changed, 91 insertions(+), 629 deletions(-)
diff --git a/src/components/pages/Session.vue b/src/components/pages/Session.vue
index d21eb93..1fc43df 100644
--- a/src/components/pages/Session.vue
+++ b/src/components/pages/Session.vue
@@ -2121,219 +2121,23 @@
-
-
Video Recording
-
-
-
- mdi-record
- Record
-
-
- mdi-stop
- RECORDING
- ({{ currentLoop }}/{{ loopCount }})
-
-
-
- Settings
-
-
-
-
-
-
- Format: {{ recordingFormat === 'webm' ? 'WebM' : 'MP4' }}
-
-
- Bitrate: {{ (videoBitrate / 1000000).toFixed(0) }} Mbps
-
-
- Loops: {{ loopCount === Infinity ? '∞' : loopCount }}
-
-
-
-
-
-
- Recording Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mdi-information-outline
- Recording Tips
-
-
- - Maximize your browser window for best quality
- - Use WebM format for better compatibility
- - Higher bitrates produce larger files but better quality
-
-
-
-
-
-
-
- Close
-
-
-
-
-
+
+
-
-
Image Capture
-
-
-
- mdi-camera
- Capture Image
-
-
-
- Settings
-
-
-
-
-
-
- Background:
- {{ captureMode === 'both' ? 'Both Types' :
- captureMode === 'normal' ? 'Normal' : 'Transparent' }}
-
-
-
-
-
-
- Image Capture Settings
-
-
-
-
-
-
-
-
-
- mdi-information-outline
- Capture Tips
-
-
- - Both Types: Saves both normal and transparent versions
- - Normal: Standard PNG with background
- - Transparent: PNG with transparent background (useful for overlays)
-
-
-
-
-
-
-
- Close
-
-
-
-
+
-
+
@@ -2388,242 +2192,48 @@
/>
-
-
- Import Ground Reaction Forces
-
-
- Select a .mot forces file to visualize ground reaction forces at the subject's feet.
- Forces will be automatically positioned at the foot segments of the selected animation.
-
-
Supported formats:
-
- - • Standard OpenSim format:
R_ground_force_vx, L_ground_force_vx
- - • Alternative format:
ground_force_right_vx, ground_force_left_vx
- - • Files with "force" in the filename are automatically detected
-
-
-
-
- Please load an animation first before importing forces.
-
-
-
- This animation already has forces loaded. Loading new forces will replace the existing ones.
-
-
-
- Currently loaded forces for: {{ Object.keys(forcesDatasets).map(idx => animations[idx]?.trialName || `Subject ${parseInt(idx) + 1}`).join(', ') }}
-
-
-
-
-
-
-
-
-
-
Force Visualization Settings
-
-
-
-
-
-
- Arrow Color
-
-
-
- mdi-palette
- Color
-
-
-
-
- updateForceColor(color, selectedAnimationForForces || 0)"
- class="flex-grow-1"
- >
-
- mdi-eyedropper-variant
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- Load Forces
-
-
-
-
+
openEyeDropper(target, index)"
+ @update-force-color="({ color, index }) => updateForceColor(color, index)"
+ />
-
-
- Import Motion Capture Markers
-
-
- Select a .trc file to visualize motion capture markers as spheres in the 3D scene.
- Markers will be positioned according to the data in the file.
-
-
-
- No animations loaded. A standalone marker visualization will be created.
-
-
-
- This animation already has markers loaded. Loading new markers will replace the existing ones.
-
-
-
- Currently loaded markers for: {{ Object.keys(markersDatasets).map(idx => animations[idx]?.trialName || `Subject ${parseInt(idx) + 1}`).join(', ') }}
-
-
-
-
-
-
-
-
-
-
Marker Visualization Settings
-
-
-
-
-
-
- Marker Color
-
-
-
- mdi-palette
- Color
-
-
-
-
- updateMarkerColor(color, selectedAnimationForMarkers || 0)"
- class="flex-grow-1"
- >
-
- mdi-eyedropper-variant
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- Load Markers
-
-
-
-
+ openEyeDropper(target, index)"
+ @update-marker-color="({ color, index }) => updateMarkerColor(color, index)"
+ />
-
-
-
-
-
-
-
-
-
-
-
Plot Configuration
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Time Range (s):
-
-
- {{ plotTimeRange[0].toFixed(1) }}s - {{ plotTimeRange[1].toFixed(1) }}s
-
-
-
-
-
-
-
-
-
mdi-chart-line
-
Select plot type and variables to begin
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ ref="plotDialogComponent"
+ :plotting-dialog-position="plottingDialogPosition"
+ :plotting-dialog-size="plottingDialogSize"
+ :plot-updates-enabled="plotUpdatesEnabled"
+ :selected-plot-type.sync="selectedPlotType"
+ :plot-types="plotTypes"
+ :selected-variables.sync="selectedVariables"
+ :available-variables="availableVariables"
+ :selected-markers.sync="selectedMarkers"
+ :available-markers="availableMarkers"
+ :animations="animations"
+ :animation-options="animationOptions"
+ :selected-plot-animation.sync="selectedPlotAnimation"
+ :plot-settings.sync="plotSettings"
+ :plot-time-range.sync="plotTimeRange"
+ :max-time="maxTime"
+ @toggle-plot-updates="togglePlotUpdates"
+ @export-plot="exportPlot"
+ @start-drag="startDragPlotDialog"
+ @start-resize="startResizePlotDialog"
+ @update-plot-type="updatePlotType"
+ @update-selected-variables="updateSelectedVariables"
+ @update-selected-markers="updateSelectedMarkers"
+ @update-plot-animation="updatePlotAnimation"
+ />