11import { useCallback , useEffect , useState } from 'react' ;
2- import { useEcho } from '@laravel/echo-react' ;
32import { ApiError } from '../api/client' ;
43import { deleteMenu , fetchMenu , fetchMenus , menuKey , saveMenu , SETTINGS_DESCRIPTOR } from '../editor/menus' ;
4+ import { EDITOR_VERSION } from '../editor/config' ;
55import { activeTab , hasUnsavedWork , useEditorStore } from '../editor/store' ;
66import { validateMenuSource , type MenuDiagnostics } from '../editor/validator' ;
77import { AdminTerminal } from './AdminTerminal' ;
88import { MaintenanceBanner } from './MaintenanceBanner' ;
9+ import { RealtimeBridge } from './RealtimeBridge' ;
910import { WorkspaceLayout } from './WorkspaceLayout' ;
1011import { useToast } from './Toasts' ;
1112import type { MenuDescriptor , SessionState } from '../types/editor' ;
1213
1314interface EditorWorkspaceProps {
1415 sessionId : string ;
1516 session : SessionState ;
17+ realtimeReady : boolean ;
1618}
1719
1820/** The live editor, bound to one confirmed session and its Minecraft server. */
19- export function EditorWorkspace ( { sessionId, session } : EditorWorkspaceProps ) {
21+ export function EditorWorkspace ( { sessionId, session, realtimeReady } : EditorWorkspaceProps ) {
2022 const { store, dispatch } = useEditorStore ( ) ;
2123 const notify = useToast ( ) ;
2224 const [ diagnostics , setDiagnostics ] = useState < MenuDiagnostics | null > ( null ) ;
@@ -117,29 +119,50 @@ export function EditorWorkspace({ sessionId, session }: EditorWorkspaceProps) {
117119 [ dispatch , loadMenus , notify , report ] ,
118120 ) ;
119121
120- useSaveShortcut ( save ) ;
121- useUnsavedWarning ( hasUnsavedWork ( store ) ) ;
122+ const create = useCallback (
123+ async ( platform : string ) : Promise < void > => {
124+ const fileName = window . prompt ( `New ${ platform . toLowerCase ( ) } menu file name` , 'new_menu.yml' ) ;
122125
123- useEcho < { connected : boolean } > ( `session.${ sessionId } ` , '.plugin.status' , payload => {
124- dispatch ( { type : 'plugin/status' , online : payload . connected } ) ;
126+ if ( fileName === null || fileName . trim ( ) === '' ) {
127+ return ;
128+ }
125129
126- if ( payload . connected ) {
127- void loadMenus ( ) ;
128- }
129- } ) ;
130+ try {
131+ await saveMenu ( platform , fileName . trim ( ) , starterMenu ( platform , fileName . trim ( ) ) ) ;
132+ notify ( 'success' , `${ fileName . trim ( ) } created` ) ;
133+ await loadMenus ( ) ;
134+ } catch ( error ) {
135+ report ( error , `Could not create ${ fileName } ` ) ;
136+ }
137+ } ,
138+ [ loadMenus , notify , report ] ,
139+ ) ;
130140
131- useEcho < { platform : string ; fileName : string ; change : string } > ( `session.${ sessionId } ` , '.menu.changed' , ( ) => {
132- void loadMenus ( ) ;
133- } ) ;
141+ useSaveShortcut ( save ) ;
142+ useUnsavedWarning ( hasUnsavedWork ( store ) ) ;
134143
135144 return (
136- < WorkspaceLayout
137- subtitle = "Menu Editor"
138- connectionLabel = { store . pluginOnline ? 'Connected' : 'Server offline' }
139- connectionState = { store . pluginOnline ? 'connected' : 'offline' }
140- sessionInfo = { `session ${ sessionId . slice ( 0 , 8 ) } ` }
141- banner = { < MaintenanceBanner /> }
142- toolbar = {
145+ < >
146+ { realtimeReady && (
147+ < RealtimeBridge
148+ sessionId = { sessionId }
149+ onPluginStatus = { online => {
150+ dispatch ( { type : 'plugin/status' , online } ) ;
151+
152+ if ( online ) {
153+ void loadMenus ( ) ;
154+ }
155+ } }
156+ onMenuChanged = { ( ) => void loadMenus ( ) }
157+ />
158+ ) }
159+
160+ < WorkspaceLayout
161+ connectionLabel = { store . pluginOnline ? 'Connected' : 'Server offline' }
162+ connectionState = { store . pluginOnline ? 'connected' : 'offline' }
163+ sessionInfo = { sessionInfo ( sessionId , session . serverVersion ) }
164+ banner = { < MaintenanceBanner /> }
165+ toolbar = {
143166 < button
144167 type = "button"
145168 className = "btn btn-secondary btn-sm"
@@ -148,24 +171,45 @@ export function EditorWorkspace({ sessionId, session }: EditorWorkspaceProps) {
148171 ⌨ Terminal
149172 </ button >
150173 }
151- serverVersion = { session . serverVersion }
152- diagnostics = { diagnostics }
153- visualMode = { visualMode }
154- busy = { busy }
155- canSave
156- onToggleVisual = { ( ) => setVisualMode ( mode => ! mode ) }
157- onValidate = { ( ) =>
174+ serverVersion = { session . serverVersion }
175+ diagnostics = { diagnostics }
176+ visualMode = { visualMode }
177+ busy = { busy }
178+ canSave
179+ onToggleVisual = { ( ) => setVisualMode ( mode => ! mode ) }
180+ onValidate = { ( ) =>
158181 current !== null && setDiagnostics ( validateMenuSource ( current . content , current . menu . platform ) )
159182 }
160- onSave = { ( ) => void save ( ) }
161- onOpen = { menu => void openMenu ( menu ) }
162- onDelete = { menu => void remove ( menu ) }
163- onCloseDiagnostics = { ( ) => setDiagnostics ( null ) }
164- footer = { terminalOpen ? < AdminTerminal onClose = { ( ) => setTerminalOpen ( false ) } /> : null }
165- />
183+ onSave = { ( ) => void save ( ) }
184+ onOpen = { menu => void openMenu ( menu ) }
185+ onCreate = { platform => void create ( platform ) }
186+ onDelete = { menu => void remove ( menu ) }
187+ statusLeft = { `${ store . menus . filter ( menu => menu . platform !== 'CONFIG' ) . length } menus loaded` }
188+ statusRight = { EDITOR_VERSION }
189+ onCloseDiagnostics = { ( ) => setDiagnostics ( null ) }
190+ footer = { terminalOpen ? < AdminTerminal onClose = { ( ) => setTerminalOpen ( false ) } /> : null }
191+ />
192+ </ >
166193 ) ;
167194}
168195
196+ function sessionInfo ( sessionId : string , serverVersion : string | null ) : string {
197+ const suffix = serverVersion === null ? '' : ` · MC ${ serverVersion } ` ;
198+
199+ return `Session: ${ sessionId . slice ( 0 , 8 ) } ${ suffix } ` ;
200+ }
201+
202+ /** A new menu starts from the smallest file the plugin will actually load. */
203+ function starterMenu ( platform : string , fileName : string ) : string {
204+ const name = fileName . replace ( / \. y a ? m l $ / i, '' ) ;
205+
206+ if ( platform . toUpperCase ( ) === 'BEDROCK' ) {
207+ return [ 'file_version: 1' , `menuName: '${ name } '` , 'type: SIMPLE' , 'content:' , ` - '${ name } '` , 'buttons: {}' , '' ] . join ( '\n' ) ;
208+ }
209+
210+ return [ 'file_version: 1' , `menuName: '${ name } '` , 'type: CHEST' , 'menuSize: 27' , 'items: {}' , '' ] . join ( '\n' ) ;
211+ }
212+
169213/** Ctrl+S and Cmd+S save, which is what anyone editing a file will reach for. */
170214function useSaveShortcut ( save : ( ) => Promise < void > ) : void {
171215 useEffect ( ( ) => {
0 commit comments