mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 04:04: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",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.5.1",
|
"version": "1.5.2",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export const UpdateAvailableModal = ({
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
size="sm"
|
size="sm"
|
||||||
title="A newer version is available"
|
title="An update is available"
|
||||||
closeOnEscape={!isDownloading && !isUpdating}
|
closeOnEscape={!isDownloading && !isUpdating}
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useRef, useState, type MouseEvent } from 'react';
|
import { useEffect, useRef, useState, type MouseEvent } from 'react';
|
||||||
import { Box, Paper, ActionIcon } from '@mantine/core';
|
import { Box, Paper, ActionIcon } from '@mantine/core';
|
||||||
import { X } from 'lucide-react';
|
import { Minus } from 'lucide-react';
|
||||||
import { useNotepadStore } from '@/stores/notepad';
|
import { useNotepadStore } from '@/stores/notepad';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { NOTEPAD_MIN_WIDTH, NOTEPAD_MIN_HEIGHT } from '@/constants/notepad';
|
import { NOTEPAD_MIN_WIDTH, NOTEPAD_MIN_HEIGHT } from '@/constants/notepad';
|
||||||
|
|
@ -168,6 +168,27 @@ export const NotepadContainer = () => {
|
||||||
onMouseDown={handleResizeStart('top-right')}
|
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 h="100%" style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -200,9 +221,8 @@ export const NotepadContainer = () => {
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setVisible(false)}
|
onClick={() => setVisible(false)}
|
||||||
color="red"
|
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<Minus size="1rem" />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Box>
|
</Box>
|
||||||
</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 {
|
import { type MouseEvent, type DragEvent, useState } from 'react';
|
||||||
type MouseEvent,
|
import { Box, ActionIcon } from '@mantine/core';
|
||||||
type DragEvent,
|
import { Plus } from 'lucide-react';
|
||||||
type KeyboardEvent,
|
import { Tab } from '@/components/Notepad/Tab';
|
||||||
useState,
|
|
||||||
useRef,
|
|
||||||
useEffect,
|
|
||||||
} from 'react';
|
|
||||||
import { Box, ActionIcon, Text, TextInput } from '@mantine/core';
|
|
||||||
import { X, Plus } from 'lucide-react';
|
|
||||||
import { useNotepadStore } from '@/stores/notepad';
|
import { useNotepadStore } from '@/stores/notepad';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
|
||||||
|
|
@ -16,181 +10,6 @@ interface NotepadTabsProps {
|
||||||
onCloseTab: (title: string) => void;
|
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 = ({
|
export const NotepadTabs = ({
|
||||||
onCreateNewTab,
|
onCreateNewTab,
|
||||||
onCloseTab,
|
onCloseTab,
|
||||||
|
|
@ -270,7 +89,7 @@ export const NotepadTabs = ({
|
||||||
}`,
|
}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
minHeight: 32,
|
minHeight: '2rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
|
|
@ -305,7 +124,7 @@ export const NotepadTabs = ({
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus size={12} />
|
<Plus size="0.75rem" />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue