diff --git a/app/controllers/favicons_controller.ts b/app/controllers/favicons_controller.ts index b5300a5..5cbfb22 100644 --- a/app/controllers/favicons_controller.ts +++ b/app/controllers/favicons_controller.ts @@ -26,13 +26,11 @@ export default class FaviconsController { ]; async index(ctx: HttpContext) { - console.log('0'); const url = ctx.request.qs()?.url; if (!url) { throw new Error('Missing URL'); } - console.log('1'); const faviconRequestUrl = this.buildFaviconUrl(url, '/favicon.ico'); try { const favicon = await this.getFavicon(faviconRequestUrl); @@ -42,7 +40,6 @@ export default class FaviconsController { `[Favicon] [first: ${faviconRequestUrl}] Unable to retrieve favicon from favicon.ico url` ); } - console.log('2'); const requestDocument = await this.makeRequestWithUserAgent(url); const documentAsText = await requestDocument.text(); @@ -57,7 +54,6 @@ export default class FaviconsController { return this.sendDefaultImage(ctx); } - console.log('3'); const finalUrl = this.buildFaviconUrl(requestDocument.url, faviconPath); try { if (!faviconPath) { @@ -186,14 +182,12 @@ export default class FaviconsController { Buffer.from(base64, 'base64'); private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) { - console.log('ouiiiiiiii', type, size); ctx.response.header('Content-Type', type); ctx.response.header('Content-Length', size); ctx.response.send(buffer); } private sendDefaultImage(ctx: HttpContext) { - console.log('oui'); const readStream = createReadStream( resolve(process.cwd(), './public/empty-image.png') ); diff --git a/inertia/components/common/dropdown/dropdown.tsx b/inertia/components/common/dropdown/dropdown.tsx new file mode 100644 index 0000000..4c72e55 --- /dev/null +++ b/inertia/components/common/dropdown/dropdown.tsx @@ -0,0 +1,56 @@ +import KEYS from '#constants/keys'; +import styled from '@emotion/styled'; +import { ReactNode, useRef } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import DropdownContainer from '~/components/common/dropdown/dropdown_container'; +import DropdownLabel from '~/components/common/dropdown/dropdown_label'; +import useClickOutside from '~/hooks/use_click_outside'; +import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; +import useToggle from '~/hooks/use_modal'; + +const DropdownStyle = styled.div<{ opened: boolean }>(({ opened, theme }) => ({ + cursor: 'pointer', + userSelect: 'none', + position: 'relative', + minWidth: 'fit-content', + width: 'fit-content', + maxWidth: '250px', + backgroundColor: opened ? theme.colors.secondary : theme.colors.background, + padding: '4px', + borderRadius: theme.border.radius, + + '&:hover': { + backgroundColor: theme.colors.secondary, + }, + + '& svg': { + height: '24px', + width: '24px', + }, +})); + +export default function Dropdown({ + children, + label, +}: { + children: ReactNode; + label: ReactNode | string; +}) { + const dropdownRef = useRef(null); + const { isShowing, toggle, close } = useToggle(); + const { globalHotkeysEnabled } = useGlobalHotkeys(); + + useClickOutside(dropdownRef, close); + + useHotkeys(KEYS.ESCAPE_KEY, close, { + enabled: globalHotkeysEnabled, + enableOnFormTags: ['INPUT'], + }); + + return ( + + {label} + {children} + + ); +} diff --git a/inertia/components/common/dropdown/dropdown_container.tsx b/inertia/components/common/dropdown/dropdown_container.tsx new file mode 100644 index 0000000..54ab57c --- /dev/null +++ b/inertia/components/common/dropdown/dropdown_container.tsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled'; + +const DropdownContainer = styled.div<{ show: boolean }>(({ show, theme }) => ({ + position: 'absolute', + top: 'calc(100% + 0.5em)', + right: 0, + minWidth: '175px', + backgroundColor: show ? theme.colors.secondary : theme.colors.background, + border: `2px solid ${theme.colors.secondary}`, + borderRadius: theme.border.radius, + boxShadow: theme.colors.boxShadow, + display: show ? 'flex' : 'none', + flexDirection: 'column', + overflow: 'hidden', +})); + +export default DropdownContainer; diff --git a/inertia/components/common/dropdown/dropdown_item.tsx b/inertia/components/common/dropdown/dropdown_item.tsx new file mode 100644 index 0000000..aaeaca1 --- /dev/null +++ b/inertia/components/common/dropdown/dropdown_item.tsx @@ -0,0 +1,16 @@ +import styled from '@emotion/styled'; + +const DropdownItem = styled.div(({ theme }) => ({ + fontSize: '14px', + padding: '8px 12px', + display: 'flex', + gap: '0.35em', + alignItems: 'center', + borderRadius: theme.border.radius, + + '&:hover': { + backgroundColor: theme.colors.background, + }, +})); + +export default DropdownItem; diff --git a/inertia/components/common/dropdown/dropdown_label.tsx b/inertia/components/common/dropdown/dropdown_label.tsx new file mode 100644 index 0000000..e5d55c6 --- /dev/null +++ b/inertia/components/common/dropdown/dropdown_label.tsx @@ -0,0 +1,11 @@ +import styled from '@emotion/styled'; + +const DropdownLabel = styled.p(({ theme }) => ({ + height: 'auto', + width: 'auto', + color: theme.colors.font, + display: 'flex', + gap: '0.35em', +})); + +export default DropdownLabel; diff --git a/inertia/components/common/navigation/bask_to_dashboard.tsx b/inertia/components/common/navigation/back_to_dashboard.tsx similarity index 70% rename from inertia/components/common/navigation/bask_to_dashboard.tsx rename to inertia/components/common/navigation/back_to_dashboard.tsx index 40f3261..32b9ce7 100644 --- a/inertia/components/common/navigation/bask_to_dashboard.tsx +++ b/inertia/components/common/navigation/back_to_dashboard.tsx @@ -4,13 +4,16 @@ import { router } from '@inertiajs/react'; import { ReactNode } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; +import useSearchParam from '~/hooks/use_search_param'; +import { appendCollectionId } from '~/lib/navigation'; export default function BackToDashboard({ children }: { children: ReactNode }) { + const collectionId = useSearchParam('collectionId'); const { globalHotkeysEnabled } = useGlobalHotkeys(); useHotkeys( KEYS.ESCAPE_KEY, () => { - router.visit(PATHS.DASHBOARD); + router.visit(appendCollectionId(PATHS.DASHBOARD, collectionId)); }, { enabled: globalHotkeysEnabled, enableOnFormTags: ['INPUT'] } ); diff --git a/inertia/hooks/use_click_outside.tsx b/inertia/hooks/use_click_outside.tsx new file mode 100644 index 0000000..ac975ff --- /dev/null +++ b/inertia/hooks/use_click_outside.tsx @@ -0,0 +1,22 @@ +import { RefObject, useEffect } from 'react'; + +// Source : https://stackoverflow.com/a/63359693 + +/** + * This Hook can be used for detecting clicks outside the Opened Menu + */ +export default function useClickOutside( + ref: RefObject, + onClickOutside: () => void +) { + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (ref?.current && !ref.current?.contains(event.target as any)) { + onClickOutside(); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [ref, onClickOutside]); +} diff --git a/inertia/hooks/use_modal.tsx b/inertia/hooks/use_modal.tsx index 4528c1b..8cb7aaf 100644 --- a/inertia/hooks/use_modal.tsx +++ b/inertia/hooks/use_modal.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -const useModal = (defaultValue: boolean = false) => { +const useToggle = (defaultValue: boolean = false) => { const [isShowing, setIsShowing] = useState(defaultValue); const toggle = () => setIsShowing((value) => !value); @@ -15,4 +15,4 @@ const useModal = (defaultValue: boolean = false) => { }; }; -export default useModal; +export default useToggle; diff --git a/inertia/pages/collections/create.tsx b/inertia/pages/collections/create.tsx index b0f89e2..e77c024 100644 --- a/inertia/pages/collections/create.tsx +++ b/inertia/pages/collections/create.tsx @@ -2,7 +2,7 @@ import { useForm } from '@inertiajs/react'; import { ChangeEvent, FormEvent, useMemo } from 'react'; import FormField from '~/components/common/form/_form_field'; import TextBox from '~/components/common/form/textbox'; -import BackToDashboard from '~/components/common/navigation/bask_to_dashboard'; +import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import FormLayout from '~/components/layouts/form_layout'; import { Visibility } from '../../../app/enums/visibility'; diff --git a/inertia/pages/dashboard.tsx b/inertia/pages/dashboard.tsx index c9b5cee..1ce3be8 100644 --- a/inertia/pages/dashboard.tsx +++ b/inertia/pages/dashboard.tsx @@ -16,7 +16,7 @@ import CollectionsContext from '~/contexts/collections_context'; import FavoritesContext from '~/contexts/favorites_context'; import GlobalHotkeysContext from '~/contexts/global_hotkeys_context'; import { useMediaQuery } from '~/hooks/use_media_query'; -import useModal from '~/hooks/use_modal'; +import useToggle from '~/hooks/use_modal'; interface HomePageProps { collections: Collection[]; @@ -31,7 +31,7 @@ const SideBar = styled.div(({ theme }) => ({ export default function HomePage(props: Readonly) { const isMobile = useMediaQuery('(max-width: 768px)'); - const { isShowing, open, close } = useModal(); + const { isShowing, open, close } = useToggle(); const handlers = useSwipeable({ trackMouse: true, onSwipedRight: open, diff --git a/inertia/pages/links/create.tsx b/inertia/pages/links/create.tsx index cd5a2a0..4d2de44 100644 --- a/inertia/pages/links/create.tsx +++ b/inertia/pages/links/create.tsx @@ -3,7 +3,7 @@ import { useForm } from '@inertiajs/react'; import { ChangeEvent, FormEvent, useMemo } from 'react'; import FormField from '~/components/common/form/_form_field'; import TextBox from '~/components/common/form/textbox'; -import BackToDashboard from '~/components/common/navigation/bask_to_dashboard'; +import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import FormLayout from '~/components/layouts/form_layout'; import useSearchParam from '~/hooks/use_search_param'; import { isValidHttpUrl } from '~/lib/navigation'; @@ -46,6 +46,7 @@ export default function CreateLinkPage({ title="Create a link" handleSubmit={handleSubmit} canSubmit={canSubmit} + collectionId={collectionId} >