mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-30 00:53:14 +00:00
Merge pull request #139 from arc53/feature/nicks-list1
This commit is contained in:
@@ -8,15 +8,12 @@ import { ActiveState } from './models/misc';
|
||||
export default function App() {
|
||||
//TODO : below media query is disjoint from tailwind. Please wire it together.
|
||||
const [navState, setNavState] = useState<ActiveState>(
|
||||
window.matchMedia('((min-width: 768px)').matches ? 'ACTIVE' : 'INACTIVE',
|
||||
window.matchMedia('(min-width: 768px)').matches ? 'ACTIVE' : 'INACTIVE',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-full min-w-full">
|
||||
<Navigation
|
||||
navState={navState}
|
||||
setNavState={(val: ActiveState) => setNavState(val)}
|
||||
/>
|
||||
<Navigation navState={navState} setNavState={setNavState} />
|
||||
<div
|
||||
className={`transition-all duration-200 ${
|
||||
navState === 'ACTIVE' ? 'ml-0 md:ml-72 lg:ml-60' : 'ml-0 md:ml-16'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Arrow1 from './assets/arrow.svg';
|
||||
import Arrow2 from './assets/dropdown-arrow.svg';
|
||||
@@ -18,13 +18,14 @@ import {
|
||||
selectSourceDocs,
|
||||
setSelectedDocs,
|
||||
} from './preferences/preferenceSlice';
|
||||
import { useOutsideAlerter } from './hooks';
|
||||
|
||||
export default function Navigation({
|
||||
navState,
|
||||
setNavState,
|
||||
}: {
|
||||
navState: ActiveState;
|
||||
setNavState: (val: ActiveState) => void;
|
||||
setNavState: React.Dispatch<React.SetStateAction<ActiveState>>;
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const docs = useSelector(selectSourceDocs);
|
||||
@@ -41,9 +42,26 @@ export default function Navigation({
|
||||
const [selectedDocsModalState, setSelectedDocsModalState] =
|
||||
useState<ActiveState>(isSelectedDocsSet ? 'INACTIVE' : 'ACTIVE');
|
||||
|
||||
const navRef = useRef(null);
|
||||
useOutsideAlerter(
|
||||
navRef,
|
||||
() => {
|
||||
if (
|
||||
window.matchMedia('(max-width: 768px)').matches &&
|
||||
navState === 'ACTIVE' &&
|
||||
apiKeyModalState === 'INACTIVE'
|
||||
) {
|
||||
setNavState('INACTIVE');
|
||||
setIsDocsListOpen(false);
|
||||
}
|
||||
},
|
||||
[navState, isDocsListOpen, apiKeyModalState],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={navRef}
|
||||
className={`${
|
||||
navState === 'INACTIVE' && '-ml-96 md:-ml-[14rem]'
|
||||
} duration-20 fixed z-20 flex h-full w-72 flex-col border-r-2 bg-gray-50 transition-all`}
|
||||
|
||||
@@ -67,16 +67,18 @@ export default function Conversation() {
|
||||
className="relative right-[38px] bottom-[7px] -mr-[30px] animate-spin cursor-pointer self-end"
|
||||
></img>
|
||||
) : (
|
||||
<img
|
||||
onClick={() => {
|
||||
if (inputRef.current?.textContent) {
|
||||
handleQuestion(inputRef.current.textContent);
|
||||
inputRef.current.textContent = '';
|
||||
}
|
||||
}}
|
||||
src={Send}
|
||||
className="relative right-[35px] bottom-[15px] -mr-[21px] cursor-pointer self-end"
|
||||
></img>
|
||||
<div className="relative right-[43px] bottom-[7px] -mr-[35px] h-[35px] w-[35px] cursor-pointer self-end rounded-full hover:bg-gray-3000">
|
||||
<img
|
||||
className="ml-[9px] mt-[9px]"
|
||||
onClick={() => {
|
||||
if (inputRef.current?.textContent) {
|
||||
handleQuestion(inputRef.current.textContent);
|
||||
inputRef.current.textContent = '';
|
||||
}
|
||||
}}
|
||||
src={Send}
|
||||
></img>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 w-10/12 self-center text-center text-xs text-gray-2000">
|
||||
|
||||
19
frontend/src/hooks.ts
Normal file
19
frontend/src/hooks.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export function useOutsideAlerter<T extends HTMLElement>(
|
||||
ref: RefObject<T>,
|
||||
handler: () => void,
|
||||
additionalDeps: unknown[],
|
||||
) {
|
||||
useEffect(() => {
|
||||
function handleClickOutside(this: Document, event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [ref, ...additionalDeps]);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { selectApiKey, setApiKey } from './preferenceSlice';
|
||||
import { useOutsideAlerter } from './../hooks';
|
||||
|
||||
export default function APIKeyModal({
|
||||
modalState,
|
||||
@@ -16,6 +17,20 @@ export default function APIKeyModal({
|
||||
const apiKey = useSelector(selectApiKey);
|
||||
const [key, setKey] = useState(apiKey);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const modalRef = useRef(null);
|
||||
|
||||
useOutsideAlerter(
|
||||
modalRef,
|
||||
() => {
|
||||
if (
|
||||
window.matchMedia('(max-width: 768px)').matches &&
|
||||
modalState === 'ACTIVE'
|
||||
) {
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
},
|
||||
[modalState],
|
||||
);
|
||||
|
||||
function handleSubmit() {
|
||||
if (key.length <= 1) {
|
||||
@@ -39,7 +54,10 @@ export default function APIKeyModal({
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
||||
>
|
||||
<article className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg">
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg"
|
||||
>
|
||||
<p className="text-xl text-jet">OpenAI API Key</p>
|
||||
<p className="text-md leading-6 text-gray-500">
|
||||
Before you can start using DocsGPT we need you to provide an API key
|
||||
|
||||
Reference in New Issue
Block a user