Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 84 additions & 40 deletions frontend/src/components/Popups/ConnectionModal/ConnectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Dialog, TextInput, Dropdown, Banner, Dropzone, Typography, TextLink, Flex } from '@neo4j-ndl/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import connectAPI from '../../../services/ConnectAPI';
import { useCredentials } from '../../../context/UserCredentials';
import { useSearchParams } from 'react-router-dom';
Expand Down Expand Up @@ -46,6 +46,11 @@ export default function ConnectionModal({
const [searchParams, setSearchParams] = useSearchParams();
const [userDbVectorIndex, setUserDbVectorIndex] = useState<number | undefined>(initialuserdbvectorindex ?? undefined);
const [vectorIndexLoading, setVectorIndexLoading] = useState<boolean>(false);
const connectRef = useRef<HTMLButtonElement>(null);
const uriRef = useRef<HTMLInputElement>(null);
const databaseRef = useRef<HTMLInputElement>(null);
const userNameRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (searchParams.has('connectURL')) {
Expand Down Expand Up @@ -274,6 +279,24 @@ export default function ConnectionModal({
setMessage({ type: 'unknown', content: '' });
}, []);

const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>, nextRef?: React.RefObject<HTMLInputElement>) => {
if (e.code === 'Enter') {
e.preventDefault();
// @ts-ignore
const { form } = e.target;
if (form) {
const index = Array.prototype.indexOf.call(form, e.target);
if (index + 1 < form.elements.length) {
form.elements[index + 1].focus();
} else {
submitConnection();
}
} else {
nextRef?.current?.focus();
}
}
};

const isDisabled = useMemo(() => !username || !URI || !password, [username, URI, password]);

return (
Expand Down Expand Up @@ -348,6 +371,7 @@ export default function ConnectionModal({
/>
<div className='ml-[5%] w-[70%] inline-block'>
<TextInput
ref={uriRef}
id='url'
value={URI}
disabled={false}
Expand All @@ -357,50 +381,70 @@ export default function ConnectionModal({
onChange={(e) => setURI(e.target.value)}
onPaste={(e) => handleHostPasteChange(e)}
aria-label='Connection URI'
onKeyDown={(e) => handleKeyPress(e, databaseRef)}
/>
</div>
</div>
<TextInput
id='database'
value={database}
disabled={false}
label='Database'
aria-label='Database'
placeholder='neo4j'
fluid
required
onChange={(e) => setDatabase(e.target.value)}
className='w-full'
/>
<div className='n-flex n-flex-row n-flex-wrap mb-2'>
<div className='w-[48.5%] mr-1.5 inline-block'>
<TextInput
id='username'
value={username}
disabled={false}
label='Username'
aria-label='Username'
placeholder='neo4j'
fluid
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className='w-[48.5%] ml-[1.5%] inline-block'>
<TextInput
id='password'
value={password}
disabled={false}
label='Password'
aria-label='Password'
placeholder='password'
type='password'
fluid
onChange={(e) => setPassword(e.target.value)}
/>
<form>
<TextInput
ref={databaseRef}
id='database'
value={database}
disabled={false}
label='Database'
aria-label='Database'
placeholder='neo4j'
fluid
required
onChange={(e) => setDatabase(e.target.value)}
className='w-full'
onKeyDown={handleKeyPress}
/>
<div className='n-flex n-flex-row n-flex-wrap mb-2'>
<div className='w-[48.5%] mr-1.5 inline-block'>
<TextInput
ref={userNameRef}
id='username'
value={username}
disabled={false}
label='Username'
aria-label='Username'
placeholder='neo4j'
fluid
onChange={(e) => setUsername(e.target.value)}
onKeyDown={handleKeyPress}
/>
</div>
<div className='w-[48.5%] ml-[1.5%] inline-block'>
<TextInput
ref={passwordRef}
id='password'
value={password}
disabled={false}
label='Password'
aria-label='Password'
placeholder='password'
type='password'
fluid
onChange={(e) => setPassword(e.target.value)}
onKeyDown={handleKeyPress}
/>
</div>
</div>
</div>
</form>
<Flex flexDirection='row' justifyContent='flex-end'>
<Button loading={isLoading} disabled={isDisabled} onClick={() => submitConnection()}>
<Button
loading={isLoading}
disabled={isDisabled}
onClick={() => submitConnection()}
ref={connectRef}
onKeyDown={(e) => {
e.stopPropagation();
if (e.key === 'Enter') {
submitConnection();
}
}}
>
{buttonCaptions.connect}
</Button>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export default function DeduplicationTab() {
const onRemove = (nodeid: string, similarNodeId: string) => {
setDuplicateNodes((prev) => {
return prev.map((d) =>
d.e.elementId === nodeid
(d.e.elementId === nodeid
? {
...d,
similar: d.similar.filter((n) => n.elementId != similarNodeId),
}
: d
: d)
);
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export default function EntityExtractionSetting({
type='creatable'
/>

<Flex className='!mt-4 flex items-center' flexDirection='row' justifyContent='flex-end'>
<Flex className='!mt-4 mb-2 flex items-center' flexDirection='row' justifyContent='flex-end'>
<Flex flexDirection='row' gap='4'>
<ButtonWithToolTip
loading={loading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function GraphEnhancementDialog({
<Dialog
modalProps={{
id: 'graph-enhancement-popup',
className: 'n-p-token-4 n-rounded-lg h-[90%]',
className: 'n-p-token-4 n-rounded-lg',
}}
open={open}
size='unset'
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/components/UI/ButtonWithToolTip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Tip } from '@neo4j-ndl/react';
import React, { MouseEventHandler } from 'react';
import React, { MouseEventHandler, useState } from 'react';

const ButtonWithToolTip = ({
text,
Expand Down Expand Up @@ -30,8 +30,9 @@ const ButtonWithToolTip = ({
type?: 'submit' | 'button' | 'reset';
color?: 'primary' | 'danger' | 'warning' | 'success' | 'neutral' | undefined;
}) => {
const [isHovered, setIsHovered] = useState<boolean>(false);
return (
<Tip allowedPlacements={[placement]}>
<Tip allowedPlacements={[placement]} type='tooltip'>
<Tip.Trigger>
<Button
aria-label={label}
Expand All @@ -43,13 +44,17 @@ const ButtonWithToolTip = ({
fill={fill}
type={type}
color={color}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
</Button>
</Tip.Trigger>
<Tip.Content isPortaled={false} style={{ whiteSpace: 'nowrap' }}>
{text}
</Tip.Content>
{isHovered && (
<Tip.Content isPortaled={false} style={{ whiteSpace: 'nowrap' }}>
{text}
</Tip.Content>
)}
</Tip>
);
};
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/components/UI/IconButtonToolTip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IconButton, Tip } from '@neo4j-ndl/react';
import { useState } from 'react';

const IconButtonWithToolTip = ({
text,
Expand All @@ -21,6 +22,7 @@ const IconButtonWithToolTip = ({
placement?: 'bottom' | 'top' | 'right' | 'left';
disabled?: boolean;
}) => {
const [isHovered, setIsHovered] = useState<boolean>(false);
return (
<Tip allowedPlacements={[placement]}>
<Tip.Trigger>
Expand All @@ -31,13 +33,17 @@ const IconButtonWithToolTip = ({
grouped={grouped}
onClick={onClick}
disabled={disabled}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
</IconButton>
</Tip.Trigger>
<Tip.Content isPortaled={false} style={{ whiteSpace: 'nowrap' }}>
{text}
</Tip.Content>
{isHovered && (
<Tip.Content isPortaled={false} style={{ whiteSpace: 'nowrap' }}>
{text}
</Tip.Content>
)}
</Tip>
);
};
Expand Down
26 changes: 14 additions & 12 deletions frontend/src/context/ThemeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import { ReactNode, createContext, useMemo, useState } from 'react';
import { ReactNode, createContext, useMemo, useState, useEffect } from 'react';
import { NeedleThemeProvider, useMediaQuery } from '@neo4j-ndl/react';

export const ThemeWrapperContext = createContext({
toggleColorMode: () => {},
colorMode: localStorage.getItem('mode') as 'light' | 'dark',
});

interface ThemeWrapperProps {
children: ReactNode;
}
const ThemeWrapper = ({ children }: ThemeWrapperProps) => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
// @ts-ignore
const defaultMode: 'light' | 'dark' = localStorage.getItem('mode');
const [mode, setMode] = useState<'light' | 'dark'>(prefersDarkMode ? 'dark' : defaultMode ?? 'light');
const [usingPreferredMode, setUsingPreferredMode] = useState<boolean>(true);
const defaultMode = localStorage.getItem('mode') as 'light' | 'dark';
const [mode, setMode] = useState<'light' | 'dark'>(defaultMode ?? (prefersDarkMode ? 'dark' : 'light'));
const [usingPreferredMode, setUsingPreferredMode] = useState<boolean>(!defaultMode);

useEffect(() => {
// Ensure the body class is updated on initial load
themeBodyInjection(mode);
}, [mode]);
const themeWrapperUtils = useMemo(
() => ({
colorMode: mode,
toggleColorMode: () => {
setMode((prevMode) => {
const newMode = prevMode === 'light' ? 'dark' : 'light';
setUsingPreferredMode(false);
localStorage.setItem('mode', prevMode === 'light' ? 'dark' : 'light');
themeBodyInjection(prevMode);
return prevMode === 'light' ? 'dark' : 'light';
localStorage.setItem('mode', newMode);
themeBodyInjection(newMode);
return newMode;
});
},
}),
[mode]
);
const themeBodyInjection = (mode: string) => {
if (mode === 'light') {
if (mode === 'dark') {
document.body.classList.add('ndl-theme-dark');
} else {
document.body.classList.remove('ndl-theme-dark');
Expand All @@ -40,7 +43,6 @@ const ThemeWrapper = ({ children }: ThemeWrapperProps) => {
if (usingPreferredMode) {
prefersDarkMode ? themeBodyInjection('light') : themeBodyInjection('dark');
}

return (
<ThemeWrapperContext.Provider value={themeWrapperUtils}>
<NeedleThemeProvider theme={mode as 'light' | 'dark' | undefined} wrapperProps={{ isWrappingChildren: true }}>
Expand Down