-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
334 scroll the context menu to the added widget #353
Changes from all commits
d519740
140f131
016ed02
31d9826
e3bc487
328a7a0
74dd1db
566e545
b8b172d
544fb09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import React, { useMemo, useCallback, useState, useEffect } from 'react' | ||
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react' | ||
import { useSelector, useDispatch } from 'react-redux' | ||
import { useTranslation } from 'react-i18next' | ||
import { useLocation, useNavigate, useParams } from 'react-router-dom' | ||
|
@@ -149,7 +149,7 @@ export default function AllViews () { | |
</div> | ||
) | ||
return ( | ||
<div key={widget.id} onClick={() => url ? navigate(url) : null} className={`p-4 border border-background rounded-md shadow-sm ${url ? 'cursor-pointer' : ''}`}> | ||
<div key={widget.id} onClick={() => url ? navigate(url) : null} className={`cursor-pointer relative flex flex-col transition-all bg-card/40 border-2 border-card/30 shadow-md hover:shadow-lg mb-4 hover:z-50 hover:scale-105 duration-400 rounded-lg h-full ${url ? 'cursor-pointer' : ''}`}> | ||
<div className='block text-center text-foreground'> | ||
{cardContent} | ||
</div> | ||
|
@@ -170,12 +170,12 @@ export default function AllViews () { | |
|
||
return ( | ||
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4'> | ||
<div onClick={() => navigate(addQuerystringToPath(location.pathname, { addview: 'yes', cme: 'yes' }))} className='p-4 border border-gray-300 rounded-md shadow-sm cursor-pointer'> | ||
<div onClick={() => navigate(addQuerystringToPath(location.pathname, { addview: 'yes', cme: 'yes' }))} className='border-2 flex items-center justify-center border-t-foreground/30 border-x-foreground/20 border-b-foreground/10 p-4 background-black/10 rounded-lg border-dashed relative'> | ||
<div className='block text-center'> | ||
<div> | ||
<h3 className='text-lg font-semibold text-foreground'>{t('Add View')}</h3> | ||
<h3 className='text-lg font-semibold text-foreground m-0'>{t('Add View')}</h3> | ||
<span className='text-sm text-gray-600 block'> | ||
<Icon name='Plus' style={{ fontSize: 30 }} /> | ||
<Icon name='Plus' style={{ fontSize: 30 }} className='text-foreground' /> | ||
</span> | ||
</div> | ||
</div> | ||
|
@@ -197,6 +197,11 @@ function AddOption ({ title, onClick, description, disabled = false }) { | |
) | ||
} | ||
|
||
/** | ||
* Dialog for adding new views to the context menu | ||
* Handles the creation process and communicates with ContextMenu component | ||
* for proper scrolling and highlighting of newly created items | ||
*/ | ||
function AddViewDialog ({ group, orderInFrontOfWidgetId, parentId, addToEnd, parentWidget }) { | ||
const { t } = useTranslation() | ||
const location = useLocation() | ||
|
@@ -212,12 +217,22 @@ function AddViewDialog ({ group, orderInFrontOfWidgetId, parentId, addToEnd, par | |
const [widgetData, setWidgetData] = useState({ title: '', visibility: 'all' }) | ||
const [isCreating, setIsCreating] = useState(false) | ||
|
||
// Store the widget ID between renders | ||
const createdWidgetIdRef = useRef(null) | ||
|
||
const handleReset = () => { | ||
setAddChoice(null) | ||
setSelectedItem(null) | ||
setWidgetData({ title: '', visibility: 'all' }) | ||
} | ||
|
||
/** | ||
* Creates a new context widget and communicates its ID to the ContextMenu component | ||
* Uses multiple communication channels for reliability: | ||
* 1. URL parameters - Main method for direct communication | ||
* 2. LocalStorage - Backup in case URL params get cleared | ||
* 3. Custom events - For real-time notification | ||
*/ | ||
const handleCreate = useCallback(async ({ widgetData, selectedItem, addChoice }) => { | ||
setIsCreating(true) | ||
let groupTopic | ||
|
@@ -234,7 +249,7 @@ function AddViewDialog ({ group, orderInFrontOfWidgetId, parentId, addToEnd, par | |
visibility: widgetData.visibility === 'all' ? null : widgetData.visibility, | ||
type: addChoice === CHAT ? CHAT : null, // The default is for type to be null unless there is a specific need | ||
title: widgetData.title === '' ? null : widgetData.title, | ||
icon: null, // TODO CONTEXT: what is required for icons? | ||
icon: null, | ||
viewGroupId: addChoice === GROUP ? selectedItem.id : null, | ||
viewPostId: addChoice === POST ? selectedItem.id : null, | ||
customViewInput: addChoice === CUSTOM_VIEW ? cleanCustomView(selectedItem) : null, | ||
|
@@ -244,17 +259,55 @@ function AddViewDialog ({ group, orderInFrontOfWidgetId, parentId, addToEnd, par | |
orderInFrontOfWidgetId | ||
} | ||
|
||
// Widget will be inserted into the menu as a 'loading' widget, and then properly inserted when returned from the db | ||
try { | ||
await dispatch(createContextWidget({ data: contextWidgetInput, groupId: group.id })) | ||
handleReset() | ||
navigate(addQuerystringToPath(location.pathname, { cme: 'yes' })) | ||
const result = await dispatch(createContextWidget({ data: contextWidgetInput, groupId: group.id })) | ||
|
||
// Extract the new widget ID from the response | ||
const newWidgetId = result?.payload?.data?.createContextWidget?.id | ||
|
||
if (newWidgetId) { | ||
// Save the ID in our ref for post-navigation use | ||
createdWidgetIdRef.current = newWidgetId | ||
|
||
// Method 1: Store in localStorage as a backup communication method (expires after 5 seconds) | ||
localStorage.setItem('hylo:last-created-widget', newWidgetId) | ||
localStorage.setItem('hylo:last-created-timestamp', Date.now().toString()) | ||
|
||
// Method 2: Dispatch event for immediate notification | ||
window.dispatchEvent(new CustomEvent('hylo:new-widget-added', { | ||
detail: { widgetId: newWidgetId, timestamp: Date.now() } | ||
})) | ||
|
||
// Create redirect URL with the widget ID | ||
const baseGroupUrl = baseUrl({ groupSlug: group.slug, view: null }) | ||
|
||
// Method 3: Include widget ID in URL parameters | ||
const urlParams = new URLSearchParams() | ||
urlParams.set('newWidgetId', newWidgetId) | ||
|
||
// First, reset the current dialog state | ||
handleReset() | ||
|
||
// Then navigate to the group page with the new widget ID in URL | ||
navigate(`${baseGroupUrl}?${urlParams.toString()}`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think we probably want to stay in the edit mode, why navigate to the base URL? |
||
|
||
// Dispatch the event again after a delay to handle component remounts during navigation | ||
setTimeout(() => { | ||
window.dispatchEvent(new CustomEvent('hylo:new-widget-added', { | ||
detail: { widgetId: newWidgetId, timestamp: Date.now() } | ||
})) | ||
}, 500) | ||
} else { | ||
handleReset() | ||
navigate(baseUrl({ groupSlug: group.slug, view: null })) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on failure, why are we navigating away? i think we should stay on the edit menu page. and probably log an error |
||
} | ||
} catch (error) { | ||
console.error('Failed to create context widget:', error) | ||
handleReset() | ||
} finally { | ||
setIsCreating(false) | ||
} | ||
}, []) | ||
}, [dispatch, group.id, group.slug, navigate, orderInFrontOfWidgetId, parentId]) | ||
|
||
return ( | ||
<div className='fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50'> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we actually need these three methods? what different use cases are they for? can we get it working with just one?