From 49ec23f6eabed03b73f2a4e2695359689d993d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Vivet?= Date: Fri, 13 Feb 2026 01:21:55 +0100 Subject: [PATCH 1/7] raw --- Core/GDCore/Events/Event.h | 15 ++ Core/GDCore/Events/Serialization.cpp | 4 + .../EventsSheet/Bookmarks/BookmarksPanel.js | 138 ++++++++++++++++ .../EventsSheet/Bookmarks/BookmarksUtils.js | 143 +++++++++++++++++ newIDE/app/src/EventsSheet/Toolbar.js | 12 ++ newIDE/app/src/EventsSheet/index.js | 149 ++++++++++++++++++ 6 files changed, 461 insertions(+) create mode 100644 newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js create mode 100644 newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js diff --git a/Core/GDCore/Events/Event.h b/Core/GDCore/Events/Event.h index 46917f36aca2..6be9d2615a8d 100644 --- a/Core/GDCore/Events/Event.h +++ b/Core/GDCore/Events/Event.h @@ -300,6 +300,20 @@ class GD_CORE_API BaseEvent { const gd::String& GetAiGeneratedEventId() const { return aiGeneratedEventId; } + + /** + * \brief Set the event bookmark ID. + */ + void SetEventBookmarkId(const gd::String& eventBookmarkId_) { + eventBookmarkId = eventBookmarkId_; + } + + /** + * \brief Get the event bookmark ID. + */ + const gd::String& GetEventBookmarkId() const { + return eventBookmarkId; + } ///@} std::weak_ptr @@ -319,6 +333,7 @@ class GD_CORE_API BaseEvent { gd::String type; ///< Type of the event. Must be assigned at the creation. ///< Used for saving the event for instance. gd::String aiGeneratedEventId; ///< When generated by an AI/external tool. + gd::String eventBookmarkId; ///< Bookmark identifier for the event. static gd::EventsList badSubEvents; static gd::VariablesContainer badLocalVariables; diff --git a/Core/GDCore/Events/Serialization.cpp b/Core/GDCore/Events/Serialization.cpp index ec1a8bc2c6b8..c8c6d176f48d 100644 --- a/Core/GDCore/Events/Serialization.cpp +++ b/Core/GDCore/Events/Serialization.cpp @@ -223,6 +223,8 @@ void EventsListSerialization::UnserializeEventsFrom( event->SetFolded(eventElem.GetBoolAttribute("folded", false)); event->SetAiGeneratedEventId( eventElem.GetStringAttribute("aiGeneratedEventId", "")); + event->SetEventBookmarkId( + eventElem.GetStringAttribute("eventBookmarkId", "")); list.InsertEvent(event, list.GetEventsCount()); } @@ -240,6 +242,8 @@ void EventsListSerialization::SerializeEventsTo(const EventsList& list, if (event.IsFolded()) eventElem.SetAttribute("folded", event.IsFolded()); if (!event.GetAiGeneratedEventId().empty()) eventElem.SetAttribute("aiGeneratedEventId", event.GetAiGeneratedEventId()); + if (!event.GetEventBookmarkId().empty()) + eventElem.SetAttribute("eventBookmarkId", event.GetEventBookmarkId()); eventElem.AddChild("type").SetValue(event.GetType()); event.SerializeTo(eventElem); diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js new file mode 100644 index 000000000000..a5494b89014e --- /dev/null +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js @@ -0,0 +1,138 @@ +// @flow +import { Trans, t } from '@lingui/macro'; +import * as React from 'react'; +import Background from '../../UI/Background'; +import { Column, Line } from '../../UI/Grid'; +import IconButton from '../../UI/IconButton'; +import TextField from '../../UI/TextField'; +import Text from '../../UI/Text'; +import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; +import Cross from '../../UI/CustomSvgIcons/Cross'; +import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight'; +import Star from '@material-ui/icons/Star'; +import Delete from '@material-ui/icons/Delete'; +import { type Bookmark } from './BookmarksUtils'; +import EmptyMessage from '../../UI/EmptyMessage'; +import ScrollView from '../../UI/ScrollView'; +import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext'; + +type Props = {| + bookmarks: Array, + onNavigateToBookmark: (bookmark: Bookmark) => void, + onDeleteBookmark: (bookmarkId: string) => void, + onRenameBookmark: (bookmarkId: string, newName: string) => void, + onClose: () => void, +|}; + +const BookmarksPanel = ({ + bookmarks, + onNavigateToBookmark, + onDeleteBookmark, + onRenameBookmark, + onClose, +}: Props) => { + const gdevelopTheme = React.useContext(GDevelopThemeContext); + + const getEventTypeDisplayName = (eventType: string): string => { + const typeMap = { + 'BuiltinCommonInstructions::Standard': 'Standard Event', + 'BuiltinCommonInstructions::Comment': 'Comment', + 'BuiltinCommonInstructions::Group': 'Group', + 'BuiltinCommonInstructions::While': 'While', + 'BuiltinCommonInstructions::Repeat': 'Repeat', + 'BuiltinCommonInstructions::ForEach': 'For Each', + 'BuiltinCommonInstructions::Link': 'Link', + }; + + return ( + typeMap[eventType] || + eventType + .replace('BuiltinCommonInstructions::', '') + .replace(/([A-Z])/g, ' $1') + .trim() + ); + }; + + return ( + + + + + + Bookmarks + + + + + + + + + {bookmarks.length === 0 ? ( + + + No bookmarks yet. Right-click on an event to add a bookmark. + + + ) : ( + bookmarks.map(bookmark => ( + + + + + + onRenameBookmark(bookmark.id, value) + } + fullWidth + margin="none" + translatableHintText={t`Bookmark name`} + /> + + {getEventTypeDisplayName(bookmark.eventType)} + + + onNavigateToBookmark(bookmark)} + tooltip={t`Go to event`} + > + + + onDeleteBookmark(bookmark.id)} + tooltip={t`Delete bookmark`} + > + + + + + )) + )} + + + + + ); +}; + +export default BookmarksPanel; diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js new file mode 100644 index 000000000000..fa262556ee9e --- /dev/null +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js @@ -0,0 +1,143 @@ +// @flow + +const gd: libGDevelop = global.gd; + +export type Bookmark = {| + eventPtr: number, + name: string, + eventType: string, + id: string, + timestamp: number, +|}; + +/** + * Scan all events recursively and collect those that are bookmarked + */ +export const scanEventsForBookmarks = ( + events: gdEventsList +): Array => { + const bookmarks: Array = []; + + const scanEventsList = (eventsList: gdEventsList) => { + // Safety check: ensure eventsList is valid and has the size method + if (!eventsList || typeof eventsList.size !== 'function') { + return; + } + + for (let i = 0; i < eventsList.size(); i++) { + const event = eventsList.getEventAt(i); + + // Check if event has a bookmark ID + const bookmarkId = event.getEventBookmarkId(); + if (bookmarkId && bookmarkId.length > 0) { + const bookmark: Bookmark = { + eventPtr: event.ptr, + name: generateBookmarkName(event), + eventType: event.getType(), + id: bookmarkId, + timestamp: Date.now(), // We don't persist timestamp, so use current time + }; + bookmarks.push(bookmark); + } + + // Recursively scan sub-events + if (event.canHaveSubEvents && event.canHaveSubEvents()) { + const subEvents = event.getSubEvents(); + scanEventsList(subEvents); + } + } + }; + + scanEventsList(events); + return bookmarks; +}; + +/** + * Generate a default name for a bookmark based on the event + */ +export const generateBookmarkName = (event: gdBaseEvent): string => { + const eventType = event.getType(); + + // For comment events, use the comment text + if (eventType === 'BuiltinCommonInstructions::Comment') { + const commentEvent = gd.asCommentEvent(event); + const comment = commentEvent.getComment(); + if (comment.length > 0) { + return comment.length > 50 ? comment.substring(0, 50) + '...' : comment; + } + return 'Comment'; + } + + // For group events, use the group name + if (eventType === 'BuiltinCommonInstructions::Group') { + const groupEvent = gd.asGroupEvent(event); + const name = groupEvent.getName(); + if (name.length > 0) { + return name.length > 50 ? name.substring(0, 50) + '...' : name; + } + return 'Group'; + } + + // For standard events, try to extract first condition or action text + if (eventType === 'BuiltinCommonInstructions::Standard') { + const standardEvent = gd.asStandardEvent(event); + + // Try to get first condition + const conditions = standardEvent.getConditions(); + if (conditions.size() > 0) { + const firstCondition = conditions.get(0); + const type = firstCondition.getType(); + if (type.length > 0) { + const conditionText = type.replace(/:/g, ' '); + return conditionText.length > 50 + ? conditionText.substring(0, 50) + '...' + : conditionText; + } + } + + // Try to get first action + const actions = standardEvent.getActions(); + if (actions.size() > 0) { + const firstAction = actions.get(0); + const type = firstAction.getType(); + if (type.length > 0) { + const actionText = type.replace(/:/g, ' '); + return actionText.length > 50 + ? actionText.substring(0, 50) + '...' + : actionText; + } + } + + return 'Standard Event'; + } + + // For other event types, use a generic name based on the type + const readableType = eventType + .replace('BuiltinCommonInstructions::', '') + .replace(/([A-Z])/g, ' $1') + .trim(); + + return readableType || 'Event'; +}; + +/** + * Recursively search for an event by its pointer in an event list + */ +export const findEventByPtr = ( + events: gdEventsList, + ptr: number +): ?gdBaseEvent => { + for (let i = 0; i < events.size(); i++) { + const event = events.getEventAt(i); + if (event.ptr === ptr) return event; + + // Recursively search sub-events + if (event.canHaveSubEvents && event.canHaveSubEvents()) { + const subEvents = event.getSubEvents(); + const found = findEventByPtr(subEvents, ptr); + if (found) return found; + } + } + + return null; +}; diff --git a/newIDE/app/src/EventsSheet/Toolbar.js b/newIDE/app/src/EventsSheet/Toolbar.js index 1af61272bfe5..5dec1391da24 100644 --- a/newIDE/app/src/EventsSheet/Toolbar.js +++ b/newIDE/app/src/EventsSheet/Toolbar.js @@ -18,6 +18,7 @@ import ToolbarSearchIcon from '../UI/CustomSvgIcons/ToolbarSearch'; import EditSceneIcon from '../UI/CustomSvgIcons/EditScene'; import { getShortcutDisplayName, useShortcutMap } from '../KeyboardShortcuts'; import AddLocalVariableIcon from '../UI/CustomSvgIcons/LocalVariable'; +import Star from '@material-ui/icons/Star'; type Props = {| onAddStandardEvent: () => void, @@ -39,6 +40,7 @@ type Props = {| redo: () => void, canRedo: boolean, onToggleSearchPanel: () => void, + onToggleBookmarksPanel: () => void, onOpenSettings?: ?() => void, settingsIcon?: React.Node, moveEventsIntoNewGroup: () => void, @@ -66,6 +68,7 @@ const Toolbar = React.memo(function Toolbar({ redo, canRedo, onToggleSearchPanel, + onToggleBookmarksPanel, onOpenSettings, settingsIcon, moveEventsIntoNewGroup, @@ -224,6 +227,15 @@ const Toolbar = React.memo(function Toolbar({ > + + onToggleBookmarksPanel()} + tooltip={t`Bookmarks`} + > + + {onOpenSettings && } {onOpenSettings && ( , searchFocusOffset: ?number, + showBookmarksPanel: boolean, + bookmarks: Array, + layoutVariablesDialogOpen: boolean, allEventsMetadata: Array, @@ -311,6 +322,9 @@ export class EventsSheetComponentWithoutHandle extends React.Component< searchResults: null, searchFocusOffset: null, + showBookmarksPanel: false, + bookmarks: [], + layoutVariablesDialogOpen: false, allEventsMetadata: [], @@ -334,6 +348,10 @@ export class EventsSheetComponentWithoutHandle extends React.Component< this.resourceExternallyChangedCallbackId = registerOnResourceExternallyChangedCallback( this.onResourceExternallyChanged.bind(this) ); + + // Load bookmarks from localStorage and scan events + // Load bookmarks by scanning events + this._refreshBookmarks(); } componentWillUnmount() { unregisterOnResourceExternallyChangedCallback( @@ -417,6 +435,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component< onOpenSettings={this.props.onOpenSettings} settingsIcon={this.props.settingsIcon} onToggleSearchPanel={this._toggleSearchPanel} + onToggleBookmarksPanel={this._toggleBookmarksPanel} canMoveEventsIntoNewGroup={hasSomethingSelected(this.state.selection)} moveEventsIntoNewGroup={this.moveEventsIntoNewGroup} onOpenSceneVariables={this.editLayoutVariables} @@ -465,6 +484,112 @@ export class EventsSheetComponentWithoutHandle extends React.Component< this.setState({ showSearchPanel: false }); }; + _refreshBookmarks = () => { + if (!this.props.events) return; + const bookmarks = scanEventsForBookmarks(this.props.events); + this.setState({ bookmarks }); + }; + + _toggleBookmarksPanel = () => { + this.setState(prevState => ({ + showBookmarksPanel: !prevState.showBookmarksPanel, + })); + }; + + _addBookmark = () => { + const selectedEvents = getSelectedEventContexts(this.state.selection); + if (selectedEvents.length === 0) return; + + const eventContext = selectedEvents[selectedEvents.length - 1]; + const { event } = eventContext; + + // Check if already bookmarked + const existingBookmarkId = event.getEventBookmarkId(); + if (existingBookmarkId && existingBookmarkId.length > 0) return; + + // Set bookmark ID on the event + const bookmarkId = uuidv4(); + + event.setEventBookmarkId(bookmarkId); + + // Refresh bookmarks from events + this._refreshBookmarks(); + + // Trigger unsaved changes + if (this.props.unsavedChanges) { + this.props.unsavedChanges.triggerUnsavedChanges(); + } + }; + + _removeBookmark = () => { + const selectedEvents = getSelectedEventContexts(this.state.selection); + if (selectedEvents.length === 0) return; + + const eventContext = selectedEvents[selectedEvents.length - 1]; + const { event } = eventContext; + + // Clear bookmark ID from the event + event.setEventBookmarkId(''); + + // Refresh bookmarks from events + this._refreshBookmarks(); + + // Trigger unsaved changes + if (this.props.unsavedChanges) { + this.props.unsavedChanges.triggerUnsavedChanges(); + } + }; + + _isEventBookmarked = (): boolean => { + const selectedEvents = getSelectedEventContexts(this.state.selection); + if (selectedEvents.length === 0) return false; + + const eventContext = selectedEvents[selectedEvents.length - 1]; + const { event } = eventContext; + + const bookmarkId = event.getEventBookmarkId(); + return bookmarkId && bookmarkId.length > 0; + }; + + _navigateToBookmark = (bookmark: Bookmark) => { + const event = findEventByPtr(this.props.events, bookmark.eventPtr); + + if (!event) { + // Event no longer exists - remove bookmark + this._deleteBookmark(bookmark.id); + return; + } + + // Use existing navigation pattern + this._ensureUnfoldedAndScrollTo(() => event); + }; + + _deleteBookmark = (bookmarkId: string) => { + // Find the event with this bookmark ID and clear it + const bookmark = this.state.bookmarks.find(b => b.id === bookmarkId); + if (!bookmark) return; + + const event = findEventByPtr(this.props.events, bookmark.eventPtr); + if (event) { + event.setEventBookmarkId(''); + + // Trigger unsaved changes + if (this.props.unsavedChanges) { + this.props.unsavedChanges.triggerUnsavedChanges(); + } + } + + // Refresh bookmarks from events + this._refreshBookmarks(); + }; + + _renameBookmark = (bookmarkId: string, newName: string) => { + // Bookmark names are generated dynamically from event content. + // To change a bookmark name, the user should edit the event itself. + // This method is kept for future extensibility but currently does nothing. + console.warn('Bookmark renaming is not supported. Edit the event to change its description.'); + }; + addSubEvent = () => { const { project } = this.props; @@ -899,6 +1024,15 @@ export class EventsSheetComponentWithoutHandle extends React.Component< visible: this._selectionIsElseEvent(), }, { type: 'separator' }, + { + label: this._isEventBookmarked() + ? i18n._(t`Remove Bookmark`) + : i18n._(t`Add Bookmark`), + click: () => + this._isEventBookmarked() ? this._removeBookmark() : this._addBookmark(), + visible: hasEventSelected(this.state.selection), + }, + { type: 'separator' }, { label: i18n._(t`Add`), submenu: [ @@ -2147,6 +2281,21 @@ export class EventsSheetComponentWithoutHandle extends React.Component< /> )} + {this.state.showBookmarksPanel && ( + Bookmarks panel} + scope="scene-events-bookmarks" + onClose={() => this.setState({ showBookmarksPanel: false })} + > + this.setState({ showBookmarksPanel: false })} + /> + + )} Date: Fri, 13 Feb 2026 01:26:57 +0100 Subject: [PATCH 2/7] binding for compilation --- GDevelop.js/Bindings/Bindings.idl | 3 +++ GDevelop.js/types.d.ts | 2 ++ GDevelop.js/types/gdbaseevent.js | 2 ++ 3 files changed, 7 insertions(+) diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 3803d134ed0c..632b01b4f338 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2462,6 +2462,9 @@ interface BaseEvent { [Const, Ref] DOMString GetAiGeneratedEventId(); void SetAiGeneratedEventId([Const] DOMString aiGeneratedEventId); + + [Const, Ref] DOMString GetEventBookmarkId(); + void SetEventBookmarkId([Const] DOMString eventBookmarkId); }; interface StandardEvent { diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 61e2e2bdb60d..1d5a9345e9a7 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1893,6 +1893,8 @@ export class BaseEvent extends EmscriptenObject { unserializeFrom(project: Project, element: SerializerElement): void; getAiGeneratedEventId(): string; setAiGeneratedEventId(aiGeneratedEventId: string): void; + getEventBookmarkId(): string; + setEventBookmarkId(eventBookmarkId: string): void; } export class StandardEvent extends BaseEvent { diff --git a/GDevelop.js/types/gdbaseevent.js b/GDevelop.js/types/gdbaseevent.js index 161ef1dbae38..d9be90ddf21e 100644 --- a/GDevelop.js/types/gdbaseevent.js +++ b/GDevelop.js/types/gdbaseevent.js @@ -19,6 +19,8 @@ declare class gdBaseEvent extends gdBaseEvent { unserializeFrom(project: gdProject, element: gdSerializerElement): void; getAiGeneratedEventId(): string; setAiGeneratedEventId(aiGeneratedEventId: string): void; + getEventBookmarkId(): string; + setEventBookmarkId(eventBookmarkId: string): void; delete(): void; ptr: number; }; \ No newline at end of file From 56df8e4ece40bb528cabbf299344f6893ac282dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Vivet?= Date: Fri, 13 Feb 2026 11:30:44 +0100 Subject: [PATCH 3/7] It works --- .../EventsSheet/Bookmarks/BookmarksPanel.js | 139 +++++++++--------- .../EventsSheet/Bookmarks/BookmarksUtils.js | 8 +- newIDE/app/src/EventsSheet/index.js | 15 +- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js index a5494b89014e..170b5cf0b52b 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js @@ -4,13 +4,12 @@ import * as React from 'react'; import Background from '../../UI/Background'; import { Column, Line } from '../../UI/Grid'; import IconButton from '../../UI/IconButton'; -import TextField from '../../UI/TextField'; import Text from '../../UI/Text'; import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; -import Cross from '../../UI/CustomSvgIcons/Cross'; -import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight'; +import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal'; import Star from '@material-ui/icons/Star'; import Delete from '@material-ui/icons/Delete'; +import Cross from '../../UI/CustomSvgIcons/Cross'; import { type Bookmark } from './BookmarksUtils'; import EmptyMessage from '../../UI/EmptyMessage'; import ScrollView from '../../UI/ScrollView'; @@ -24,6 +23,15 @@ type Props = {| onClose: () => void, |}; +const styles = { + emptyContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '20px', + }, +}; + const BookmarksPanel = ({ bookmarks, onNavigateToBookmark, @@ -55,82 +63,75 @@ const BookmarksPanel = ({ return ( - - - + + + + Bookmarks - - - - - + + + + + + + {bookmarks.length === 0 ? ( +
+ + + No bookmarks yet. Right-click on an event to add a bookmark. + + +
+ ) : ( - {bookmarks.length === 0 ? ( - - - No bookmarks yet. Right-click on an event to add a bookmark. - - - ) : ( - bookmarks.map(bookmark => ( - - - - - - onRenameBookmark(bookmark.id, value) - } - fullWidth - margin="none" - translatableHintText={t`Bookmark name`} - /> - + {bookmarks.map(bookmark => ( + + + onNavigateToBookmark(bookmark)} + tooltip={t`Go to event`} + > + + + + + {getEventTypeDisplayName(bookmark.eventType)} - onNavigateToBookmark(bookmark)} - tooltip={t`Go to event`} - > - - - onDeleteBookmark(bookmark.id)} - tooltip={t`Delete bookmark`} - > - - - - - )) - )} + + + {bookmark.name} + + + + onDeleteBookmark(bookmark.id)} + tooltip={t`Delete bookmark`} + > + + + + + ))} -
+ )}
); }; diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js index fa262556ee9e..a0629edecb33 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js @@ -19,12 +19,12 @@ export const scanEventsForBookmarks = ( const bookmarks: Array = []; const scanEventsList = (eventsList: gdEventsList) => { - // Safety check: ensure eventsList is valid and has the size method - if (!eventsList || typeof eventsList.size !== 'function') { + // Safety check: ensure eventsList is valid and has the getEventsCount method + if (!eventsList || typeof eventsList.getEventsCount !== 'function') { return; } - for (let i = 0; i < eventsList.size(); i++) { + for (let i = 0; i < eventsList.getEventsCount(); i++) { const event = eventsList.getEventAt(i); // Check if event has a bookmark ID @@ -127,7 +127,7 @@ export const findEventByPtr = ( events: gdEventsList, ptr: number ): ?gdBaseEvent => { - for (let i = 0; i < events.size(); i++) { + for (let i = 0; i < events.getEventsCount(); i++) { const event = events.getEventAt(i); if (event.ptr === ptr) return event; diff --git a/newIDE/app/src/EventsSheet/index.js b/newIDE/app/src/EventsSheet/index.js index 6ba00ef42d09..0c6190c980d3 100644 --- a/newIDE/app/src/EventsSheet/index.js +++ b/newIDE/app/src/EventsSheet/index.js @@ -491,9 +491,17 @@ export class EventsSheetComponentWithoutHandle extends React.Component< }; _toggleBookmarksPanel = () => { - this.setState(prevState => ({ - showBookmarksPanel: !prevState.showBookmarksPanel, - })); + this.setState( + prevState => ({ + showBookmarksPanel: !prevState.showBookmarksPanel, + }), + () => { + // Refresh bookmarks when opening the panel + if (this.state.showBookmarksPanel) { + this._refreshBookmarks(); + } + } + ); }; _addBookmark = () => { @@ -509,7 +517,6 @@ export class EventsSheetComponentWithoutHandle extends React.Component< // Set bookmark ID on the event const bookmarkId = uuidv4(); - event.setEventBookmarkId(bookmarkId); // Refresh bookmarks from events From 1bac0cd1428add8244ad6150e12305c4e64aff31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Vivet?= Date: Tue, 24 Feb 2026 00:30:11 +0100 Subject: [PATCH 4/7] Fonctionnel --- .../EventsSheet/Bookmarks/BookmarksPanel.js | 214 ++++++++++-------- .../EventsSheet/Bookmarks/BookmarksUtils.js | 61 +++++ .../EventsTree/SortableEventsTree.js | 12 +- .../app/src/EventsSheet/EventsTree/index.js | 8 +- newIDE/app/src/EventsSheet/Toolbar.js | 4 +- newIDE/app/src/EventsSheet/index.js | 53 +++-- 6 files changed, 239 insertions(+), 113 deletions(-) diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js index 170b5cf0b52b..08d34352e823 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js @@ -5,29 +5,69 @@ import Background from '../../UI/Background'; import { Column, Line } from '../../UI/Grid'; import IconButton from '../../UI/IconButton'; import Text from '../../UI/Text'; -import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; +import { LineStackLayout, ColumnStackLayout } from '../../UI/Layout'; +import StarBorder from '@material-ui/icons/StarBorder'; import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal'; -import Star from '@material-ui/icons/Star'; import Delete from '@material-ui/icons/Delete'; import Cross from '../../UI/CustomSvgIcons/Cross'; import { type Bookmark } from './BookmarksUtils'; import EmptyMessage from '../../UI/EmptyMessage'; import ScrollView from '../../UI/ScrollView'; import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext'; +import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer'; type Props = {| bookmarks: Array, onNavigateToBookmark: (bookmark: Bookmark) => void, onDeleteBookmark: (bookmarkId: string) => void, onRenameBookmark: (bookmarkId: string, newName: string) => void, + onFocusBookmark?: ?(bookmark: Bookmark) => void, onClose: () => void, + isOpen: boolean, |}; +const DRAWER_WIDTH = 280; +const BOTTOM_PANEL_HEIGHT = 300; + +const getDrawerContainerStyle = (isMobile: boolean, isOpen: boolean) => ({ + position: isMobile ? 'fixed' : 'absolute', + ...(isMobile + ? { + bottom: 0, + left: 0, + right: 0, + width: '100%', + height: BOTTOM_PANEL_HEIGHT, + transform: isOpen ? 'translateY(0)' : `translateY(${BOTTOM_PANEL_HEIGHT}px)`, + boxShadow: '0 -4px 12px rgba(0,0,0,0.2)', + } + : { + top: 0, + right: 0, + height: '100%', + width: DRAWER_WIDTH, + transform: isOpen ? 'translateX(0%)' : 'translateX(100%)', + boxShadow: '-4px 0 12px rgba(0,0,0,0.2)', + }), + zIndex: 10, + transition: 'transform 0.3s ease-in-out', + display: 'flex', + flexDirection: 'column', + pointerEvents: 'auto', +}); + const styles = { + backgroundWrapper: { + flex: 1, + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + }, emptyContainer: { display: 'flex', alignItems: 'center', justifyContent: 'center', + flex: 1, padding: '20px', }, }; @@ -37,102 +77,94 @@ const BookmarksPanel = ({ onNavigateToBookmark, onDeleteBookmark, onRenameBookmark, + onFocusBookmark, onClose, + isOpen, }: Props) => { const gdevelopTheme = React.useContext(GDevelopThemeContext); - - const getEventTypeDisplayName = (eventType: string): string => { - const typeMap = { - 'BuiltinCommonInstructions::Standard': 'Standard Event', - 'BuiltinCommonInstructions::Comment': 'Comment', - 'BuiltinCommonInstructions::Group': 'Group', - 'BuiltinCommonInstructions::While': 'While', - 'BuiltinCommonInstructions::Repeat': 'Repeat', - 'BuiltinCommonInstructions::ForEach': 'For Each', - 'BuiltinCommonInstructions::Link': 'Link', - }; - - return ( - typeMap[eventType] || - eventType - .replace('BuiltinCommonInstructions::', '') - .replace(/([A-Z])/g, ' $1') - .trim() - ); - }; + const { isMobile } = useResponsiveWindowSize(); return ( - - - - - - - Bookmarks - - - - - - - - {bookmarks.length === 0 ? ( -
- - - No bookmarks yet. Right-click on an event to add a bookmark. - - -
- ) : ( - - - {bookmarks.map(bookmark => ( - - - onNavigateToBookmark(bookmark)} - tooltip={t`Go to event`} - > - - - - - - {getEventTypeDisplayName(bookmark.eventType)} - - - - +
+ + + + + + + Bookmarks + + + + + + + + + {bookmarks.length === 0 ? ( +
+ + + No bookmarks yet. Right-click on an event to add it as bookmark. + + +
+ ) : ( + + + {bookmarks.map(bookmark => { + const rgbColor = bookmark.color + ? `rgb(${bookmark.color.replace(/;/g, ',')})` + : null; + + return ( +
+ { + onNavigateToBookmark(bookmark); + if (onFocusBookmark) { + onFocusBookmark(bookmark); + } + }} + tooltip={t`Go to event`} + > + + + {bookmark.name} - - - onDeleteBookmark(bookmark.id)} - tooltip={t`Delete bookmark`} - > - - - - - ))} - - - )} - + onDeleteBookmark(bookmark.id)} + tooltip={t`Delete bookmark`} + > + + +
+ ); + })} +
+
+ )} +
+
+
); }; diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js index a0629edecb33..db7f9d807a81 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js @@ -8,6 +8,7 @@ export type Bookmark = {| eventType: string, id: string, timestamp: number, + color?: ?string, |}; /** @@ -36,6 +37,7 @@ export const scanEventsForBookmarks = ( eventType: event.getType(), id: bookmarkId, timestamp: Date.now(), // We don't persist timestamp, so use current time + color: getEventColor(event), }; bookmarks.push(bookmark); } @@ -52,6 +54,35 @@ export const scanEventsForBookmarks = ( return bookmarks; }; +/** + * Extract color from an event if it's a Group or Comment + */ +export const getEventColor = (event: gdBaseEvent): ?string => { + const eventType = event.getType(); + + // For group events, get the color (format: "R;G;B") + if (eventType === 'BuiltinCommonInstructions::Group') { + const groupEvent = gd.asGroupEvent(event); + const r = groupEvent.getBackgroundColorR(); + const g = groupEvent.getBackgroundColorG(); + const b = groupEvent.getBackgroundColorB(); + // Return in format "R;G;B" for consistency + return `${r};${g};${b}`; + } + + // For comment events, get the background color (format: "R;G;B") + if (eventType === 'BuiltinCommonInstructions::Comment') { + const commentEvent = gd.asCommentEvent(event); + const r = commentEvent.getBackgroundColorRed(); + const g = commentEvent.getBackgroundColorGreen(); + const b = commentEvent.getBackgroundColorBlue(); + // Return in format "R;G;B" for consistency + return `${r};${g};${b}`; + } + + return null; +}; + /** * Generate a default name for a bookmark based on the event */ @@ -141,3 +172,33 @@ export const findEventByPtr = ( return null; }; + +/** + * Recursively search for an event by its pointer and return it with its parent list and index + */ +export type EventLocation = {| + event: gdBaseEvent, + eventsList: gdEventsList, + indexInList: number, +|}; + +export const findEventLocationByPtr = ( + events: gdEventsList, + ptr: number +): ?EventLocation => { + for (let i = 0; i < events.getEventsCount(); i++) { + const event = events.getEventAt(i); + if (event.ptr === ptr) { + return { event, eventsList: events, indexInList: i }; + } + + // Recursively search sub-events + if (event.canHaveSubEvents && event.canHaveSubEvents()) { + const subEvents = event.getSubEvents(); + const found = findEventLocationByPtr(subEvents, ptr); + if (found) return found; + } + } + + return null; +}; diff --git a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js index e075b26cd6ac..81c3d466042e 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js +++ b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js @@ -42,6 +42,7 @@ type RowItemData = { onVisibilityToggle: ({| node: SortableTreeNode |}) => void, scaffoldBlockPxWidth: number, searchFocusOffset: ?number, + bookmarkFocusId: ?string, }; type Props = {| @@ -57,6 +58,7 @@ type Props = {| }) => boolean, searchQuery?: any, searchFocusOffset?: ?number, + bookmarkFocusId?: ?string, className?: string, reactVirtualizedListProps?: { ref?: (list: { @@ -244,6 +246,7 @@ const TreeRow = ({ onVisibilityToggle, scaffoldBlockPxWidth, searchFocusOffset, + bookmarkFocusId, } = data; const entry = flatData[index]; @@ -254,6 +257,8 @@ const TreeRow = ({ const isSearchMatch = matchIndexSet.has(index); const isSearchFocus = searchFocusOffset != null && matchIndexes[searchFocusOffset] === index; + const isBookmarkFocus = + bookmarkFocusId != null && node.event && node.event.getEventBookmarkId() === bookmarkFocusId; const scaffold = lowerSiblingCounts.map((lowerSiblingCount, i) => { const isNodeDepth = i === depth - 1; @@ -327,8 +332,8 @@ const TreeRow = ({
@@ -353,6 +358,7 @@ const SortableEventsTree = ({ searchMethod, searchQuery, searchFocusOffset, + bookmarkFocusId, className, reactVirtualizedListProps, }: Props) => { @@ -431,6 +437,7 @@ const SortableEventsTree = ({ onVisibilityToggle, scaffoldBlockPxWidth, searchFocusOffset, + bookmarkFocusId, }), [ flatData, @@ -439,6 +446,7 @@ const SortableEventsTree = ({ onVisibilityToggle, scaffoldBlockPxWidth, searchFocusOffset, + bookmarkFocusId, ] ); diff --git a/newIDE/app/src/EventsSheet/EventsTree/index.js b/newIDE/app/src/EventsSheet/EventsTree/index.js index 3bf1f74def40..efa51c0ebe52 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/index.js +++ b/newIDE/app/src/EventsSheet/EventsTree/index.js @@ -346,6 +346,7 @@ type EventsTreeProps = {| searchResults: ?Array, searchFocusOffset: ?number, + bookmarkFocusId: ?string, onEventMoved: (previousRowIndex: number, nextRowIndex: number) => void, onEndEditingEvent: (event: gdBaseEvent) => void, @@ -1116,7 +1117,12 @@ const EventsTree = React.forwardRef( searchMethod={_isNodeHighlighted} searchQuery={props.searchResults} searchFocusOffset={props.searchFocusOffset} - className={props.searchResults ? eventsTreeWithSearchResults : ''} + bookmarkFocusId={props.bookmarkFocusId} + className={ + props.searchResults || props.bookmarkFocusId + ? eventsTreeWithSearchResults + : '' + } reactVirtualizedListProps={{ ref: list => { _list.current = list; diff --git a/newIDE/app/src/EventsSheet/Toolbar.js b/newIDE/app/src/EventsSheet/Toolbar.js index 5dec1391da24..a637e33e664f 100644 --- a/newIDE/app/src/EventsSheet/Toolbar.js +++ b/newIDE/app/src/EventsSheet/Toolbar.js @@ -18,7 +18,7 @@ import ToolbarSearchIcon from '../UI/CustomSvgIcons/ToolbarSearch'; import EditSceneIcon from '../UI/CustomSvgIcons/EditScene'; import { getShortcutDisplayName, useShortcutMap } from '../KeyboardShortcuts'; import AddLocalVariableIcon from '../UI/CustomSvgIcons/LocalVariable'; -import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; type Props = {| onAddStandardEvent: () => void, @@ -234,7 +234,7 @@ const Toolbar = React.memo(function Toolbar({ onClick={() => onToggleBookmarksPanel()} tooltip={t`Bookmarks`} > - + {onOpenSettings && } {onOpenSettings && ( diff --git a/newIDE/app/src/EventsSheet/index.js b/newIDE/app/src/EventsSheet/index.js index 0c6190c980d3..6929c9e7c860 100644 --- a/newIDE/app/src/EventsSheet/index.js +++ b/newIDE/app/src/EventsSheet/index.js @@ -129,6 +129,7 @@ import { type Bookmark, scanEventsForBookmarks, findEventByPtr, + findEventLocationByPtr, } from './Bookmarks/BookmarksUtils'; import { v4 as uuidv4 } from 'uuid'; @@ -226,6 +227,7 @@ type State = {| showBookmarksPanel: boolean, bookmarks: Array, + bookmarkFocusId: ?string, layoutVariablesDialogOpen: boolean, @@ -324,6 +326,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component< showBookmarksPanel: false, bookmarks: [], + bookmarkFocusId: null, layoutVariablesDialogOpen: false, @@ -559,16 +562,27 @@ export class EventsSheetComponentWithoutHandle extends React.Component< }; _navigateToBookmark = (bookmark: Bookmark) => { - const event = findEventByPtr(this.props.events, bookmark.eventPtr); + const eventLocation = findEventLocationByPtr(this.props.events, bookmark.eventPtr); - if (!event) { + if (!eventLocation) { // Event no longer exists - remove bookmark this._deleteBookmark(bookmark.id); return; } - // Use existing navigation pattern + const { event, eventsList, indexInList } = eventLocation; + + // Scroll to and unfold the event this._ensureUnfoldedAndScrollTo(() => event); + + // Select the event + const eventContext = { + eventsList, + event, + indexInList, + projectScopedContainersAccessor: this.props.projectScopedContainersAccessor, + }; + this.selectEvent(eventContext); }; _deleteBookmark = (bookmarkId: string) => { @@ -597,6 +611,10 @@ export class EventsSheetComponentWithoutHandle extends React.Component< console.warn('Bookmark renaming is not supported. Edit the event to change its description.'); }; + _focusBookmark = (bookmark: Bookmark) => { + this.setState({ bookmarkFocusId: bookmark.id }); + }; + addSubEvent = () => { const { project } = this.props; @@ -2238,6 +2256,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component< onOpenLayout={onOpenLayout} searchResults={eventsSearchResultEvents} searchFocusOffset={searchFocusOffset} + bookmarkFocusId={this.state.bookmarkFocusId} onEventMoved={this._onEventMoved} onEndEditingEvent={this._onEndEditingStringEvent} showObjectThumbnails={ @@ -2288,21 +2307,21 @@ export class EventsSheetComponentWithoutHandle extends React.Component< /> )} - {this.state.showBookmarksPanel && ( - Bookmarks panel} - scope="scene-events-bookmarks" + Bookmarks panel} + scope="scene-events-bookmarks" + onClose={() => this.setState({ showBookmarksPanel: false })} + > + this.setState({ showBookmarksPanel: false })} - > - this.setState({ showBookmarksPanel: false })} - /> - - )} + /> + Date: Tue, 24 Feb 2026 01:27:58 +0100 Subject: [PATCH 5/7] Add color support --- .../EventsSheet/Bookmarks/BookmarksPanel.css | 72 +++ .../EventsSheet/Bookmarks/BookmarksUtils.js | 120 +++-- .../Bookmarks/{BookmarksPanel.js => index.js} | 109 ++-- .../EventsTree/SortableEventsTree.js | 3 +- newIDE/app/src/EventsSheet/index.js | 467 +++++++++--------- 5 files changed, 408 insertions(+), 363 deletions(-) create mode 100644 newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.css rename newIDE/app/src/EventsSheet/Bookmarks/{BookmarksPanel.js => index.js} (52%) diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.css b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.css new file mode 100644 index 000000000000..ec7b92cea4cb --- /dev/null +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.css @@ -0,0 +1,72 @@ +.bookmarksPanelContainer { + display: flex; + flex-direction: column; + z-index: 10; + pointer-events: auto; +} + +.bookmarksPanelContainer.mobile { + position: fixed; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: min(300px, 60vh); + max-height: 80vh; + box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.2); + transform: translateY(100%); + transition: transform 0.3s ease-in-out; +} + +.bookmarksPanelContainer.mobile.open { + transform: translateY(0); +} + +.bookmarksPanelContainer.desktop { + position: absolute; + top: 0; + right: 0; + height: 100%; + width: 280px; + box-shadow: -4px 0 12px rgba(0, 0, 0, 0.2); + transform: translateX(100%); + transition: transform 0.3s ease-in-out; +} + +.bookmarksPanelContainer.desktop.open { + transform: translateX(0%); +} + +.emptyContainer { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + padding: 20px; +} + +.bookmarkItem { + display: flex; + align-items: center; + padding: 8px 4px 8px 5px; + gap: 4px; + border-radius: 4px; + cursor: pointer; + border-bottom: 1px solid; + transition: background-color 0.2s ease; +} + +.bookmarkItem:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.bookmarkItemName { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.starIcon { + margin-right: 8px; +} diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js index db7f9d807a81..752e3f6a33cd 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/BookmarksUtils.js @@ -1,4 +1,5 @@ // @flow +import { rgbToHex } from '../../Utils/ColorTransformer'; const gd: libGDevelop = global.gd; @@ -8,7 +9,7 @@ export type Bookmark = {| eventType: string, id: string, timestamp: number, - color?: ?string, + borderLeftColor?: ?string, |}; /** @@ -17,67 +18,80 @@ export type Bookmark = {| export const scanEventsForBookmarks = ( events: gdEventsList ): Array => { + if (!events) return []; + const bookmarks: Array = []; const scanEventsList = (eventsList: gdEventsList) => { - // Safety check: ensure eventsList is valid and has the getEventsCount method - if (!eventsList || typeof eventsList.getEventsCount !== 'function') { - return; - } - - for (let i = 0; i < eventsList.getEventsCount(); i++) { - const event = eventsList.getEventAt(i); - - // Check if event has a bookmark ID - const bookmarkId = event.getEventBookmarkId(); - if (bookmarkId && bookmarkId.length > 0) { - const bookmark: Bookmark = { - eventPtr: event.ptr, - name: generateBookmarkName(event), - eventType: event.getType(), - id: bookmarkId, - timestamp: Date.now(), // We don't persist timestamp, so use current time - color: getEventColor(event), - }; - bookmarks.push(bookmark); + try { + // Safety check: ensure eventsList is valid and has the getEventsCount method + if (!eventsList || typeof eventsList.getEventsCount !== 'function') { + return; } - // Recursively scan sub-events - if (event.canHaveSubEvents && event.canHaveSubEvents()) { - const subEvents = event.getSubEvents(); - scanEventsList(subEvents); + for (let i = 0; i < eventsList.getEventsCount(); i++) { + const event = eventsList.getEventAt(i); + if (!event) continue; + + try { + // Check if event has a bookmark ID + const bookmarkId = + event.getEventBookmarkId && event.getEventBookmarkId(); + if (bookmarkId && bookmarkId.length > 0) { + const bookmark: Bookmark = { + eventPtr: event.ptr, + name: generateBookmarkName(event), + eventType: event.getType(), + id: bookmarkId, + timestamp: Date.now(), + borderLeftColor: getEventTypeColor(event), + }; + bookmarks.push(bookmark); + } + + // Recursively scan sub-events + if (event.canHaveSubEvents && event.canHaveSubEvents()) { + const subEvents = event.getSubEvents(); + if (subEvents) { + scanEventsList(subEvents); + } + } + } catch (err) { + console.error('Error processing event for bookmarks:', err); + } } + } catch (err) { + console.error('Error scanning event list for bookmarks:', err); } }; scanEventsList(events); + return bookmarks; }; /** - * Extract color from an event if it's a Group or Comment + * Get the border color for a bookmark based on the event type */ -export const getEventColor = (event: gdBaseEvent): ?string => { +const getEventTypeColor = (event: gdBaseEvent): ?string => { const eventType = event.getType(); - // For group events, get the color (format: "R;G;B") - if (eventType === 'BuiltinCommonInstructions::Group') { - const groupEvent = gd.asGroupEvent(event); - const r = groupEvent.getBackgroundColorR(); - const g = groupEvent.getBackgroundColorG(); - const b = groupEvent.getBackgroundColorB(); - // Return in format "R;G;B" for consistency - return `${r};${g};${b}`; - } - - // For comment events, get the background color (format: "R;G;B") if (eventType === 'BuiltinCommonInstructions::Comment') { const commentEvent = gd.asCommentEvent(event); - const r = commentEvent.getBackgroundColorRed(); - const g = commentEvent.getBackgroundColorGreen(); - const b = commentEvent.getBackgroundColorBlue(); - // Return in format "R;G;B" for consistency - return `${r};${g};${b}`; + return `#${rgbToHex( + commentEvent.getBackgroundColorRed(), + commentEvent.getBackgroundColorGreen(), + commentEvent.getBackgroundColorBlue() + )}`; + } + + if (eventType === 'BuiltinCommonInstructions::Group') { + const groupEvent = gd.asGroupEvent(event); + return `#${rgbToHex( + groupEvent.getBackgroundColorR(), + groupEvent.getBackgroundColorG(), + groupEvent.getBackgroundColorB() + )}`; } return null; @@ -158,15 +172,21 @@ export const findEventByPtr = ( events: gdEventsList, ptr: number ): ?gdBaseEvent => { + if (!events || !ptr) return null; + for (let i = 0; i < events.getEventsCount(); i++) { const event = events.getEventAt(i); + if (!event) continue; + if (event.ptr === ptr) return event; // Recursively search sub-events if (event.canHaveSubEvents && event.canHaveSubEvents()) { const subEvents = event.getSubEvents(); - const found = findEventByPtr(subEvents, ptr); - if (found) return found; + if (subEvents) { + const found = findEventByPtr(subEvents, ptr); + if (found) return found; + } } } @@ -186,8 +206,12 @@ export const findEventLocationByPtr = ( events: gdEventsList, ptr: number ): ?EventLocation => { + if (!events || !ptr) return null; + for (let i = 0; i < events.getEventsCount(); i++) { const event = events.getEventAt(i); + if (!event) continue; + if (event.ptr === ptr) { return { event, eventsList: events, indexInList: i }; } @@ -195,8 +219,10 @@ export const findEventLocationByPtr = ( // Recursively search sub-events if (event.canHaveSubEvents && event.canHaveSubEvents()) { const subEvents = event.getSubEvents(); - const found = findEventLocationByPtr(subEvents, ptr); - if (found) return found; + if (subEvents) { + const found = findEventLocationByPtr(subEvents, ptr); + if (found) return found; + } } } diff --git a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js b/newIDE/app/src/EventsSheet/Bookmarks/index.js similarity index 52% rename from newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js rename to newIDE/app/src/EventsSheet/Bookmarks/index.js index 08d34352e823..e346f86493f8 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/BookmarksPanel.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/index.js @@ -15,84 +15,49 @@ import EmptyMessage from '../../UI/EmptyMessage'; import ScrollView from '../../UI/ScrollView'; import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext'; import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer'; +import './BookmarksPanel.css'; type Props = {| bookmarks: Array, onNavigateToBookmark: (bookmark: Bookmark) => void, onDeleteBookmark: (bookmarkId: string) => void, - onRenameBookmark: (bookmarkId: string, newName: string) => void, - onFocusBookmark?: ?(bookmark: Bookmark) => void, onClose: () => void, isOpen: boolean, |}; -const DRAWER_WIDTH = 280; -const BOTTOM_PANEL_HEIGHT = 300; - -const getDrawerContainerStyle = (isMobile: boolean, isOpen: boolean) => ({ - position: isMobile ? 'fixed' : 'absolute', - ...(isMobile - ? { - bottom: 0, - left: 0, - right: 0, - width: '100%', - height: BOTTOM_PANEL_HEIGHT, - transform: isOpen ? 'translateY(0)' : `translateY(${BOTTOM_PANEL_HEIGHT}px)`, - boxShadow: '0 -4px 12px rgba(0,0,0,0.2)', - } - : { - top: 0, - right: 0, - height: '100%', - width: DRAWER_WIDTH, - transform: isOpen ? 'translateX(0%)' : 'translateX(100%)', - boxShadow: '-4px 0 12px rgba(0,0,0,0.2)', - }), - zIndex: 10, - transition: 'transform 0.3s ease-in-out', - display: 'flex', - flexDirection: 'column', - pointerEvents: 'auto', -}); - -const styles = { - backgroundWrapper: { - flex: 1, - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - }, - emptyContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flex: 1, - padding: '20px', - }, -}; - const BookmarksPanel = ({ bookmarks, onNavigateToBookmark, onDeleteBookmark, - onRenameBookmark, - onFocusBookmark, onClose, isOpen, }: Props) => { const gdevelopTheme = React.useContext(GDevelopThemeContext); const { isMobile } = useResponsiveWindowSize(); + // Handle keyboard shortcuts + React.useEffect(() => { + if (!isOpen) return; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [isOpen, onClose]); + return (
- + Bookmarks @@ -104,49 +69,37 @@ const BookmarksPanel = ({ {bookmarks.length === 0 ? ( -
+
- No bookmarks yet. Right-click on an event to add it as bookmark. + No bookmarks yet. Right-click on an event and select "Add Bookmark" to bookmark it.
) : ( - {bookmarks.map(bookmark => { - const rgbColor = bookmark.color - ? `rgb(${bookmark.color.replace(/;/g, ',')})` - : null; - - return ( + {bookmarks.map(bookmark => (
{ - onNavigateToBookmark(bookmark); - if (onFocusBookmark) { - onFocusBookmark(bookmark); - } - }} + onClick={() => onNavigateToBookmark(bookmark)} tooltip={t`Go to event`} > - + {bookmark.name}
- ); - })} + ) + )}
)} diff --git a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js index 81c3d466042e..272675daff42 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js +++ b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js @@ -259,6 +259,7 @@ const TreeRow = ({ searchFocusOffset != null && matchIndexes[searchFocusOffset] === index; const isBookmarkFocus = bookmarkFocusId != null && node.event && node.event.getEventBookmarkId() === bookmarkFocusId; + const isFocused = isSearchFocus || isBookmarkFocus; const scaffold = lowerSiblingCounts.map((lowerSiblingCount, i) => { const isNodeDepth = i === depth - 1; @@ -333,7 +334,7 @@ const TreeRow = ({ className={classNames( 'rst__row', (isSearchMatch || isBookmarkFocus) && 'rst__rowSearchMatch', - (isSearchFocus) && 'rst__rowSearchFocus' + isFocused && 'rst__rowSearchFocus' )} >
diff --git a/newIDE/app/src/EventsSheet/index.js b/newIDE/app/src/EventsSheet/index.js index 6929c9e7c860..e186c4d50b93 100644 --- a/newIDE/app/src/EventsSheet/index.js +++ b/newIDE/app/src/EventsSheet/index.js @@ -124,7 +124,7 @@ import LocalVariablesDialog from '../VariablesList/LocalVariablesDialog'; import GlobalAndSceneVariablesDialog from '../VariablesList/GlobalAndSceneVariablesDialog'; import { type HotReloadPreviewButtonProps } from '../HotReload/HotReloadPreviewButton'; import { useHighlightedAiGeneratedEvent } from './UseHighlightedAiGeneratedEvent'; -import BookmarksPanel from './Bookmarks/BookmarksPanel'; +import BookmarksPanel from './Bookmarks'; import { type Bookmark, scanEventsForBookmarks, @@ -401,6 +401,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< positionsBeforeAction: [], positionAfterAction: [], }); + // Refresh bookmarks since events were modified externally + this._refreshBookmarks(); } ); }; @@ -489,8 +491,12 @@ export class EventsSheetComponentWithoutHandle extends React.Component< _refreshBookmarks = () => { if (!this.props.events) return; - const bookmarks = scanEventsForBookmarks(this.props.events); - this.setState({ bookmarks }); + try { + const bookmarks = scanEventsForBookmarks(this.props.events); + this.setState({ bookmarks }); + } catch (err) { + console.error('Error refreshing bookmarks:', err); + } }; _toggleBookmarksPanel = () => { @@ -507,46 +513,25 @@ export class EventsSheetComponentWithoutHandle extends React.Component< ); }; - _addBookmark = () => { - const selectedEvents = getSelectedEventContexts(this.state.selection); - if (selectedEvents.length === 0) return; - - const eventContext = selectedEvents[selectedEvents.length - 1]; - const { event } = eventContext; - - // Check if already bookmarked - const existingBookmarkId = event.getEventBookmarkId(); - if (existingBookmarkId && existingBookmarkId.length > 0) return; - - // Set bookmark ID on the event - const bookmarkId = uuidv4(); - event.setEventBookmarkId(bookmarkId); - - // Refresh bookmarks from events - this._refreshBookmarks(); - - // Trigger unsaved changes - if (this.props.unsavedChanges) { - this.props.unsavedChanges.triggerUnsavedChanges(); - } - }; - - _removeBookmark = () => { - const selectedEvents = getSelectedEventContexts(this.state.selection); - if (selectedEvents.length === 0) return; + _toggleBookmark = () => { + try { + const selectedEvents = getSelectedEventContexts(this.state.selection); + if (selectedEvents.length === 0) return; - const eventContext = selectedEvents[selectedEvents.length - 1]; - const { event } = eventContext; + const eventContext = selectedEvents[selectedEvents.length - 1]; + const { event } = eventContext; - // Clear bookmark ID from the event - event.setEventBookmarkId(''); + const bookmarkId = event.getEventBookmarkId && event.getEventBookmarkId(); + const isBookmarked = bookmarkId && bookmarkId.length > 0; - // Refresh bookmarks from events - this._refreshBookmarks(); + event.setEventBookmarkId(isBookmarked ? '' : uuidv4()); - // Trigger unsaved changes - if (this.props.unsavedChanges) { - this.props.unsavedChanges.triggerUnsavedChanges(); + this._refreshBookmarks(); + if (this.props.unsavedChanges) { + this.props.unsavedChanges.triggerUnsavedChanges(); + } + } catch (err) { + console.error('Error toggling bookmark:', err); } }; @@ -555,14 +540,15 @@ export class EventsSheetComponentWithoutHandle extends React.Component< if (selectedEvents.length === 0) return false; const eventContext = selectedEvents[selectedEvents.length - 1]; - const { event } = eventContext; - - const bookmarkId = event.getEventBookmarkId(); - return bookmarkId && bookmarkId.length > 0; + const bookmarkId = eventContext.event.getEventBookmarkId?.(); + return !!bookmarkId && bookmarkId.length > 0; }; _navigateToBookmark = (bookmark: Bookmark) => { - const eventLocation = findEventLocationByPtr(this.props.events, bookmark.eventPtr); + const eventLocation = findEventLocationByPtr( + this.props.events, + bookmark.eventPtr + ); if (!eventLocation) { // Event no longer exists - remove bookmark @@ -576,13 +562,15 @@ export class EventsSheetComponentWithoutHandle extends React.Component< this._ensureUnfoldedAndScrollTo(() => event); // Select the event - const eventContext = { + this.selectEvent({ eventsList, event, indexInList, projectScopedContainersAccessor: this.props.projectScopedContainersAccessor, - }; - this.selectEvent(eventContext); + }); + + // Focus the bookmark for visual highlight + this.setState({ bookmarkFocusId: bookmark.id }); }; _deleteBookmark = (bookmarkId: string) => { @@ -592,29 +580,21 @@ export class EventsSheetComponentWithoutHandle extends React.Component< const event = findEventByPtr(this.props.events, bookmark.eventPtr); if (event) { - event.setEventBookmarkId(''); - - // Trigger unsaved changes - if (this.props.unsavedChanges) { - this.props.unsavedChanges.triggerUnsavedChanges(); + try { + event.setEventBookmarkId(''); + if (this.props.unsavedChanges) { + this.props.unsavedChanges.triggerUnsavedChanges(); + } + } catch (err) { + console.error('Error removing bookmark from event:', err); } + } else { + console.warn('Bookmarked event no longer exists'); } - // Refresh bookmarks from events this._refreshBookmarks(); }; - _renameBookmark = (bookmarkId: string, newName: string) => { - // Bookmark names are generated dynamically from event content. - // To change a bookmark name, the user should edit the event itself. - // This method is kept for future extensibility but currently does nothing. - console.warn('Bookmark renaming is not supported. Edit the event to change its description.'); - }; - - _focusBookmark = (bookmark: Bookmark) => { - this.setState({ bookmarkFocusId: bookmark.id }); - }; - addSubEvent = () => { const { project } = this.props; @@ -793,6 +773,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< positionsBeforeAction: positions, positionAfterAction: positions, }); + // Refresh bookmarks in case the event color was changed + this._refreshBookmarks(); } this.setState({ textEditedEvent: null, @@ -921,6 +903,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< positionsBeforeAction: positions, positionAfterAction: positions, }); + // Refresh bookmarks in case the event was a group/comment with color change + this._refreshBookmarks(); } } ); @@ -1004,181 +988,186 @@ export class EventsSheetComponentWithoutHandle extends React.Component< if (this._eventsTree) this._eventsTree.unfoldToLevel(level); }; - _buildEventContextMenu = (i18n: I18nType) => [ - { - label: i18n._(t`Edit`), - click: () => this.openEventTextDialog(), - visible: - filterEditableWithEventTextDialog( - getSelectedEvents(this.state.selection) - ).length > 0, - }, - { - label: i18n._(t`Copy`), - click: () => this.copySelection(), - accelerator: 'CmdOrCtrl+C', - }, - { - label: i18n._(t`Cut`), - click: () => this.cutSelection(), - accelerator: 'CmdOrCtrl+X', - }, - { - label: i18n._(t`Paste`), - click: () => this.pasteEvents(), - enabled: hasClipboardEvents(), - accelerator: 'CmdOrCtrl+V', - }, - { - label: i18n._(t`Delete`), - click: () => this.deleteSelection(), - accelerator: 'Delete', - }, - { - label: i18n._(t`Toggle Disabled`), - click: () => this.toggleDisabled(), - enabled: this._selectionCanToggleDisabled(), - accelerator: getShortcutDisplayName( - this.props.shortcutMap['TOGGLE_EVENT_DISABLED'] || 'KeyD' - ), - }, - { - label: i18n._(t`Remove the Else`), - click: () => - this._replaceSelectedEventType('BuiltinCommonInstructions::Standard'), - visible: this._selectionIsElseEvent(), - }, - { type: 'separator' }, - { - label: this._isEventBookmarked() - ? i18n._(t`Remove Bookmark`) - : i18n._(t`Add Bookmark`), - click: () => - this._isEventBookmarked() ? this._removeBookmark() : this._addBookmark(), - visible: hasEventSelected(this.state.selection), - }, - { type: 'separator' }, - { - label: i18n._(t`Add`), - submenu: [ - { - label: i18n._(t`New Event Below`), - click: () => { - this.addNewEvent('BuiltinCommonInstructions::Standard'); + _buildEventContextMenu = (i18n: I18nType) => { + const isEventBookmarked = this._isEventBookmarked(); + + return [ + { + label: i18n._(t`Edit`), + click: () => this.openEventTextDialog(), + visible: + filterEditableWithEventTextDialog( + getSelectedEvents(this.state.selection) + ).length > 0, + }, + { + label: i18n._(t`Copy`), + click: () => this.copySelection(), + accelerator: 'CmdOrCtrl+C', + }, + { + label: i18n._(t`Cut`), + click: () => this.cutSelection(), + accelerator: 'CmdOrCtrl+X', + }, + { + label: i18n._(t`Paste`), + click: () => this.pasteEvents(), + enabled: hasClipboardEvents(), + accelerator: 'CmdOrCtrl+V', + }, + { + label: i18n._(t`Delete`), + click: () => this.deleteSelection(), + accelerator: 'Delete', + }, + { + label: i18n._(t`Toggle Disabled`), + click: () => this.toggleDisabled(), + enabled: this._selectionCanToggleDisabled(), + accelerator: getShortcutDisplayName( + this.props.shortcutMap['TOGGLE_EVENT_DISABLED'] || 'KeyD' + ), + }, + { + label: i18n._(t`Remove the Else`), + click: () => + this._replaceSelectedEventType('BuiltinCommonInstructions::Standard'), + visible: this._selectionIsElseEvent(), + }, + { type: 'separator' }, + { + label: isEventBookmarked + ? i18n._(t`Remove Bookmark`) + : i18n._(t`Add Bookmark`), + click: () => this._toggleBookmark(), + visible: hasEventSelected(this.state.selection), + }, + { type: 'separator' }, + { + label: i18n._(t`Add`), + submenu: [ + { + label: i18n._(t`New Event Below`), + click: () => { + this.addNewEvent('BuiltinCommonInstructions::Standard'); + }, + accelerator: getShortcutDisplayName( + this.props.shortcutMap['ADD_STANDARD_EVENT'] + ), }, - accelerator: getShortcutDisplayName( - this.props.shortcutMap['ADD_STANDARD_EVENT'] - ), - }, - { - label: i18n._(t`Sub Event`), - click: () => this.addSubEvent(), - enabled: this._selectionCanHaveSubEvents(), - accelerator: getShortcutDisplayName( - this.props.shortcutMap['ADD_SUBEVENT'] - ), - }, - { - label: i18n._(t`Local Variable`), - click: () => this.addLocalVariable(), - enabled: this._selectionCanHaveLocalVariables(), - accelerator: getShortcutDisplayName( - this.props.shortcutMap['ADD_LOCAL_VARIABLE'] - ), - }, - { - label: i18n._(t`Comment`), - click: () => { - this.addNewEvent('BuiltinCommonInstructions::Comment'); + { + label: i18n._(t`Sub Event`), + click: () => this.addSubEvent(), + enabled: this._selectionCanHaveSubEvents(), + accelerator: getShortcutDisplayName( + this.props.shortcutMap['ADD_SUBEVENT'] + ), }, - accelerator: getShortcutDisplayName( - this.props.shortcutMap['ADD_COMMENT_EVENT'] - ), - }, - ...this.state.allEventsMetadata - .filter( - metadata => - metadata.type !== 'BuiltinCommonInstructions::Standard' && - metadata.type !== 'BuiltinCommonInstructions::Comment' - ) - .map(metadata => ({ - label: metadata.fullName, + { + label: i18n._(t`Local Variable`), + click: () => this.addLocalVariable(), + enabled: this._selectionCanHaveLocalVariables(), + accelerator: getShortcutDisplayName( + this.props.shortcutMap['ADD_LOCAL_VARIABLE'] + ), + }, + { + label: i18n._(t`Comment`), click: () => { - this.addNewEvent(metadata.type); + this.addNewEvent('BuiltinCommonInstructions::Comment'); }, - })), - ], - }, - { - label: i18n._(t`Replace`), - submenu: [ - { - label: i18n._(t`Make it a Else for the previous event`), - click: () => - this._replaceSelectedEventType('BuiltinCommonInstructions::Else'), - enabled: this._selectionIsStandardEvent(), - }, - { type: 'separator' }, - { - label: i18n._(t`Extract Events to a Function`), - click: () => this.extractEventsToFunction(), - }, - { - label: i18n._(t`Move Events into a Group`), - click: () => this.moveEventsIntoNewGroup(), - accelerator: getShortcutDisplayName( - this.props.shortcutMap['MOVE_EVENTS_IN_NEW_GROUP'] - ), - }, - { type: 'separator' }, - { - label: i18n._(t`Analyze Objects Used in this Event`), - click: this._openEventsContextAnalyzer, - }, - ], - }, - { type: 'separator' }, - { - label: i18n._(t`Events Sheet`), - submenu: [ - { - label: i18n._(t`Zoom In`), - click: () => this.onZoomEvent('IN')(), - accelerator: 'CmdOrCtrl+=', - enabled: - this.props.preferences.values.eventsSheetZoomLevel < zoomLevel.max, - }, - { - label: i18n._(t`Zoom Out`), - click: () => this.onZoomEvent('OUT')(), - accelerator: 'CmdOrCtrl+-', - enabled: - this.props.preferences.values.eventsSheetZoomLevel > zoomLevel.min, - }, - { type: 'separator' }, - { - label: i18n._(t`Collapse All`), - click: this.collapseAll, - }, - { - label: i18n._(t`Expand All to Level`), - submenu: [ - { - label: i18n._(t`All`), - click: () => this.expandToLevel(-1), - }, - { type: 'separator' }, - ...[0, 1, 2, 3, 4, 5, 6, 7, 8].map(index => { - return { - label: i18n._(t`Level ${index + 1}`), - click: () => this.expandToLevel(index), - }; - }), - ], - }, - ], - }, - ]; + accelerator: getShortcutDisplayName( + this.props.shortcutMap['ADD_COMMENT_EVENT'] + ), + }, + ...this.state.allEventsMetadata + .filter( + metadata => + metadata.type !== 'BuiltinCommonInstructions::Standard' && + metadata.type !== 'BuiltinCommonInstructions::Comment' + ) + .map(metadata => ({ + label: metadata.fullName, + click: () => { + this.addNewEvent(metadata.type); + }, + })), + ], + }, + { + label: i18n._(t`Replace`), + submenu: [ + { + label: i18n._(t`Make it a Else for the previous event`), + click: () => + this._replaceSelectedEventType('BuiltinCommonInstructions::Else'), + enabled: this._selectionIsStandardEvent(), + }, + { type: 'separator' }, + { + label: i18n._(t`Extract Events to a Function`), + click: () => this.extractEventsToFunction(), + }, + { + label: i18n._(t`Move Events into a Group`), + click: () => this.moveEventsIntoNewGroup(), + accelerator: getShortcutDisplayName( + this.props.shortcutMap['MOVE_EVENTS_IN_NEW_GROUP'] + ), + }, + { type: 'separator' }, + { + label: i18n._(t`Analyze Objects Used in this Event`), + click: this._openEventsContextAnalyzer, + }, + ], + }, + { type: 'separator' }, + { + label: i18n._(t`Events Sheet`), + submenu: [ + { + label: i18n._(t`Zoom In`), + click: () => this.onZoomEvent('IN')(), + accelerator: 'CmdOrCtrl+=', + enabled: + this.props.preferences.values.eventsSheetZoomLevel < + zoomLevel.max, + }, + { + label: i18n._(t`Zoom Out`), + click: () => this.onZoomEvent('OUT')(), + accelerator: 'CmdOrCtrl+-', + enabled: + this.props.preferences.values.eventsSheetZoomLevel > + zoomLevel.min, + }, + { type: 'separator' }, + { + label: i18n._(t`Collapse All`), + click: this.collapseAll, + }, + { + label: i18n._(t`Expand All to Level`), + submenu: [ + { + label: i18n._(t`All`), + click: () => this.expandToLevel(-1), + }, + { type: 'separator' }, + ...[0, 1, 2, 3, 4, 5, 6, 7, 8].map(index => { + return { + label: i18n._(t`Level ${index + 1}`), + click: () => this.expandToLevel(index), + }; + }), + ], + }, + ], + }, + ]; + }; _selectionIsStandardEvent = () => { const eventContext = getLastSelectedEventContext(this.state.selection); @@ -1629,6 +1618,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< positionsBeforeAction: eventRowIndex, positionAfterAction: eventRowIndex, }); + // Refresh bookmarks in case the event content changed + this._refreshBookmarks(); }; _getChangedEventRows = (events: Array) => { @@ -1719,6 +1710,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< }, 70); } this.updateToolbar(); + // Refresh bookmarks in case events were added/deleted/changed + this._refreshBookmarks(); } ); }); @@ -1778,6 +1771,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< }, 70); } this.updateToolbar(); + // Refresh bookmarks in case events were added/deleted/changed + this._refreshBookmarks(); } ); }); @@ -2317,8 +2312,6 @@ export class EventsSheetComponentWithoutHandle extends React.Component< bookmarks={this.state.bookmarks} onNavigateToBookmark={this._navigateToBookmark} onDeleteBookmark={this._deleteBookmark} - onRenameBookmark={this._renameBookmark} - onFocusBookmark={this._focusBookmark} onClose={() => this.setState({ showBookmarksPanel: false })} /> From 870a8781173f07ad76fc5e20f218e737cabdda14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Vivet?= Date: Tue, 24 Feb 2026 01:29:14 +0100 Subject: [PATCH 6/7] format --- newIDE/app/src/EventsSheet/Bookmarks/index.js | 97 ++++++++++--------- .../EventsTree/SortableEventsTree.js | 4 +- newIDE/app/src/EventsSheet/index.js | 3 +- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/newIDE/app/src/EventsSheet/Bookmarks/index.js b/newIDE/app/src/EventsSheet/Bookmarks/index.js index e346f86493f8..9c750cffd54b 100644 --- a/newIDE/app/src/EventsSheet/Bookmarks/index.js +++ b/newIDE/app/src/EventsSheet/Bookmarks/index.js @@ -36,24 +36,29 @@ const BookmarksPanel = ({ const { isMobile } = useResponsiveWindowSize(); // Handle keyboard shortcuts - React.useEffect(() => { - if (!isOpen) return; + React.useEffect( + () => { + if (!isOpen) return; - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - onClose(); - } - }; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [isOpen, onClose]); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, + [isOpen, onClose] + ); return (
- + @@ -67,12 +72,13 @@ const BookmarksPanel = ({ - + {bookmarks.length === 0 ? (
- No bookmarks yet. Right-click on an event and select "Add Bookmark" to bookmark it. + No bookmarks yet. Right-click on an event and select "Add + Bookmark" to bookmark it.
@@ -80,38 +86,39 @@ const BookmarksPanel = ({ {bookmarks.map(bookmark => ( -
+ onNavigateToBookmark(bookmark)} + tooltip={t`Go to event`} + > + + + + {bookmark.name} + + onDeleteBookmark(bookmark.id)} + tooltip={t`Delete bookmark`} > - onNavigateToBookmark(bookmark)} - tooltip={t`Go to event`} - > - - - - {bookmark.name} - - onDeleteBookmark(bookmark.id)} - tooltip={t`Delete bookmark`} - > - - -
- ) - )} + + +
+ ))} )} diff --git a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js index 272675daff42..95d4fb41000a 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js +++ b/newIDE/app/src/EventsSheet/EventsTree/SortableEventsTree.js @@ -258,7 +258,9 @@ const TreeRow = ({ const isSearchFocus = searchFocusOffset != null && matchIndexes[searchFocusOffset] === index; const isBookmarkFocus = - bookmarkFocusId != null && node.event && node.event.getEventBookmarkId() === bookmarkFocusId; + bookmarkFocusId != null && + node.event && + node.event.getEventBookmarkId() === bookmarkFocusId; const isFocused = isSearchFocus || isBookmarkFocus; const scaffold = lowerSiblingCounts.map((lowerSiblingCount, i) => { diff --git a/newIDE/app/src/EventsSheet/index.js b/newIDE/app/src/EventsSheet/index.js index e186c4d50b93..c8983d74d560 100644 --- a/newIDE/app/src/EventsSheet/index.js +++ b/newIDE/app/src/EventsSheet/index.js @@ -566,7 +566,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< eventsList, event, indexInList, - projectScopedContainersAccessor: this.props.projectScopedContainersAccessor, + projectScopedContainersAccessor: this.props + .projectScopedContainersAccessor, }); // Focus the bookmark for visual highlight From 8cee7ecf9419345bf9b74d5a2ea275c5a0256ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Vivet?= Date: Tue, 24 Feb 2026 01:45:49 +0100 Subject: [PATCH 7/7] Fow Flow --- newIDE/app/src/EventsSheet/index.js | 4 ++-- newIDE/app/src/UI/Background.js | 3 ++- newIDE/app/src/UI/ErrorBoundary.js | 1 + newIDE/app/src/UI/Text.js | 1 + .../componentStories/EventsSheet/EventsTree.stories.js | 4 ++++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/newIDE/app/src/EventsSheet/index.js b/newIDE/app/src/EventsSheet/index.js index c8983d74d560..5778fe127165 100644 --- a/newIDE/app/src/EventsSheet/index.js +++ b/newIDE/app/src/EventsSheet/index.js @@ -540,8 +540,8 @@ export class EventsSheetComponentWithoutHandle extends React.Component< if (selectedEvents.length === 0) return false; const eventContext = selectedEvents[selectedEvents.length - 1]; - const bookmarkId = eventContext.event.getEventBookmarkId?.(); - return !!bookmarkId && bookmarkId.length > 0; + const bookmarkId = eventContext.event.getEventBookmarkId && eventContext.event.getEventBookmarkId(); + return bookmarkId && bookmarkId.length > 0; }; _navigateToBookmark = (bookmark: Bookmark) => { diff --git a/newIDE/app/src/UI/Background.js b/newIDE/app/src/UI/Background.js index e04689959327..f05a505bebcc 100644 --- a/newIDE/app/src/UI/Background.js +++ b/newIDE/app/src/UI/Background.js @@ -19,6 +19,7 @@ type Props = {| /** Sometimes required on Safari */ noFullHeight?: boolean, noExpand?: boolean, + expand?: boolean, |}; /** @@ -32,7 +33,7 @@ const Background = (props: Props) => ( ...styles.container, height: props.noFullHeight ? undefined : '100%', width: props.width ? props.width : undefined, - flex: props.noExpand ? undefined : 1, + flex: props.noExpand || props.expand === false ? undefined : 1, ...(props.maxWidth ? styles.maxWidth : undefined), }} background="dark" diff --git a/newIDE/app/src/UI/ErrorBoundary.js b/newIDE/app/src/UI/ErrorBoundary.js index 05815299618d..88d3af2cd32a 100644 --- a/newIDE/app/src/UI/ErrorBoundary.js +++ b/newIDE/app/src/UI/ErrorBoundary.js @@ -56,6 +56,7 @@ type ErrorBoundaryScope = | 'scene-events' | 'scene-events-search' | 'scene-events-instruction-editor' + | 'scene-events-bookmarks' | 'debugger' | 'resources' | 'extension-editor' diff --git a/newIDE/app/src/UI/Text.js b/newIDE/app/src/UI/Text.js index daf79ece5ec9..a8cd066d7314 100644 --- a/newIDE/app/src/UI/Text.js +++ b/newIDE/app/src/UI/Text.js @@ -64,6 +64,7 @@ type Props = {| fontVariantNumeric?: 'tabular-nums', |}, tooltip?: string, + className?: string, |}; type Interface = {||}; diff --git a/newIDE/app/src/stories/componentStories/EventsSheet/EventsTree.stories.js b/newIDE/app/src/stories/componentStories/EventsSheet/EventsTree.stories.js index 572d6d06bf03..563cbd18f7ca 100644 --- a/newIDE/app/src/stories/componentStories/EventsSheet/EventsTree.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsSheet/EventsTree.stories.js @@ -68,6 +68,7 @@ export const DefaultMediumScreenScopeInLayout = () => ( onOpenLayout={action('open layout')} searchResults={null} searchFocusOffset={null} + bookmarkFocusId={null} onEventMoved={() => {}} showObjectThumbnails={true} screenType={'normal'} @@ -124,6 +125,7 @@ export const DefaultSmallScreenScopeInLayout = () => ( onOpenLayout={action('open layout')} searchResults={null} searchFocusOffset={null} + bookmarkFocusId={null} onEventMoved={() => {}} showObjectThumbnails={true} screenType={'normal'} @@ -177,6 +179,7 @@ export const DefaultMediumScreenScopeNotInLayout = () => ( onOpenLayout={action('open layout')} searchResults={null} searchFocusOffset={null} + bookmarkFocusId={null} onEventMoved={() => {}} showObjectThumbnails={true} screenType={'normal'} @@ -233,6 +236,7 @@ export const EmptySmallScreenScopeInALayout = () => ( onOpenLayout={action('open layout')} searchResults={null} searchFocusOffset={null} + bookmarkFocusId={null} onEventMoved={() => {}} showObjectThumbnails={true} screenType={'normal'}