Skip to content

Commit

Permalink
webapp: Add help modal
Browse files Browse the repository at this point in the history
  • Loading branch information
noczero committed Sep 29, 2024
1 parent 4d8c9d9 commit 429d73a
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 35 deletions.
125 changes: 125 additions & 0 deletions packages/webapp/src/single/HelpModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useEffect, useRef, FC } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as icons from '@fortawesome/free-solid-svg-icons';

interface HelperModalsProps {
showHelp: boolean;
setShowHelp: (show: boolean) => void;
dispatch: (action: { type: string }) => void;
}

const ShortcutItem: FC<{ label: string, keys: string[] }> = ({ label, keys }) => (
<div className="w-full flex justify-between items-center">
<div className="text-sm">{label}</div>
<div className="flex space-x-1 text-xs">
{keys.map((key, index) => (
<div key={index} className="h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300">
{key}
</div>
))}
</div>
</div>
);

export const HelpModal: FC<HelperModalsProps> = ({ showHelp, setShowHelp, dispatch }) => {
const modalRef = useRef<HTMLDivElement>(null);

const handleToggleModal = () => {
setShowHelp(!showHelp);
dispatch({ type: 'help' });
};

const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setShowHelp(false);
}
};

useEffect(() => {
if (showHelp) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [showHelp]);

return (
<>
<div className="hidden lg:flex fixed bottom-0 right-0 px-2 py-2 z-20">
<a
className="flex items-center justify-center w-6 h-6 rounded hover:bg-gray-700 hover:cursor-pointer"
onClick={handleToggleModal}
title="Help"
>
<FontAwesomeIcon
icon={icons.faQuestion}
className="text-gray-700 hover:text-gray-400 active:text-gray-200"
/>
</a>
</div>
{showHelp && (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="modal fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[9999] overflow-hidden overscroll-contain">
<div
ref={modalRef}
className="m-auto rounded-2xl max-w-full w-[48rem] mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl max-h-[100dvh] overflow-y-auto scrollbar-hidden"
>
<div className="text-gray-700 dark:text-gray-100">
<div className="flex justify-between dark:text-gray-300 px-5 pt-4">
<div className="text-lg font-medium self-center">
Keyboard shortcuts
</div>
<button
className="flex items-center justify-center w-6 h-6 rounded hover:bg-gray-700 hover:cursor-pointer"
onClick={() => setShowHelp(false)}
>
<FontAwesomeIcon
icon={icons.faXmark}
className="text-gray-700 hover:text-gray-400 active:text-gray-200"
/>
</button>
</div>
<div className="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
<div className="flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
<div className="flex flex-col space-y-3 w-full self-start">
<ShortcutItem label="Next" keys={['Right', '/', 'k', '/', 'Space']} />
<ShortcutItem label="Prev" keys={['Left', '/', 'j', '/', '⌫']} />
<ShortcutItem label="Details" keys={['i']} />
<ShortcutItem label="Similar" keys={['s']} />
</div>
<div className="flex flex-col space-y-3 w-full self-start">
<ShortcutItem label="Chronology" keys={['c']} />
<ShortcutItem label="Map" keys={['m']} />
<ShortcutItem label="Show Media Stream" keys={['Esc']} />
<ShortcutItem label="Show shortcuts" keys={['H']} />
</div>
</div>
</div>
<div className="flex justify-between dark:text-gray-300 px-5 pt-4">
<div className="text-lg font-medium self-center">Links</div>
</div>
<div className="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
<div className="flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
<div className="flex flex-col space-y-3 w-full self-start">
<div className="w-full flex justify-between items-center">
<div className="text-sm">
<a className="rounded hover:bg-gray-700 hover:cursor-pointer" href="https://docs.home-gallery.org/">
Documentation
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
};
13 changes: 10 additions & 3 deletions packages/webapp/src/single/MediaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { Zoomable } from "./Zoomable";
import useBodyDimensions from "../utils/useBodyDimensions";
import { classNames } from '../utils/class-names'
import { SingleTagDialogProvider } from "../dialog/tag-dialog-provider";
import {HelpModal} from "./HelpModal";


const findEntryIndex = (location, entries, id) => {
if (location.state?.index && entries[location.state.index]?.id.startsWith(id)) {
Expand Down Expand Up @@ -65,7 +67,8 @@ const hotkeysToAction = {
's': 'similar',
'c': 'chronology',
't': 'toggleNavigation',
'm': 'map'
'm': 'map',
'h': 'help'
}

export const MediaView = () => {
Expand All @@ -80,13 +83,14 @@ export const MediaView = () => {
const showDetails = useSingleViewStore(state => state.showDetails);
const showAnnotations = useSingleViewStore(state => state.showAnnotations);
const showNavigation = useSingleViewStore(state => state.showNavigation);
const showHelp = useSingleViewStore(state => state.showHelp);
const setLastId = useSingleViewStore(state => state.setLastId);
const setLastIndex = useSingleViewStore(state => state.setLastIndex);
const search = useSearchStore(state => state.search);
const setShowDetails = useSingleViewStore(actions => actions.setShowDetails);
const setShowAnnotations = useSingleViewStore(actions => actions.setShowAnnotations);
const setShowNavigation = useSingleViewStore(actions => actions.setShowNavigation);

const setShowHelp = useSingleViewStore(actions => actions.setShowHelp);
const [hideNavigation, setHideNavigation] = useState(false)

let index = findEntryIndex(location, entries, id);
Expand Down Expand Up @@ -147,6 +151,8 @@ export const MediaView = () => {
navigate(`/search/${encodeUrl(action.query)}`);
} else if (type == 'map') {
navigate(`/map?lat=${current.latitude.toFixed(5)}&lng=${current.longitude.toFixed(5)}&zoom=14`, {state: {listLocation}})
} else if (type == 'help') {
setShowHelp(!showHelp);
}
}

Expand Down Expand Up @@ -208,7 +214,8 @@ export const MediaView = () => {
</div>
{ showDetails &&
<div className="md:w-90">
<Details entry={current} dispatch={dispatch} />
<Details entry={current} dispatch={dispatch}/>
<HelpModal showHelp={showHelp} setShowHelp={setShowHelp} dispatch={dispatch}/>
</div>
}
</div>
Expand Down
71 changes: 39 additions & 32 deletions packages/webapp/src/store/single-view-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,48 @@ import { create } from 'zustand'
import { persist } from 'zustand/middleware'

export interface SingleViewStore {
lastId: string
lastIndex: number
showDetails: boolean
showAnnotations: boolean
showNavigation: boolean

setLastId(lastId: string): void
setLastIndex(lastIndex: number): void
setShowDetails(show: boolean): void
setShowAnnotations(show: boolean): void
setShowNavigation(show: boolean): void
lastId: string
lastIndex: number
showDetails: boolean
showAnnotations: boolean
showNavigation: boolean
showHelp: boolean

setLastId(lastId: string): void

setLastIndex(lastIndex: number): void

setShowDetails(show: boolean): void

setShowAnnotations(show: boolean): voidsetShowNavigation(show: boolean): void

setShowHelp(show: boolean): void
}

const excludeStateProps = (excludeProps: string[] = []) => (state: any): any => Object.fromEntries(
Object.entries(state).filter(([key]) => !excludeProps.includes(key)))
Object.entries(state).filter(([key]) => !excludeProps.includes(key)))

export const useSingleViewStore = create<
SingleViewStore,
[
["zustand/persist", SingleViewStore]
]
SingleViewStore,
[
["zustand/persist", SingleViewStore]
]
>(
persist((set) => ({
lastId: '',
lastIndex: -1,
showDetails: false,
showAnnotations: false,
showNavigation: true,

setLastId: (lastId: string) => set((state: SingleViewStore) => ({...state, lastId})),
setLastIndex: (lastIndex: number) => set((state: SingleViewStore) => ({...state, lastIndex})),
setShowDetails: (show: boolean) => set((state: SingleViewStore) => ({...state, showDetails: show})),
setShowAnnotations: (show: boolean) => set((state: SingleViewStore) => ({...state, showAnnotations: show})),
setShowNavigation: (show: boolean) => set((state: SingleViewStore) => ({...state, showNavigation: show})),
}), {
name: 'gallery-single-view',
partialize: excludeStateProps(['lastId', 'lastIndex']),
}))
persist((set) => ({
lastId: '',
lastIndex: -1,
showDetails: false,
showAnnotations: false,
showNavigation: true,
showHelp: false,

setLastId: (lastId: string) => set((state: SingleViewStore) => ({...state, lastId})),
setLastIndex: (lastIndex: number) => set((state: SingleViewStore) => ({...state, lastIndex})),
setShowDetails: (show: boolean) => set((state: SingleViewStore) => ({...state, showDetails: show})),
setShowAnnotations: (show: boolean) => set((state: SingleViewStore) => ({...state, showAnnotations: show})),
setShowNavigation: (show: boolean) => set((state: SingleViewStore) => ({...state, showNavigation: show})),
setShowHelp: (show: boolean) => set((state: SingleViewStore) => ({...state, showHelp: show})),
}), {
name: 'gallery-single-view',
partialize: excludeStateProps(['lastId', 'lastIndex']),
}))

0 comments on commit 429d73a

Please sign in to comment.