import { dialog , shell } from "electron" ; import * as pathlib from "path" ; import { FSAL } from "./fsal/fsal" ; import NoteworthyApp from "./app" ; import { IFileWithContents , IPossiblyUntitledFile , IDirEntryMeta , IFileMeta } from "@common/files" ; import { DialogSaveDiscardOptions } from "@common/dialog" ; import { to } from "@common/util/to" ; import { filterNonVoid } from "@common/util/non-void" ; import { WorkspaceService } from "./workspace/workspace-service" ; import { ThemeService } from "./theme/theme-service" ; import { PluginService } from "./plugins/plugin-service" ; import { IOutline } from "./plugins/outline-plugin" ; import { ITagSearchResult , SearchResult , IFileSearchResult , IHashSearchResult , CrossRefPlugin } from "./plugins/crossref-plugin" ; import { WorkspacePlugin } from "./plugins/plugin" ; import { IMetadata } from "./plugins/metadata-plugin" ; import { getFileMetadata } from "@common/files" ; todo type FunctionPropertyNames < T > = { [ K in keyof T ] : T [ K ] extends Function ? K : never ; } [ keyof T ] ; type FunctionProperties < T > = Pick < T , FunctionPropertyNames < T > > ; export class MainIpc_LifecycleHandlers { todo constructor ( private _app ) { } async requestAppQuit ( ) { todo if ( this . _app . _renderProxy ) { let [ err , result ] = await to < string > ( this . _app . _renderProxy . requestClose ( ) ) ; if ( err == "Cancel" ) { return ; } else if ( err ) { return Promise . reject ( err ) ; } } this . _app . quit ( ) ; } } export class MainIpc_FileHandlers { constructor ( private _app , private _fsal , private _workspaceService , private _pluginService ) { } async requestFileCreate ( path , contents = "" ) { todo return this . _fsal . createFile ( path , contents ) . then ( ( ) => { return this . _workspaceService . updatePath ( path ) || null ; } , ( reason ) => { console . error ( "error creating file" , reason ) ; return null ; } ) ; } async requestFileSave ( file ) { if ( ! this . _app . window ) { return false ; } this . _fsal . saveFile ( file . path , file . contents , false ) ; todo this . _app . _renderProxy ?. fileDidSave ( { saveas : false , path : file . path } ) ; return true ; } async requestFileContents ( fileInfo ) { let { hash } = fileInfo ; if ( hash === undefined ) { throw new Error ( "MainIPC :: requestFileContents() :: no file path or hash provided" ) ; } let fileMeta ; if ( hash === undefined || ! ( fileMeta = this . _workspaceService . getFileByHash ( hash ) ) ) { todo console . log ( hash , hash && this . _workspaceService . getFileByHash ( hash ) ) ; throw new Error ( "file loading from arbitrary path not implemented" ) ; } const fileContents = this . _fsal . readFile ( fileMeta . path ) ; if ( fileContents === null ) { throw new Error ( "MainIPC :: failed to read file" ) ; } let file = { parent : null , contents : fileContents , ... fileMeta } ; return file ; } } export class MainIpc_DialogHandlers { todo constructor ( private _app , private _fsal , private _workspaceService ) { } showNotification ( msg ) { } showError ( msg ) { } dialogFolderOpen ( ) { if ( ! this . _app . window ) { return ; } const dirPaths = dialog . showOpenDialogSync ( this . _app . window . window , { properties : [ 'openDirectory' , 'createDirectory' ] , } ) ; if ( ! dirPaths || ! dirPaths . length ) return ; this . _workspaceService . setWorkspaceDir ( dirPaths [ 0 ] ) ; } dialogFileOpen ( ) { if ( ! this . _app . window ) { return ; } const filePaths = dialog . showOpenDialogSync ( this . _app . window . window , { properties : [ 'openFile' ] , } ) ; if ( ! filePaths || ! filePaths . length ) return ; throw new Error ( "MainIpc_DialogHandlers :: opening individual file from path is not implemented" ) ; } async dialogFileSaveAs ( file ) { if ( ! this . _app . window ) { return null ; } const newFilePath = dialog . showSaveDialogSync ( this . _app . window . window , { defaultPath : file . path || "" , } ) ; if ( ! newFilePath ) return null ; this . _fsal . saveFile ( newFilePath , file . contents , false ) ; this . _app . _renderProxy ?. fileDidSave ( { saveas : true , path : newFilePath } ) ; return newFilePath ; } todo async askSaveDiscardChanges ( filePath ) { if ( ! this . _app . window ) { throw new Error ( "no window open! cannot open dialog!" ) ; } let response = await dialog . showMessageBox ( this . _app . window . window , { type : "warning" , title : "Warning: Unsaved Changes" , message : `File (${ filePath }) contains unsaved changes.` , buttons : Array . from ( DialogSaveDiscardOptions ) , defaultId : DialogSaveDiscardOptions . indexOf ( "Save" ) , cancelId : DialogSaveDiscardOptions . indexOf ( "Cancel" ) , } ) ; return DialogSaveDiscardOptions [ response . response ] ; } } export class MainIpc_ThemeHandlers { todo constructor ( private _themeService ) { } async requestThemeRefresh ( ) { this . _themeService . setTheme ( ) ; } } export class MainIpc_TagHandlers { todo constructor ( private _app , private _workspaceService , private _pluginService , private _fileHandlers ) { } async tagSearch ( query ) { let plugin = this . _pluginService . getWorkspacePluginByName ( "crossref_plugin" ) ; if ( ! plugin ) { return [ ] ; } const hashes = plugin . getTagMentions ( query ) ; if ( hashes === null ) { return [ ] ; } return filterNonVoid ( hashes . map ( hash => ( this . _workspaceService . getFileByHash ( hash ) ) ) ) ; } async fuzzyTagSearch ( query ) { let plugin = this . _pluginService . getWorkspacePluginByName ( "crossref_plugin" ) ; if ( ! plugin ) { return [ ] ; } return plugin . fuzzyTagSearch ( query ) ; } async fuzzyTagFileSearch ( query ) { let maybePlugin = this . _pluginService . getWorkspacePluginByName ( "crossref_plugin" ) ; if ( ! maybePlugin ) { return [ ] ; } let plugin = maybePlugin ; let tagResults = plugin . fuzzyTagSearch ( query ) ; let fileHashes = new Set < string > ( ) ; tagResults . flatMap ( result => plugin . getTagMentions ( result . result ) ) . forEach ( hash => fileHashes . add ( hash ) ) ; let docResults = [ ] ; fileHashes . forEach ( hash => { let fileMeta = this . _workspaceService . getFileByHash ( hash ) ; if ( fileMeta !== null ) { docResults . push ( { type : "file-result" , file : fileMeta } ) ; } } ) ; let results = [ ] ; return results . concat ( tagResults , docResults ) ; } async getHashForTag ( data ) { let plugin = this . _pluginService . getWorkspacePluginByName ( "crossref_plugin" ) ; if ( ! plugin ) { console . error ( "crossref plugin not active" ) ; return null ; } let defs = plugin . getDefsForTag ( data . tag ) ; let fileHash ; if ( defs == null ) { console . error ( "crossref plugin not active" ) ; return null ; } else if ( defs . length == 0 ) { if ( ! data . create ) { return null ; } console . log ( `MainIpc_TagHandlers :: creating file for tag '${ data . tag }'` ) ; todo let fileName = data . tag + ".md" ; todo let filePath ; if ( data . directoryHint ) { filePath = pathlib . join ( data . directoryHint , fileName ) ; } else { filePath = this . _workspaceService . resolveWorkspaceRelativePath ( fileName ) ; } if ( ! filePath ) { console . error ( "MainIpc_TagHandlers :: could not create file for tag, no active workspace" ) ; return null ; } todo let fileContents = this . _app . getDefaultFileContents ( ".md" , fileName ) ; let file = await this . _fileHandlers . requestFileCreate ( filePath , fileContents ) ; if ( ! file ) { console . error ( "MainIpc_TagHandlers :: unknown error creating file for tag" ) ; return null ; } fileHash = file . hash ; } else if ( defs . length == 1 ) { fileHash = defs [ 0 ] ; } else { todo console . warn ( `MainIPC_TagHandlers :: more than one defining file for tag ${ data . tag } (not implemented)` ) ; return null ; } return fileHash ; } async getFileForTag ( data ) { let fileHash = await this . getHashForTag ( data ) ; if ( ! fileHash ) return null ; return this . _workspaceService . getFileByHash ( fileHash ) ; } } export class MainIpc_OutlineHandlers { constructor ( private _pluginService ) { } async requestOutlineForHash ( hash ) { let plugin = this . _pluginService . getWorkspacePluginByName ( "outline_plugin" ) ; if ( ! plugin ) { return [ ] ; } return plugin . getOutlineForHash ( hash ) ; } } export class MainIpc_ShellHandlers { constructor ( ) { } async requestExternalLinkOpen ( url ) { shell . openExternal ( url , { activate : true } ) ; } } export class MainIpc_MetadataHandlers { constructor ( private _pluginService ) { } async getMetadataForHash ( hash ) { let plugin = this . _pluginService . getWorkspacePluginByName ( "metadata_plugin" ) ; if ( ! plugin ) { console . error ( "no plugin!" ) ; return null ; } console . log ( `getMetadataForHash :: ${ hash }` ) ; return plugin . getMetadataForHash ( hash ) ; } } export class MainIpc_NavigationHandlers { private _navHistory : IFileMeta [ ] ; private _navIdx : number ; constructor ( private _app , private _workspaceService , private _pluginService , private _fileHandlers , private _tagHandlers ) { this . _navHistory = [ ] ; this . _navIdx = 0 ; } returns private async _navigate ( fileInfo ) { console . log ( `MainIPC_NavigationHandlers :: navigate :: ${ fileInfo . hash }` ) ; let file = await this . _fileHandlers . requestFileContents ( fileInfo ) ; if ( ! file ) { return null ; } this . _app . _renderProxy ?. fileDidOpen ( file ) ; return getFileMetadata ( file ) ; } public getNavigationHistory ( ) { return { history : [ ... this . _navHistory ] , currentIdx : this . _navIdx } ; } public navigateNext ( ) { let nextIdx = Math . min ( Math . max ( this . _navIdx , 0 ) , this . _navHistory . length ) ; let foundValid = false ; while ( nextIdx + 1 < this . _navHistory . length ) { nextIdx = nextIdx + 1 ; if ( this . _workspaceService . getFileByHash ( this . _navHistory [ nextIdx ] . hash ) ) { foundValid = true ; break ; } } if ( ! foundValid || nextIdx === this . _navIdx ) { return ; } let file = this . _navigate ( { hash : this . _navHistory [ nextIdx ] . hash } ) ; if ( ! file ) { return ; } this . _navIdx = nextIdx ; this . _app . _renderProxy ?. navHistoryChanged ( { history : this . _navHistory , currentIdx : this . _navIdx } ) ; } public navigatePrev ( ) { let prevIdx = Math . min ( Math . max ( this . _navIdx , 0 ) , this . _navHistory . length ) ; let foundValid = false ; while ( prevIdx - 1 > 0 ) { prevIdx = prevIdx - 1 ; if ( this . _workspaceService . getFileByHash ( this . _navHistory [ prevIdx ] . hash ) ) { foundValid = true ; break ; } } if ( ! foundValid || prevIdx === this . _navIdx ) { return ; } let file = this . _navigate ( { hash : this . _navHistory [ prevIdx ] . hash } ) ; if ( ! file ) { return ; } this . _navIdx = prevIdx ; this . _app . _renderProxy ?. navHistoryChanged ( { history : this . _navHistory , currentIdx : this . _navIdx } ) ; } async navigateToHash ( fileInfo ) { if ( ! this . _app . window ) { return ; } let file = await this . _navigate ( fileInfo ) ; if ( ! file ) { return ; } this . _navHistory . splice ( this . _navIdx + 1 , this . _navHistory . length - this . _navIdx + 1 , file ) ; this . _navIdx = this . _navHistory . length - 1 ; this . _app . _renderProxy ?. navHistoryChanged ( { history : this . _navHistory , currentIdx : this . _navIdx } ) ; } async navigateToTag ( data ) { if ( ! this . _app . window ) { return ; } let fileHash = await this . _tagHandlers . getHashForTag ( data ) ; if ( ! fileHash ) return ; this . navigateToHash ( { hash : fileHash } ) ; } async navigateToIndex ( idx ) { if ( idx < 0 || idx >= this . _navHistory . length ) { return Promise . reject ( "MainIpc_NavigationHandlers :: navigateToIndex :: index out of bounds" ) ; } this . _navigate ( { hash : this . _navHistory [ idx ] . hash } ) ; this . _navIdx = idx ; this . _app . _renderProxy ?. navHistoryChanged ( { history : this . _navHistory , currentIdx : this . _navIdx } ) ; } } export interface MainIpcHandlers { lifecycle : MainIpc_LifecycleHandlers ; file : MainIpc_FileHandlers ; theme : MainIpc_ThemeHandlers ; shell : MainIpc_ShellHandlers ; dialog : MainIpc_DialogHandlers ; todo tag : MainIpc_TagHandlers ; outline : MainIpc_OutlineHandlers ; metadata : MainIpc_MetadataHandlers ; navigation : MainIpc_NavigationHandlers ; } ; export type MainIpcChannel = keyof MainIpcHandlers ; export type MainIpcEvents = { [ K in MainIpcChannel ] : FunctionPropertyNames < MainIpcHandlers [ K ] > ; } ;