mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
minimize icon for the notepad "close", fix notepad reisizing over iframes
This commit is contained in:
parent
f390fc5bd4
commit
99acc22b78
5 changed files with 217 additions and 192 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.2",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const UpdateAvailableModal = ({
|
|||
opened={opened}
|
||||
onClose={onClose}
|
||||
size="sm"
|
||||
title="A newer version is available"
|
||||
title="An update is available"
|
||||
closeOnEscape={!isDownloading && !isUpdating}
|
||||
>
|
||||
<Stack gap="md">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useRef, useState, type MouseEvent } from 'react';
|
||||
import { Box, Paper, ActionIcon } from '@mantine/core';
|
||||
import { X } from 'lucide-react';
|
||||
import { Minus } from 'lucide-react';
|
||||
import { useNotepadStore } from '@/stores/notepad';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { NOTEPAD_MIN_WIDTH, NOTEPAD_MIN_HEIGHT } from '@/constants/notepad';
|
||||
|
|
@ -168,6 +168,27 @@ export const NotepadContainer = () => {
|
|||
onMouseDown={handleResizeStart('top-right')}
|
||||
/>
|
||||
|
||||
{resizeDirection && (
|
||||
<Box
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: 9999,
|
||||
backgroundColor: 'transparent',
|
||||
cursor:
|
||||
resizeDirection.includes('right') &&
|
||||
resizeDirection.includes('top')
|
||||
? 'ne-resize'
|
||||
: resizeDirection.includes('right')
|
||||
? 'ew-resize'
|
||||
: 'ns-resize',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box h="100%" style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
style={{
|
||||
|
|
@ -200,9 +221,8 @@ export const NotepadContainer = () => {
|
|||
variant="subtle"
|
||||
size="xs"
|
||||
onClick={() => setVisible(false)}
|
||||
color="red"
|
||||
>
|
||||
<X size={16} />
|
||||
<Minus size="1rem" />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
186
src/components/Notepad/Tab.tsx
Normal file
186
src/components/Notepad/Tab.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import {
|
||||
type MouseEvent,
|
||||
type DragEvent,
|
||||
type KeyboardEvent,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Box, ActionIcon, Text, TextInput } from '@mantine/core';
|
||||
import { X } from 'lucide-react';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
interface TabProps {
|
||||
title: string;
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
onSelect: () => void;
|
||||
onClose: (e: MouseEvent) => void;
|
||||
onDragStart: (e: DragEvent, index: number) => void;
|
||||
onDragOver: (e: DragEvent) => void;
|
||||
onDrop: (e: DragEvent, index: number) => void;
|
||||
onRename: (newTitle: string) => void;
|
||||
isDragOver: boolean;
|
||||
showLineNumbers: boolean;
|
||||
setShowLineNumbers: (show: boolean) => void;
|
||||
}
|
||||
|
||||
export const Tab = ({
|
||||
index,
|
||||
isActive,
|
||||
title,
|
||||
onSelect,
|
||||
onClose,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onRename,
|
||||
isDragOver,
|
||||
showLineNumbers,
|
||||
setShowLineNumbers,
|
||||
}: TabProps) => {
|
||||
const { resolvedColorScheme } = usePreferencesStore();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editingTitle, setEditingTitle] = useState(title);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
const handleTitleClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!isActive) {
|
||||
onSelect();
|
||||
}
|
||||
};
|
||||
|
||||
const handleTitleDoubleClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (isActive) {
|
||||
setIsEditing(true);
|
||||
setEditingTitle(title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveTitle();
|
||||
} else if (e.key === 'Escape') {
|
||||
setIsEditing(false);
|
||||
setEditingTitle(title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
handleSaveTitle();
|
||||
};
|
||||
|
||||
const handleSaveTitle = () => {
|
||||
const trimmedTitle = editingTitle.trim();
|
||||
if (trimmedTitle && trimmedTitle !== title) {
|
||||
onRename(trimmedTitle);
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleTabClick = () => {
|
||||
if (!isEditing) {
|
||||
onSelect();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
draggable={!isEditing}
|
||||
onClick={handleTabClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onDragStart={(e) => onDragStart(e, index)}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={(e) => onDrop(e, index)}
|
||||
style={{
|
||||
padding: '0.375rem 0.5rem',
|
||||
backgroundColor: isActive
|
||||
? resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-4)'
|
||||
: 'var(--mantine-color-gray-1)'
|
||||
: isDragOver
|
||||
? resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-5)'
|
||||
: 'var(--mantine-color-gray-2)'
|
||||
: 'transparent',
|
||||
borderRight: `1px solid ${
|
||||
resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-4)'
|
||||
: 'var(--mantine-color-gray-3)'
|
||||
}`,
|
||||
cursor: isEditing ? 'default' : 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.25rem',
|
||||
minWidth: 0,
|
||||
maxWidth: '7.5rem',
|
||||
opacity: isDragOver ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{isEditing ? (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={editingTitle}
|
||||
onChange={(e) => setEditingTitle(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
size="xs"
|
||||
variant="unstyled"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
}}
|
||||
styles={{
|
||||
input: {
|
||||
fontSize: 'var(--mantine-font-size-xs)',
|
||||
padding: 0,
|
||||
minHeight: 'auto',
|
||||
height: 'auto',
|
||||
lineHeight: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
size="xs"
|
||||
title={title}
|
||||
onClick={handleTitleClick}
|
||||
onDoubleClick={handleTitleDoubleClick}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowLineNumbers(!showLineNumbers);
|
||||
}}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<ActionIcon variant="subtle" size="xs" onClick={onClose}>
|
||||
<X size="0.625rem" />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,13 +1,7 @@
|
|||
import {
|
||||
type MouseEvent,
|
||||
type DragEvent,
|
||||
type KeyboardEvent,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Box, ActionIcon, Text, TextInput } from '@mantine/core';
|
||||
import { X, Plus } from 'lucide-react';
|
||||
import { type MouseEvent, type DragEvent, useState } from 'react';
|
||||
import { Box, ActionIcon } from '@mantine/core';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Tab } from '@/components/Notepad/Tab';
|
||||
import { useNotepadStore } from '@/stores/notepad';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
|
|
@ -16,181 +10,6 @@ interface NotepadTabsProps {
|
|||
onCloseTab: (title: string) => void;
|
||||
}
|
||||
|
||||
interface TabProps {
|
||||
title: string;
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
onSelect: () => void;
|
||||
onClose: (e: MouseEvent) => void;
|
||||
onDragStart: (e: DragEvent, index: number) => void;
|
||||
onDragOver: (e: DragEvent) => void;
|
||||
onDrop: (e: DragEvent, index: number) => void;
|
||||
onRename: (newTitle: string) => void;
|
||||
isDragOver: boolean;
|
||||
showLineNumbers: boolean;
|
||||
setShowLineNumbers: (show: boolean) => void;
|
||||
}
|
||||
|
||||
const Tab = ({
|
||||
index,
|
||||
isActive,
|
||||
title,
|
||||
onSelect,
|
||||
onClose,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onRename,
|
||||
isDragOver,
|
||||
showLineNumbers,
|
||||
setShowLineNumbers,
|
||||
}: TabProps) => {
|
||||
const { resolvedColorScheme } = usePreferencesStore();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editingTitle, setEditingTitle] = useState(title);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
const handleTitleClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!isActive) {
|
||||
onSelect();
|
||||
}
|
||||
};
|
||||
|
||||
const handleTitleDoubleClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (isActive) {
|
||||
setIsEditing(true);
|
||||
setEditingTitle(title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveTitle();
|
||||
} else if (e.key === 'Escape') {
|
||||
setIsEditing(false);
|
||||
setEditingTitle(title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
handleSaveTitle();
|
||||
};
|
||||
|
||||
const handleSaveTitle = () => {
|
||||
const trimmedTitle = editingTitle.trim();
|
||||
if (trimmedTitle && trimmedTitle !== title) {
|
||||
onRename(trimmedTitle);
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleTabClick = () => {
|
||||
if (!isEditing) {
|
||||
onSelect();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
draggable={!isEditing}
|
||||
onClick={handleTabClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onDragStart={(e) => onDragStart(e, index)}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={(e) => onDrop(e, index)}
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
backgroundColor: isActive
|
||||
? resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-4)'
|
||||
: 'var(--mantine-color-gray-1)'
|
||||
: isDragOver
|
||||
? resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-5)'
|
||||
: 'var(--mantine-color-gray-2)'
|
||||
: 'transparent',
|
||||
borderRight: `1px solid ${
|
||||
resolvedColorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-4)'
|
||||
: 'var(--mantine-color-gray-3)'
|
||||
}`,
|
||||
cursor: isEditing ? 'default' : 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
minWidth: 0,
|
||||
maxWidth: 120,
|
||||
opacity: isDragOver ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{isEditing ? (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={editingTitle}
|
||||
onChange={(e) => setEditingTitle(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
size="xs"
|
||||
variant="unstyled"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
}}
|
||||
styles={{
|
||||
input: {
|
||||
fontSize: 'var(--mantine-font-size-xs)',
|
||||
padding: 0,
|
||||
minHeight: 'auto',
|
||||
height: 'auto',
|
||||
lineHeight: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
size="xs"
|
||||
title={title}
|
||||
onClick={handleTitleClick}
|
||||
onDoubleClick={handleTitleDoubleClick}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowLineNumbers(!showLineNumbers);
|
||||
}}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<ActionIcon variant="subtle" size="xs" onClick={onClose}>
|
||||
<X size={10} />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const NotepadTabs = ({
|
||||
onCreateNewTab,
|
||||
onCloseTab,
|
||||
|
|
@ -270,7 +89,7 @@ export const NotepadTabs = ({
|
|||
}`,
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
minHeight: 32,
|
||||
minHeight: '2rem',
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
|
|
@ -305,7 +124,7 @@ export const NotepadTabs = ({
|
|||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
<Plus size={12} />
|
||||
<Plus size="0.75rem" />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue