11import React , { useEffect , useState } from 'react' ;
22import logo from './img/logo192.png' ;
3+ import tcIcon from './img/logo192.png' ;
34import './App.css' ;
4- import { NavLink , Link , Routes , Route } from 'react-router-dom' ;
5+ import { Link , NavLink , Route , Routes } from 'react-router-dom' ;
56import { MenuTreeWithCodeOptions , RoundTripMode } from "./domain/ProjectStruct" ;
67import { getActiveProfile } from "./generator/TcCodeGeneration" ;
78import { parseEmfJsonToProject , projectToPersistedJson } from "./domain/PersistedMenu" ;
8- import { StartNewProject } from './StartNewProject' ;
9+ import { StartNewProject } from './StartNewProject' ;
910import { TcMenuEditor } from "./menuedit/TcMenuEditor" ;
1011import { GenerateCodeView } from "./generator/GenerateCodeView" ;
1112import embedIcon from "./img/embedControlLogo300.png"
12- import tcIcon from "./img/logo192.png"
1313import { IoExpanderComponent } from "./generator/IoExpanderComponent" ;
1414import ReleaseNotes from "./releaseNotes" ;
15+ import { get , set } from 'idb-keyval' ;
16+ import fontEdIcon from './img/font-editor-example.jpg'
1517
1618const TC_MENU_STORAGE_KEY = "tcMenuTurboProject" ;
1719const TC_MENU_POLICY_KEY = "tcMenuTurboPolicyAccepted" ;
1820let currentlyOpenProject : MenuTreeWithCodeOptions | null = null ;
21+ let globalDirectoryHandle : FileSystemDirectoryHandle | null = null ;
1922let projectListeners : ( ( proj : MenuTreeWithCodeOptions | null ) => void ) [ ] = [ ] ;
2023let saveTimer : any = null ;
2124
2225export function getCurrentlyOpenProject ( ) : MenuTreeWithCodeOptions | null {
2326 return currentlyOpenProject ;
2427}
2528
29+ export function getDirectoryHandle ( ) : FileSystemDirectoryHandle | null {
30+ return globalDirectoryHandle ;
31+ }
32+
33+ export const findFileWithExtension = async ( directoryHandle : any , ext1 : string , ext2 : string = "undef" ) : Promise < any | null > => {
34+ for await ( const entry of directoryHandle . values ( ) ) {
35+ if ( entry . kind === 'file' && ( entry . name . endsWith ( '.' + ext1 ) || entry . name . endsWith ( '.' + ext2 ) ) ) {
36+ return entry ;
37+ }
38+ }
39+ return null ;
40+ } ;
41+
2642export function saveProjectToLocalStorage ( proj : MenuTreeWithCodeOptions | null ) {
2743 if ( saveTimer ) {
2844 clearTimeout ( saveTimer ) ;
@@ -50,7 +66,7 @@ export function saveProjectToLocalStorage(proj: MenuTreeWithCodeOptions | null)
5066 }
5167}
5268
53- export function setCurrentlyOpenProject ( proj : MenuTreeWithCodeOptions | null ) {
69+ export function setCurrentlyOpenProject ( proj : MenuTreeWithCodeOptions | null , directoryHandle : any = null ) {
5470 currentlyOpenProject = proj ;
5571 if ( proj ) {
5672 proj . menuTree . setTreeStructureChanged ( ( tree , id ) => {
@@ -74,12 +90,55 @@ export function setCurrentlyOpenProject(proj: MenuTreeWithCodeOptions | null) {
7490 }
7591 } ) ;
7692 }
93+ if ( directoryHandle ) {
94+ saveProjectToLocalStorage ( currentlyOpenProject ) ;
95+ globalDirectoryHandle = directoryHandle ;
96+ set ( 'last_project_dir' , directoryHandle )
97+ . catch ( ( ) => {
98+ alert ( "Failed to save last project directory, project will not be able to save" ) ;
99+ } ) ;
100+ }
77101 } else {
78102 localStorage . removeItem ( TC_MENU_STORAGE_KEY ) ;
79103 }
80104 projectListeners . forEach ( l => l ( proj ) ) ;
81105}
82106
107+ async function rehydrateProjectDirectory ( proj : MenuTreeWithCodeOptions ) {
108+ const savedHandle = await get ( 'last_project_dir' ) ;
109+
110+ if ( savedHandle ) {
111+ // 1. Check if we still have readwrite permissions (usually "prompt" or "denied" after tab close)
112+ let perm = await savedHandle . queryPermission ( { mode : 'readwrite' } ) ;
113+
114+ console . log ( `Permission status: ${ perm } ` ) ;
115+
116+ if ( perm !== 'granted' ) {
117+ console . log ( `Re-request perms` ) ;
118+ // 2. Trigger a simple browser pop-up asking to restore access to that specific folder
119+ try {
120+ perm = await savedHandle . requestPermission ( { mode : 'readwrite' } ) ;
121+ } catch ( e ) {
122+ console . error ( "Failed to request permission" , e ) ;
123+ perm = 'denied' ;
124+ }
125+ }
126+
127+ globalDirectoryHandle = savedHandle ;
128+
129+ console . log ( `Permission status II: ${ perm } ` ) ;
130+
131+ if ( perm === 'granted' ) {
132+ console . log ( `Successfully rehydrated folder: ${ savedHandle . name } ` ) ;
133+ setCurrentlyOpenProject ( proj , savedHandle ) ;
134+ return savedHandle ; // You are fully back in business without the picker!
135+ } else {
136+ console . log ( `Failed to rehydrate folder: ${ savedHandle . name } ` ) ;
137+ }
138+ }
139+ return null ; // Fallback to recommend closing project
140+ }
141+
83142export function useCurrentlyOpenProject ( ) {
84143 const [ project , setProject ] = React . useState < MenuTreeWithCodeOptions | null > ( ( ) => {
85144 if ( currentlyOpenProject ) return currentlyOpenProject ;
@@ -89,6 +148,12 @@ export function useCurrentlyOpenProject() {
89148 const parsed = JSON . parse ( saved ) ;
90149 const restored = parseEmfJsonToProject ( parsed . json , parsed . mode as RoundTripMode ) ;
91150 setCurrentlyOpenProject ( restored ) ;
151+ if ( restored . roundTripMode === RoundTripMode . DIRECTORY_IN_BROWSER ) {
152+ rehydrateProjectDirectory ( restored )
153+ . catch ( ( ) => {
154+ alert ( "Failed to restore last project directory, project will not be able to save" ) ;
155+ } ) ;
156+ }
92157 return restored ;
93158 } catch ( e ) {
94159 console . error ( "Failed to restore project from localStorage" , e ) ;
@@ -214,8 +279,18 @@ export const MainRoutes = () => {
214279 < Route path = "/io-expanders" element = { < IoExpanderComponent /> } />
215280 < Route path = "/release-notes" element = { < ReleaseNotes /> } />
216281 < Route path = "/bitmap-generator" element = { < div >
217- < h1 > Bitmap Generator</ h1 >
218- < p > This will be ported from the Java version shortly, in the mean time continue to use the java version for this function.</ p >
282+ < h1 > Font and Bitmap Editor</ h1 >
283+ < p > You can edit fonts, title widgets and bitmaps using the new Font Bmp Editor application.</ p >
284+ < p > The plan is to make the app available on the Windows store and MacOS app store, but even right
285+ now there are nightly builds for all platforms.</ p >
286+ < img src = { fontEdIcon } alt = "Font and Bitmap Editor application screen" />
287+ < h2 > How to get the editor</ h2 >
288+ < ul >
289+ < li > < a href = "https://github.com/TcMenu/tcMenu/actions/workflows/build_windows.yml" > Windows Nightly</ a > </ li >
290+ < li > < a href = "https://github.com/TcMenu/tcMenu/actions/workflows/build_mac.yml" > MacOS Nightly</ a > </ li >
291+ < li > < a href = "https://github.com/TcMenu/tcMenu/actions/workflows/build_linux.yml" > Linux Nightly</ a > </ li >
292+ < li > < a href = "https://github.com/TcMenu/tcMenu/" > From source</ a > </ li >
293+ </ ul >
219294 </ div > } />
220295 </ Routes >
221296 ) ;
@@ -253,7 +328,7 @@ function App() {
253328 < li > < NavLink to = "/menu-edit" > Menu Edit</ NavLink > </ li >
254329 { project && < li > < NavLink to = "/generate-code" > Generate Code</ NavLink > </ li > }
255330 { project && < li > < NavLink to = "/io-expanders" > Io Expanders</ NavLink > </ li > }
256- < li > < NavLink to = "/bitmap-generator" > Bitmaps/Widgets/Fonts </ NavLink > </ li >
331+ < li > < NavLink to = "/bitmap-generator" > Font Bmp Editor </ NavLink > </ li >
257332 </ ul >
258333 </ nav >
259334 </ div >
0 commit comments