From c72b912d76086e0a674e4fc333148547a88c8310 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 18:43:21 +0800 Subject: [PATCH] More work on editing shortcuts --- .../web/components/elements/StyledText.tsx | 4 + .../templates/navMenu/LibraryMenu.tsx | 2 +- packages/web/pages/settings/shortcuts.tsx | 384 ++++++++++++++++-- 3 files changed, 355 insertions(+), 35 deletions(-) diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index 22c782ab0..50ad458fa 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -42,6 +42,10 @@ const textVariants = { fontSize: '17px', fontFamily: '$inter', color: '$grayText', + m: '0px', + my: '15px', + marginBlockStart: '0px', + marginBlockEnd: '0px', }, settingsItem: { fontSize: '13px', diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index e82bda863..693030058 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -227,7 +227,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { const router = useRouter() const [shortcuts] = usePersistedState({ - key: 'shortcuts', + key: 'library-shortcuts', isSessionStorage: false, initialValue: [], }) diff --git a/packages/web/pages/settings/shortcuts.tsx b/packages/web/pages/settings/shortcuts.tsx index 5caf39fe5..1ee7d39f0 100644 --- a/packages/web/pages/settings/shortcuts.tsx +++ b/packages/web/pages/settings/shortcuts.tsx @@ -1,4 +1,11 @@ -import { useMemo, useState } from 'react' +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useReducer, + useState, +} from 'react' import { applyStoredTheme } from '../../lib/themeUpdater' import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery' @@ -10,16 +17,27 @@ import { VStack, HStack, SpanBox, + Separator, } from '../../components/elements/LayoutPrimitives' import { LabelChip } from '../../components/elements/LabelChip' -import { Checkbox } from '@radix-ui/react-checkbox' import { StyledText } from '../../components/elements/StyledText' -import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' +import { + Subscription, + SubscriptionType, + useGetSubscriptionsQuery, +} from '../../lib/networking/queries/useGetSubscriptionsQuery' import { DragIcon } from '../../components/elements/icons/DragIcon' import { CoverImage } from '../../components/elements/CoverImage' import { Label } from '../../lib/networking/fragments/labelFragment' import { usePersistedState } from '../../lib/hooks/usePersistedState' import { CheckSquare, Square } from 'phosphor-react' +import { Button } from '../../components/elements/Button' +import { styled } from '@stitches/react' +import { SavedSearch } from '../../lib/networking/fragments/savedSearchFragment' + +type ListAction = 'RESET' | 'ADD_ITEM' | 'REMOVE_ITEM' + +const SHORTCUTS_KEY = 'library-shortcuts' export default function Shortcuts(): JSX.Element { applyStoredTheme() @@ -27,9 +45,80 @@ export default function Shortcuts(): JSX.Element { 'legacy' | 'shortcuts' >({ key: 'library-nav-menu-style', - initialValue: 'shortcuts', + initialValue: 'legacy', }) + const listReducer = ( + state: { state: string; items: Shortcut[] }, + action: { + type: ListAction + item?: Shortcut + } + ) => { + switch (action.type) { + case 'RESET': { + const itemStr = window['localStorage'].getItem(SHORTCUTS_KEY) + if (itemStr) { + try { + const parsed = JSON.parse(itemStr) + if (Array.isArray(parsed)) { + return { state: 'CURRENT', items: parsed as Shortcut[] } + } + } catch (err) { + console.log('error: ', err) + } + } + return { state: 'CURRENT', items: [] } + } + case 'ADD_ITEM': { + const item = action.item + if (!item) { + return state + } + const existing = state.items.find( + (existing) => existing.type == item.type && existing.id == item.id + ) + if (existing) { + return state + } + state.items.push(item) + return { state: 'CURRENT', items: [...state.items] } + } + case 'REMOVE_ITEM': { + const item = action.item + if (!item) { + return state + } + const updated = state.items.filter((existing) => existing.id != item.id) + return { state: 'CURRENT', items: [...updated] } + } + default: + throw new Error('unknown action') + } + } + + const [shortcuts, dispatchList] = useReducer(listReducer, { + state: 'INITIAL', + items: [], + }) + + useEffect(() => { + try { + if (shortcuts.state == 'CURRENT') { + window['localStorage'].setItem( + SHORTCUTS_KEY, + JSON.stringify(shortcuts.items) + ) + } + } catch (error) { + console.log('error": ', error) + } + }, [shortcuts]) + + useEffect(() => { + dispatchList({ type: 'RESET' }) + }, []) + return ( { - // setHidePinnedSearches(!hidePinnedSearches) setNavMenuStyle( navMenuStyle == 'shortcuts' ? 'legacy' : 'shortcuts' ) @@ -116,8 +204,14 @@ export default function Shortcuts(): JSX.Element { }, }} > - - + + @@ -125,13 +219,22 @@ export default function Shortcuts(): JSX.Element { ) } -const AvailableItems = (): JSX.Element => { +export const SectionSeparator = styled(Separator, { + height: '1px', + my: '30px', + backgroundColor: '$grayBorder', +}) + +type ListProps = { + shortcuts: Shortcut[] + dispatchList: (arg: { type: ListAction; item?: Shortcut | undefined }) => void +} + +const AvailableItems = (props: ListProps): JSX.Element => { const { labels } = useGetLabelsQuery() const { savedSearches } = useGetSavedSearchQuery() const { subscriptions } = useGetSubscriptionsQuery() - console.log('subscriptions:', subscriptions) - const sortedLabels = useMemo(() => { if (!labels) { return [] @@ -158,6 +261,30 @@ const AvailableItems = (): JSX.Element => { a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) ) }, [savedSearches]) + + const searchSelected = useCallback( + (search: SavedSearch) => { + return !!props.shortcuts.find((shortcut) => shortcut.id == search.id) + }, + [props] + ) + + const labelSelected = useCallback( + (label: Label) => { + return !!props.shortcuts.find((shortcut) => shortcut.id == label.id) + }, + [props] + ) + + const subscriptionSelected = useCallback( + (subscription: Subscription) => { + return !!props.shortcuts.find( + (shortcut) => shortcut.id == subscription.id + ) + }, + [props] + ) + return ( { py: '30px', pl: '28px', // becomes labels have some margin built in pr: '30px', - gap: '15px', + gap: '10px', bg: '$thLeftMenuBackground', }} > Saved Searches {sortedsavedSearches?.map((search) => { return ( - + ) })} + + Labels {sortedLabels.map((label) => { return ( - + ) })} + + Subscriptions {sortedSubscriptions.map((subscription) => { return ( - { + const item: Shortcut = { + id: subscription.id, + name: subscription.name, + icon: subscription.icon, + type: + subscription.type == SubscriptionType.NEWSLETTER + ? 'newsletter' + : 'feed', + filter: + subscription.type == SubscriptionType.NEWSLETTER + ? `subscription:\"${subscription.name}\"` + : `rss:\"${subscription.url}\"`, + } + props.dispatchList({ + item, + type: subscriptionSelected(subscription) + ? 'REMOVE_ITEM' + : 'ADD_ITEM', + }) + + event.preventDefault() + }} > {subscription.name} - + {subscriptionSelected(subscription) ? ( + + ) : ( + + )} - + ) })} ) } +type AvailableItemButtonProps = { + shortcut: Shortcut + isSelected: boolean + listAction: (arg: { type: ListAction; item?: Shortcut | undefined }) => void +} + +const AvailableItemButton = (props: AvailableItemButtonProps): JSX.Element => { + const shortcutId = `checkbox-search-${props.shortcut.id}` + return ( + + {props.shortcut.name} + + ) +} + export type Shortcut = { type: 'search' | 'label' | 'newsletter' | 'feed' @@ -220,17 +476,7 @@ export type Shortcut = { label?: Label } -const SelectedItems = (): JSX.Element => { - const shortcuts = [ - { - id: '12asdfasdf', - name: 'Omnivore Blog', - icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'subscription:"Money Talk"', - type: 'newsletter', - }, - ] - +const SelectedItems = (props: ListProps): JSX.Element => { return ( { bg: '$thLeftMenuBackground', }} > - Shortcuts - {shortcuts.map((shortcut) => { + Your shortcuts + {props.shortcuts.map((shortcut) => { return ( { ) } + +type CheckboxButtonProps = { + itemKey: string + title: string + isSelected: boolean + item: Shortcut + + listAction: (arg: { type: ListAction; item?: Shortcut | undefined }) => void + children: ReactNode +} + +function CheckboxButton(props: CheckboxButtonProps): JSX.Element { + const handleChange = useCallback( + (selected: boolean) => { + if (!selected) { + props.listAction({ + type: 'REMOVE_ITEM', + item: props.item, + }) + } else { + props.listAction({ + type: 'ADD_ITEM', + item: props.item, + }) + } + }, + [props] + ) + return ( + { + handleChange(!props.isSelected) + event.preventDefault() + }} + > + {props.isSelected ? ( + + ) : ( + + )} + {props.children} + + ) +}