mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49:42 +02:00
fix: image watcher now uses relative path for subdirectory images
- Added relativePath field to ImageDetectedEvent - Compute relative path from working directory in image-watcher - Use relativePath in frontend URL (fixes 'Failed to load image' for subdirs) - Minor CLAUDE.md improvements chore: bump version to 0.1461
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { watch, type FSWatcher } from 'chokidar';
|
||||
import { basename, extname } from 'node:path';
|
||||
import { basename, extname, relative } from 'node:path';
|
||||
import { statSync } from 'node:fs';
|
||||
import type { ImageDetectedEvent } from './types.js';
|
||||
|
||||
@@ -265,10 +265,14 @@ export class ImageWatcher extends EventEmitter {
|
||||
try {
|
||||
const stat = statSync(filePath);
|
||||
const fileName = basename(filePath);
|
||||
const workingDir = this.sessionDirs.get(sessionId);
|
||||
// Compute relative path from working directory (for file-raw endpoint)
|
||||
const relativePath = workingDir ? relative(workingDir, filePath) : fileName;
|
||||
|
||||
const event: ImageDetectedEvent = {
|
||||
sessionId,
|
||||
filePath,
|
||||
relativePath,
|
||||
fileName,
|
||||
timestamp: Date.now(),
|
||||
size: stat.size,
|
||||
|
||||
@@ -1511,6 +1511,8 @@ export interface ImageDetectedEvent {
|
||||
sessionId: string;
|
||||
/** Full path to the detected image file */
|
||||
filePath: string;
|
||||
/** Path relative to the session's working directory (for file-raw endpoint) */
|
||||
relativePath: string;
|
||||
/** Image file name (basename) */
|
||||
fileName: string;
|
||||
/** Timestamp when the image was detected */
|
||||
|
||||
+56
-8
@@ -185,18 +185,20 @@ function getEventCoords(e) {
|
||||
/**
|
||||
* KeyboardHandler - Simple handler to scroll inputs into view when keyboard appears.
|
||||
* Uses focusin event and scrollIntoView - keeps it simple and reliable.
|
||||
* Also handles terminal scrolling via visualViewport API.
|
||||
* Also handles terminal scrolling and toolbar repositioning via visualViewport API.
|
||||
*/
|
||||
const KeyboardHandler = {
|
||||
lastViewportHeight: 0,
|
||||
keyboardVisible: false,
|
||||
initialViewportHeight: 0,
|
||||
|
||||
/** Initialize keyboard handling */
|
||||
init() {
|
||||
// Only initialize on touch devices
|
||||
if (!MobileDetection.isTouchDevice()) return;
|
||||
|
||||
this.lastViewportHeight = window.visualViewport?.height || window.innerHeight;
|
||||
this.initialViewportHeight = window.visualViewport?.height || window.innerHeight;
|
||||
this.lastViewportHeight = this.initialViewportHeight;
|
||||
|
||||
// Simple focus handler - scroll input into view after keyboard appears
|
||||
document.addEventListener('focusin', (e) => {
|
||||
@@ -209,32 +211,68 @@ const KeyboardHandler = {
|
||||
}, 400);
|
||||
});
|
||||
|
||||
// Use visualViewport to detect keyboard for terminal scrolling
|
||||
// Use visualViewport to detect keyboard and reposition toolbar
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener('resize', () => {
|
||||
this.handleViewportResize();
|
||||
});
|
||||
// Also handle scroll (iOS scrolls viewport when keyboard appears)
|
||||
window.visualViewport.addEventListener('scroll', () => {
|
||||
this.updateToolbarPosition();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** Handle viewport resize (keyboard show/hide) */
|
||||
handleViewportResize() {
|
||||
const currentHeight = window.visualViewport?.height || window.innerHeight;
|
||||
const heightDiff = this.lastViewportHeight - currentHeight;
|
||||
const heightDiff = this.initialViewportHeight - currentHeight;
|
||||
|
||||
// Keyboard appeared (viewport shrunk by more than 150px)
|
||||
if (heightDiff > 150 && !this.keyboardVisible) {
|
||||
this.keyboardVisible = true;
|
||||
document.body.classList.add('keyboard-visible');
|
||||
this.onKeyboardShow();
|
||||
}
|
||||
// Keyboard hidden (viewport grew back)
|
||||
else if (heightDiff < -150 && this.keyboardVisible) {
|
||||
// Keyboard hidden (viewport grew back close to initial)
|
||||
else if (heightDiff < 50 && this.keyboardVisible) {
|
||||
this.keyboardVisible = false;
|
||||
document.body.classList.remove('keyboard-visible');
|
||||
this.onKeyboardHide();
|
||||
}
|
||||
|
||||
this.updateToolbarPosition();
|
||||
this.lastViewportHeight = currentHeight;
|
||||
},
|
||||
|
||||
/** Update toolbar position based on visual viewport */
|
||||
updateToolbarPosition() {
|
||||
if (!window.visualViewport) return;
|
||||
|
||||
const toolbar = document.querySelector('.toolbar');
|
||||
if (!toolbar) return;
|
||||
|
||||
// Only reposition on mobile
|
||||
if (!MobileDetection.isSmallScreen() && !MobileDetection.isMediumScreen()) {
|
||||
toolbar.style.transform = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.keyboardVisible) {
|
||||
// The toolbar is fixed at bottom: 0 (relative to layout viewport)
|
||||
// We need to move it up to be at the bottom of the visual viewport
|
||||
// Distance from visual viewport bottom to layout viewport bottom:
|
||||
const layoutHeight = window.innerHeight;
|
||||
const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height;
|
||||
const offsetFromBottom = layoutHeight - visualBottom;
|
||||
|
||||
// Move toolbar up by this offset (negative translateY moves up)
|
||||
toolbar.style.transform = `translateY(${-offsetFromBottom}px)`;
|
||||
} else {
|
||||
toolbar.style.transform = '';
|
||||
}
|
||||
},
|
||||
|
||||
/** Called when keyboard appears */
|
||||
onKeyboardShow() {
|
||||
// Scroll active terminal to bottom so input line is visible
|
||||
@@ -248,6 +286,15 @@ const KeyboardHandler = {
|
||||
}, 100);
|
||||
},
|
||||
|
||||
/** Called when keyboard hides */
|
||||
onKeyboardHide() {
|
||||
// Reset toolbar position
|
||||
const toolbar = document.querySelector('.toolbar');
|
||||
if (toolbar) {
|
||||
toolbar.style.transform = '';
|
||||
}
|
||||
},
|
||||
|
||||
/** Check if element is an input that triggers keyboard (excludes terminal) */
|
||||
isInputElement(el) {
|
||||
if (!el) return false;
|
||||
@@ -12803,7 +12850,7 @@ class ClaudemanApp {
|
||||
* Called automatically when image:detected SSE event is received.
|
||||
*/
|
||||
openImagePopup(imageEvent) {
|
||||
const { sessionId, filePath, fileName, timestamp, size } = imageEvent;
|
||||
const { sessionId, filePath, relativePath, fileName, timestamp, size } = imageEvent;
|
||||
|
||||
// Create unique window ID
|
||||
const imageId = `${sessionId}-${timestamp}`;
|
||||
@@ -12830,7 +12877,8 @@ class ClaudemanApp {
|
||||
const sizeKB = (size / 1024).toFixed(1);
|
||||
|
||||
// Build image URL using the existing file-raw endpoint
|
||||
const imageUrl = `/api/sessions/${sessionId}/file-raw?path=${encodeURIComponent(fileName)}`;
|
||||
// Use relativePath (path from working dir) instead of fileName (basename) for subdirectory images
|
||||
const imageUrl = `/api/sessions/${sessionId}/file-raw?path=${encodeURIComponent(relativePath || fileName)}`;
|
||||
|
||||
// Create window element
|
||||
const win = document.createElement('div');
|
||||
|
||||
@@ -8056,7 +8056,7 @@ kbd {
|
||||
|
||||
/* Clean dark toolbar - positioned near bottom */
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
bottom: var(--safe-area-bottom);
|
||||
left: 0;
|
||||
right: 0;
|
||||
@@ -8073,6 +8073,10 @@ kbd {
|
||||
background: #111111;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
z-index: 50;
|
||||
/* Smooth transition for keyboard show/hide */
|
||||
transition: transform 0.15s ease-out;
|
||||
/* Ensure transform works properly */
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Show case selector in center */
|
||||
@@ -8438,6 +8442,18 @@ kbd {
|
||||
scroll-margin-bottom: 200px;
|
||||
scroll-margin-top: 80px;
|
||||
}
|
||||
|
||||
/* When keyboard is visible, adjust content to account for moved toolbar */
|
||||
.keyboard-visible .terminal-container {
|
||||
/* Reduce terminal height when keyboard is up so it doesn't overlap toolbar */
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* Ensure modals scroll properly when keyboard is visible */
|
||||
.keyboard-visible .modal-body {
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user