Skip to content
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

Closed
wants to merge 10 commits into from
75 changes: 64 additions & 11 deletions apps/web/src/routes/AllView/AllView.jsx
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'
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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)
Copy link
Collaborator

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?

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()}`)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 }))
Copy link
Collaborator

Choose a reason for hiding this comment

The 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'>
Expand Down
Loading