Don't expand home dir for grid view

This commit is contained in:
Peter Steinberger 2025-06-24 00:19:40 +02:00
parent a51ecb174f
commit d1b0c43a09
4 changed files with 139 additions and 43 deletions

View file

@ -0,0 +1,81 @@
/**
* Clickable Path Component
*
* Displays a formatted path (with ~/ for home directory) that can be clicked to copy the full path.
* Provides visual feedback with hover effects and a copy icon.
*
* @fires path-copied - When path is successfully copied (detail: { path: string })
* @fires path-copy-failed - When path copy fails (detail: { path: string, error: string })
*/
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { formatPathForDisplay, copyToClipboard } from '../utils/path-utils.js';
import { createLogger } from '../utils/logger.js';
import './copy-icon.js';
const logger = createLogger('clickable-path');
@customElement('clickable-path')
export class ClickablePath extends LitElement {
// Disable shadow DOM to use Tailwind
createRenderRoot() {
return this;
}
@property({ type: String }) path = '';
@property({ type: String }) class = '';
@property({ type: Number }) iconSize = 12;
private async handleClick(e: Event) {
e.stopPropagation();
e.preventDefault();
if (!this.path) return;
try {
const success = await copyToClipboard(this.path);
if (success) {
logger.log('Path copied to clipboard', { path: this.path });
this.dispatchEvent(
new CustomEvent('path-copied', {
detail: { path: this.path },
bubbles: true,
composed: true,
})
);
} else {
throw new Error('Copy command failed');
}
} catch (error) {
logger.error('Failed to copy path to clipboard', { error, path: this.path });
this.dispatchEvent(
new CustomEvent('path-copy-failed', {
detail: {
path: this.path,
error: error instanceof Error ? error.message : 'Unknown error',
},
bubbles: true,
composed: true,
})
);
}
}
render() {
if (!this.path) return html``;
const displayPath = formatPathForDisplay(this.path);
return html`
<div
class="truncate cursor-pointer hover:text-accent-green transition-colors inline-flex items-center gap-1 max-w-full ${this
.class}"
title="Click to copy path"
@click=${this.handleClick}
>
<span class="truncate">${displayPath}</span>
<copy-icon size="${this.iconSize}" class="flex-shrink-0"></copy-icon>
</div>
`;
}
}

View file

@ -14,10 +14,12 @@ import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import type { Session } from '../../shared/types.js';
import { createLogger } from '../utils/logger.js';
import { copyToClipboard } from '../utils/path-utils.js';
const logger = createLogger('session-card');
import './vibe-terminal-buffer.js';
import './copy-icon.js';
import './clickable-path.js';
@customElement('session-card')
export class SessionCard extends LitElement {
@ -192,46 +194,15 @@ export class SessionCard extends LitElement {
e.preventDefault();
if (this.session.pid) {
try {
await navigator.clipboard.writeText(this.session.pid.toString());
const success = await copyToClipboard(this.session.pid.toString());
if (success) {
logger.log('PID copied to clipboard', { pid: this.session.pid });
} catch (error) {
logger.error('Failed to copy PID to clipboard', { error, pid: this.session.pid });
// Fallback: select text manually
this.fallbackCopyToClipboard(this.session.pid.toString());
} else {
logger.error('Failed to copy PID to clipboard', { pid: this.session.pid });
}
}
}
private fallbackCopyToClipboard(text: string) {
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
logger.log('Text copied to clipboard (fallback)', { text });
} catch (error) {
logger.error('Fallback copy failed', { error });
}
document.body.removeChild(textArea);
}
private async handlePathClick(e: Event) {
e.stopPropagation();
e.preventDefault();
try {
await navigator.clipboard.writeText(this.session.workingDir);
logger.log('Path copied to clipboard', { path: this.session.workingDir });
} catch (error) {
logger.error('Failed to copy path to clipboard', { error, path: this.session.workingDir });
// Fallback: select text manually
this.fallbackCopyToClipboard(this.session.workingDir);
}
}
render() {
return html`
<div
@ -342,14 +313,7 @@ export class SessionCard extends LitElement {
: ''}
</div>
<div class="text-xs opacity-75 min-w-0 mt-1">
<div
class="truncate cursor-pointer hover:text-accent-green transition-colors inline-flex items-center gap-1 max-w-full"
title="Click to copy path"
@click=${this.handlePathClick}
>
<span class="truncate">${this.session.workingDir}</span>
<copy-icon size="12" class="flex-shrink-0"></copy-icon>
</div>
<clickable-path .path=${this.session.workingDir} .iconSize=${12}></clickable-path>
</div>
</div>
</div>

View file

@ -18,6 +18,7 @@ import { customElement, property, state } from 'lit/decorators.js';
import type { Session } from './session-list.js';
import './terminal.js';
import './file-browser.js';
import './clickable-path.js';
import type { Terminal } from './terminal.js';
import { CastConverter } from '../utils/cast-converter.js';
import {
@ -1076,6 +1077,9 @@ export class SessionView extends LitElement {
>
${this.session.name || this.session.command}
</div>
<div class="text-xs opacity-75 mt-0.5">
<clickable-path .path=${this.session.workingDir} .iconSize=${12}></clickable-path>
</div>
</div>
</div>
<div class="flex items-center gap-2 text-xs flex-shrink-0 ml-2 relative">

View file

@ -0,0 +1,47 @@
/**
* Path utilities for formatting and displaying paths
*/
/**
* Format a file path for display by replacing the home directory with ~
* @param path The absolute path to format
* @returns The formatted path with ~ replacing the home directory
*/
export function formatPathForDisplay(path: string): string {
const homeDir = '/Users/steipete';
if (path.startsWith(homeDir)) {
return '~' + path.slice(homeDir.length);
}
return path;
}
/**
* Copy text to clipboard with fallback for older browsers
* @param text The text to copy
* @returns Promise<boolean> indicating success
*/
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (_error) {
// Fallback for older browsers or permission issues
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const result = document.execCommand('copy');
document.body.removeChild(textArea);
return result;
} catch (_err) {
document.body.removeChild(textArea);
return false;
}
}
}