diff --git a/docs/API-Reference/view/MainViewFactory.md b/docs/API-Reference/view/MainViewFactory.md index 51e1db479..5678cf93d 100644 --- a/docs/API-Reference/view/MainViewFactory.md +++ b/docs/API-Reference/view/MainViewFactory.md @@ -3,39 +3,80 @@ const MainViewFactory = brackets.getModule("view/MainViewFactory") ``` - + -## \_ -MainViewFactory is a singleton for managing view factories. Registering a view factory: ```js registerViewFactory({ canOpenFile: function (fullPath) { return (fullPath.slice(-4) === ".ico"); }, openFile: function(file, pane) { return createIconView(file, pane); } }); ``` The openFile method is used to open the file and construct a view of it. Implementation should add the view to the pane ```js function createIconView(file, pane) { // IconView will construct its DOM and append // it to pane.$el var view = new IconView(file, pane.$el); // Then tell the pane to add it to // its view map and show it pane.addView(view, true); return new $.Deferred().resolve().promise(); } ``` Factories should only create 1 view of a file per pane. Brackets currently only supports 1 view of a file open at a given time but that may change to allow the same file open in more than 1 pane. Therefore Factories can do a simple check to see if a view already exists and show it before creating a new one: ```js var view = pane.getViewForPath(file.fullPath); if (view) { pane.showView(view); } else { return createIconView(file, pane); } ``` +## view/MainViewFactory +MainViewFactory is a singleton for managing view factories. -**Kind**: global variable - +Registering a view factory: +```js + registerViewFactory({ + canOpenFile: function (fullPath) { + return (fullPath.slice(-4) === ".ico"); + }, + openFile: function(file, pane) { + return createIconView(file, pane); + } + }); +``` + The openFile method is used to open the file and construct + a view of it. Implementation should add the view to the pane +```js + function createIconView(file, pane) { + // IconView will construct its DOM and append + // it to pane.$el + var view = new IconView(file, pane.$el); + // Then tell the pane to add it to + // its view map and show it + pane.addView(view, true); + return new $.Deferred().resolve().promise(); + } +``` + Factories should only create 1 view of a file per pane. Brackets currently only supports 1 view of + a file open at a given time but that may change to allow the same file open in more than 1 pane. Therefore + Factories can do a simple check to see if a view already exists and show it before creating a new one: +```js + var view = pane.getViewForPath(file.fullPath); + if (view) { + pane.showView(view); + } else { + return createIconView(file, pane); + } +``` + + +* [view/MainViewFactory](#module_view/MainViewFactory) + * [.registerViewFactory(factory)](#module_view/MainViewFactory..registerViewFactory) + * [.findSuitableFactoryForPath(fullPath)](#module_view/MainViewFactory..findSuitableFactoryForPath) ⇒ Factory + * [.Factory](#module_view/MainViewFactory..Factory) : Object + + -## registerViewFactory(factory) +### view/MainViewFactory.registerViewFactory(factory) Registers a view factory -**Kind**: global function +**Kind**: inner method of [view/MainViewFactory](#module_view/MainViewFactory) | Param | Type | Description | | --- | --- | --- | -| factory | [Factory](#Factory) | The view factory to register. | +| factory | Factory | The view factory to register. | - + -## findSuitableFactoryForPath(fullPath) ⇒ [Factory](#Factory) +### view/MainViewFactory.findSuitableFactoryForPath(fullPath) ⇒ Factory Finds a factory that can open the specified file -**Kind**: global function -**Returns**: [Factory](#Factory) - A factory that can create a view for the path or undefined if there isn't one. +**Kind**: inner method of [view/MainViewFactory](#module_view/MainViewFactory) +**Returns**: Factory - A factory that can create a view for the path or undefined if there isn't one. | Param | Type | Description | | --- | --- | --- | | fullPath | string | The file to open. | - + -## Factory : Object -**Kind**: global typedef +### view/MainViewFactory.Factory : Object +**Kind**: inner typedef of [view/MainViewFactory](#module_view/MainViewFactory) **Properties** | Name | Type | Description | diff --git a/docs/API-Reference/view/MainViewManager.md b/docs/API-Reference/view/MainViewManager.md index f4a71dde3..0d38ca336 100644 --- a/docs/API-Reference/view/MainViewManager.md +++ b/docs/API-Reference/view/MainViewManager.md @@ -3,9 +3,9 @@ const MainViewManager = brackets.getModule("view/MainViewManager") ``` - + -## \_ +## view/MainViewManager MainViewManager manages the arrangement of all open panes as well as provides the controller logic behind all views in the MainView (e.g. ensuring that a file doesn't appear in 2 lists) @@ -56,226 +56,261 @@ This module dispatches several events: To listen for events, do something like this: (see EventDispatcher for details on this pattern) `MainViewManager.on("eventname", handler);` -**Kind**: global variable - -## EVENT\_CURRENT\_FILE\_CHANGE : string +* [view/MainViewManager](#module_view/MainViewManager) + * [.EVENT_CURRENT_FILE_CHANGE](#module_view/MainViewManager..EVENT_CURRENT_FILE_CHANGE) : string + * [.ALL_PANES](#module_view/MainViewManager..ALL_PANES) + * [.ACTIVE_PANE](#module_view/MainViewManager..ACTIVE_PANE) + * [.isExclusiveToPane(File)](#module_view/MainViewManager..isExclusiveToPane) ⇒ Object + * [.getActivePaneId()](#module_view/MainViewManager..getActivePaneId) ⇒ string + * [.focusActivePane()](#module_view/MainViewManager..focusActivePane) + * [.setActivePaneId(paneId)](#module_view/MainViewManager..setActivePaneId) + * [.getCurrentlyViewedFile(paneId)](#module_view/MainViewManager..getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor(paneId)](#module_view/MainViewManager..getCurrentlyViewedEditor) ⇒ Editor + * [.getAllViewedEditors()](#module_view/MainViewManager..getAllViewedEditors) ⇒ Object + * [.getCurrentlyViewedPath(paneId)](#module_view/MainViewManager..getCurrentlyViewedPath) ⇒ string + * [.cacheScrollState(paneId)](#module_view/MainViewManager..cacheScrollState) + * [.restoreAdjustedScrollState(paneId, heightDelta)](#module_view/MainViewManager..restoreAdjustedScrollState) + * [.getWorkingSet(paneId)](#module_view/MainViewManager..getWorkingSet) ⇒ Array.<File> + * [.getAllOpenFiles()](#module_view/MainViewManager..getAllOpenFiles) ⇒ array.<File> + * [.getPaneIdList()](#module_view/MainViewManager..getPaneIdList) ⇒ array.<string> + * [.getWorkingSetSize(paneId)](#module_view/MainViewManager..getWorkingSetSize) ⇒ number + * [.getPaneTitle(paneId)](#module_view/MainViewManager..getPaneTitle) ⇒ string + * [.getPaneCount()](#module_view/MainViewManager..getPaneCount) ⇒ number + * [.findInAllWorkingSets(fullPath)](#module_view/MainViewManager..findInAllWorkingSets) ⇒ Object + * [.findInOpenPane(fullPath)](#module_view/MainViewManager..findInOpenPane) ⇒ Object + * [.findInWorkingSet(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSet) ⇒ number + * [.findInWorkingSetByAddedOrder(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSetByAddedOrder) ⇒ number + * [.findInWorkingSetByMRUOrder(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSetByMRUOrder) ⇒ number + * [.addToWorkingSet(paneId, file, [index], [forceRedraw])](#module_view/MainViewManager..addToWorkingSet) + * [.addListToWorkingSet(paneId, fileList)](#module_view/MainViewManager..addListToWorkingSet) + * [.switchPaneFocus()](#module_view/MainViewManager..switchPaneFocus) + * [.traverseToNextViewByMRU(direction)](#module_view/MainViewManager..traverseToNextViewByMRU) ⇒ Object + * [.traverseToNextViewInListOrder(direction)](#module_view/MainViewManager..traverseToNextViewInListOrder) ⇒ Object + * [.beginTraversal()](#module_view/MainViewManager..beginTraversal) + * [.endTraversal()](#module_view/MainViewManager..endTraversal) + * [.setLayoutScheme(rows, columns)](#module_view/MainViewManager..setLayoutScheme) + * [.getLayoutScheme()](#module_view/MainViewManager..getLayoutScheme) ⇒ Object + + + +### view/MainViewManager.EVENT\_CURRENT\_FILE\_CHANGE : string Event current file change -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## ALL\_PANES +### view/MainViewManager.ALL\_PANES Special paneId shortcut that can be used to specify that all panes should be targeted by the API. Not all APIs support this constnant. Check the API documentation before use. -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## ACTIVE\_PANE +### view/MainViewManager.ACTIVE\_PANE Special paneId shortcut that can be used to specify that the API should target the focused pane only. All APIs support this shortcut. -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## isExclusiveToPane(File) ⇒ Object +### view/MainViewManager.isExclusiveToPane(File) ⇒ Object Checks whether a file is listed exclusively in the provided pane -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | File | File | the file | - + -## getActivePaneId() ⇒ string +### view/MainViewManager.getActivePaneId() ⇒ string Retrieves the currently active Pane Id -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - Active Pane's ID. - + -## focusActivePane() +### view/MainViewManager.focusActivePane() Focuses the current pane. If the current pane has a current view, then the pane will focus the view. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## setActivePaneId(paneId) +### view/MainViewManager.setActivePaneId(paneId) Switch active pane to the specified pane id (or ACTIVE_PANE/ALL_PANES, in which case this call does nothing). -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane to activate | - + -## getCurrentlyViewedFile(paneId) ⇒ File +### view/MainViewManager.getCurrentlyViewedFile(paneId) ⇒ File Retrieves the currently viewed file of the specified paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: File - File object of the currently viewed file, or null if there isn't one or there's no such pane | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed file | - + -## getCurrentlyViewedEditor(paneId) ⇒ Editor +### view/MainViewManager.getCurrentlyViewedEditor(paneId) ⇒ Editor Retrieves the currently viewed editor of the specified paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Editor - currently editor, or null if there isn't one or there's no such pane | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed editor | - + -## getAllViewedEditors() ⇒ Object +### view/MainViewManager.getAllViewedEditors() ⇒ Object Gets an array of editors open in panes with their pane IDs. Can return an empty array if no editors are open. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - An array of objects, each containing an editor and its corresponding pane ID. - + -## getCurrentlyViewedPath(paneId) ⇒ string +### view/MainViewManager.getCurrentlyViewedPath(paneId) ⇒ string Retrieves the currently viewed path of the pane specified by paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - the path of the currently viewed file or null if there isn't one | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed path | - + -## cacheScrollState(paneId) +### view/MainViewManager.cacheScrollState(paneId) Caches the specified pane's current scroll state If there was already cached state for the specified pane, it is discarded and overwritten -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to cache the scroll state, ALL_PANES or ACTIVE_PANE | - + -## restoreAdjustedScrollState(paneId, heightDelta) +### view/MainViewManager.restoreAdjustedScrollState(paneId, heightDelta) Restores the scroll state from cache and applies the heightDelta The view implementation is responsible for applying or ignoring the heightDelta. This is used primarily when a modal bar opens to keep the editor from scrolling the current page out of view in order to maintain the appearance. The state is removed from the cache after calling this function. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to adjust the scroll state, ALL_PANES or ACTIVE_PANE | | heightDelta | number | delta H to apply to the scroll state | - + -## getWorkingSet(paneId) ⇒ Array.<File> +### view/MainViewManager.getWorkingSet(paneId) ⇒ Array.<File> Retrieves the WorkingSet for the given paneId not including temporary views -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the view list, ALL_PANES or ACTIVE_PANE | - + -## getAllOpenFiles() ⇒ array.<File> +### view/MainViewManager.getAllOpenFiles() ⇒ array.<File> Retrieves the list of all open files including temporary views -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: array.<File> - the list of all open files in all open panes - + -## getPaneIdList() ⇒ array.<string> +### view/MainViewManager.getPaneIdList() ⇒ array.<string> Retrieves the list of all open pane ids -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: array.<string> - the list of all open panes - + -## getWorkingSetSize(paneId) ⇒ number +### view/MainViewManager.getWorkingSetSize(paneId) ⇒ number Retrieves the size of the selected pane's view list -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - the number of items in the specified pane | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the workingset size. Can use `ALL_PANES` or `ACTIVE_PANE` | - + -## getPaneTitle(paneId) ⇒ string +### view/MainViewManager.getPaneTitle(paneId) ⇒ string Retrieves the title to display in the workingset view -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - title | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the title | - + -## getPaneCount() ⇒ number +### view/MainViewManager.getPaneCount() ⇒ number Retrieves the number of panes -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## findInAllWorkingSets(fullPath) ⇒ Object +### view/MainViewManager.findInAllWorkingSets(fullPath) ⇒ Object Finds all instances of the specified file in all working sets. If there is a temporary view of the file, it is not part of the result set -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - an array of paneId/index records | Param | Type | Description | | --- | --- | --- | | fullPath | string | path of the file to find views of | - + -## findInOpenPane(fullPath) ⇒ Object -Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. -If the same file is open in multiple panes, all matching panes will be returned. +### view/MainViewManager.findInOpenPane(fullPath) ⇒ Object +Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. +If the same file is open in multiple panes, all matching panes will be returned. If not found in any panes, an empty array will be returned. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - An array of objects, each containing the pane ID and the corresponding editor, if present. | Param | Type | Description | | --- | --- | --- | | fullPath | string | The full path of the file to search for. | - + -## findInWorkingSet(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSet(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -283,12 +318,12 @@ Gets the index of the file matching fullPath in the workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## findInWorkingSetByAddedOrder(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSetByAddedOrder(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the added order workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -296,12 +331,12 @@ Gets the index of the file matching fullPath in the added order workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## findInWorkingSetByMRUOrder(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSetByMRUOrder(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the MRU order workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -309,9 +344,9 @@ Gets the index of the file matching fullPath in the MRU order workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## addToWorkingSet(paneId, file, [index], [forceRedraw]) +### view/MainViewManager.addToWorkingSet(paneId, file, [index], [forceRedraw]) Adds the given file to the end of the workingset, if it is not already there. This API does not create a view of the file, it just adds it to the working set Views of files in the working set are persisted and are not destroyed until the user @@ -319,7 +354,7 @@ Views of files in the working set are persisted and are not destroyed until the made the current view. If a File is already opened then the file is just made current and its view is shown. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | @@ -328,30 +363,30 @@ Views of files in the working set are persisted and are not destroyed until the | [index] | number | Position to add to list (defaults to last); -1 is ignored | | [forceRedraw] | boolean | If true, a workingset change notification is always sent (useful if suppressRedraw was used with removeView() earlier) | - + -## addListToWorkingSet(paneId, fileList) +### view/MainViewManager.addListToWorkingSet(paneId, fileList) Adds the given file list to the end of the workingset. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | The id of the pane in which to add the file object to or ACTIVE_PANE | | fileList | Array.<File> | Array of files to add to the pane | - + -## switchPaneFocus() +### view/MainViewManager.switchPaneFocus() Switch between panes -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## traverseToNextViewByMRU(direction) ⇒ Object +### view/MainViewManager.traverseToNextViewByMRU(direction) ⇒ Object Get the next or previous file in the MRU list. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse. @@ -359,12 +394,12 @@ Get the next or previous file in the MRU list. | --- | --- | --- | | direction | number | Must be 1 or -1 to traverse forward or backward | - + -## traverseToNextViewInListOrder(direction) ⇒ Object +### view/MainViewManager.traverseToNextViewInListOrder(direction) ⇒ Object Get the next or previous file in list order. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse. @@ -372,26 +407,26 @@ Get the next or previous file in list order. | --- | --- | --- | | direction | number | Must be 1 or -1 to traverse forward or backward | - + -## beginTraversal() +### view/MainViewManager.beginTraversal() Indicates that traversal has begun. Can be called any number of times. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## endTraversal() +### view/MainViewManager.endTraversal() Un-freezes the MRU list after one or more beginTraversal() calls. Whatever file is current is bumped to the front of the MRU list. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## setLayoutScheme(rows, columns) +### view/MainViewManager.setLayoutScheme(rows, columns) Changes the layout scheme -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Summay**: Rows or Columns may be 1 or 2 but both cannot be 2. 1x2, 2x1 or 1x1 are the legal values | Param | Type | Description | @@ -399,10 +434,10 @@ Changes the layout scheme | rows | number | (may be 1 or 2) | | columns | number | (may be 1 or 2) | - + -## getLayoutScheme() ⇒ Object +### view/MainViewManager.getLayoutScheme() ⇒ Object Retrieves the current layout scheme. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - - An object containing the number of rows and columns in the layout. diff --git a/docs/API-Reference/view/Pane.md b/docs/API-Reference/view/Pane.md index 399b3df8e..2ca1cdab7 100644 --- a/docs/API-Reference/view/Pane.md +++ b/docs/API-Reference/view/Pane.md @@ -3,59 +3,235 @@ const Pane = brackets.getModule("view/Pane") ``` - + -## Pane -**Kind**: global class +## view/Pane +Pane objects host views of files, editors, etc... Clients cannot access +Pane objects directly. Instead the implementation is protected by the +MainViewManager -- however View Factories are given a Pane object which +they can use to add views. References to Pane objects should not be kept +as they may be destroyed and removed from the DOM. + +To get a custom view, there are two components: + + 1) A View Factory + 2) A View Object + +View objects are anonymous object that have a particular interface. + +Views can be added to a pane but do not have to exist in the Pane object's view list. +Such views are "temporary views". Temporary views are not serialized with the Pane state +or reconstituted when the pane is serialized from disk. They are destroyed at the earliest +opportunity. + +Temporary views are added by calling `Pane.showView()` and passing it the view object. The view +will be destroyed when the next view is shown, the pane is mereged with another pane or the "Close All" +command is exectuted on the Pane. Temporary Editor Views do not contain any modifications and are +added to the workingset (and are no longer tempoary views) once the document has been modified. They +will remain in the working set until closed from that point on. + +Views that have a longer life span are added by calling addView to associate the view with a +filename in the _views object. These views are not destroyed until they are removed from the pane +by calling one of the following: removeView, removeViews, or _reset + +Pane Object Events: + + - viewListChange - Whenever there is a file change to a file in the working set. These 2 events: `DocumentManager.pathRemove` + and `DocumentManager.fileNameChange` will cause a `viewListChange` event so the WorkingSetView can update. + + - currentViewChange - Whenever the current view changes. + (e, newView:View, oldView:View) + + - viewDestroy - Whenever a view has been destroyed + (e, view:View) + +View Interface: + +The view is an anonymous object which has the following method signatures. see ImageViewer for an example or the sample +provided with Brackets `src/extensions/samples/BracketsConfigCentral` + +```js + { + $el:jQuery + getFile: function ():!File + updateLayout: function(forceRefresh:boolean) + destroy: function() + getScrollPos: function():*= + adjustScrollPos: function(state:Object=, heightDelta:number)= + notifyContainerChange: function()= + notifyVisibilityChange: function(boolean)= + focus:function()= + } +``` +When views are created they can be added to the pane by calling `pane.addView()`. +Views can be created and parented by attaching directly to `pane.$el` + + this._codeMirror = new CodeMirror(pane.$el, ...) + +Factories can create a view that's initially hidden by calling `pane.addView(view)` and passing `false` for the show parameter. +Hidden views can be later shown by calling `pane.showView(view)` + +`$el:jQuery!` + + property that stores the jQuery wrapped DOM element of the view. All views must have one so pane objects can manipulate the DOM + element when necessary (e.g. `showView`, `_reparent`, etc...) + +`getFile():File!` + + Called throughout the life of a View when the current file is queried by the system. + +`updateLayout(forceRefresh:boolean)` + + Called to notify the view that it should be resized to fit its parent container. This may be called several times + or only once. Views can ignore the `forceRefresh` flag. It is used for editor views to force a relayout of the editor + which probably isn't necessary for most views. Views should implement their html to be dynamic and not rely on this + function to be called whenever possible. + +`destroy()` + + Views must implement a destroy method to remove their DOM element at the very least. There is no default + implementation and views are hidden before this method is called. The Pane object doesn't make assumptions + about when it is safe to remove a node. In some instances other cleanup must take place before a the DOM + node is destroyed so the implementation details are left to the view. + + Views can implement a simple destroy by calling + + this.$el.remove() + + These members are optional and need not be implemented by Views + + getScrollPos() + adjustScrollPos() + + The system at various times will want to save and restore a view's scroll position. The data returned by `getScrollPos()` + is specific to the view and will be passed back to `adjustScrollPos()` when the scroll position needs to be restored. + + When Modal Bars are invoked, the system calls `getScrollPos()` so that the current scroll psotion of all visible Views can be cached. + That cached scroll position is later passed to `adjustScrollPos()` along with a height delta. The height delta is used to + scroll the view so that it doesn't appear to have "jumped" when invoking the Modal Bar. + + Height delta will be a positive when the Modal Bar is being shown and negative number when the Modal Bar is being hidden. + + `getViewState()` is another optional member that is used to cache a view's state when hiding or destroying a view or closing the project. + The data returned by this member is stored in `ViewStateManager` and is saved with the project. + + Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state + + var view = createIconView(file, pane); + view.restoreViewState(ViewStateManager.getViewState(file.fullPath)); + + Notifications + The following optional methods receive notifications from the Pane object when certain events take place which affect the view: + +`notifyContainerChange()` + + Optional Notification callback called when the container changes. The view can perform any synchronization or state update + it needs to do when its parent container changes. + +`notifyVisiblityChange()` + + Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or + state update it needs to do when its visiblity state changes. + + +* [view/Pane](#module_view/Pane) + * [.Pane](#module_view/Pane..Pane) + * [new Pane(id, $container)](#new_module_view/Pane..Pane_new) + * [.id](#module_view/Pane..Pane+id) : string + * [.$container](#module_view/Pane..Pane+$container) : JQuery + * [.$el](#module_view/Pane..Pane+$el) : JQuery + * [.$header](#module_view/Pane..Pane+$header) : JQuery + * [.$headerText](#module_view/Pane..Pane+$headerText) : JQuery + * [.$headerFlipViewBtn](#module_view/Pane..Pane+$headerFlipViewBtn) : JQuery + * [.$headerCloseBtn](#module_view/Pane..Pane+$headerCloseBtn) : JQuery + * [.$content](#module_view/Pane..Pane+$content) : JQuery + * [.ITEM_NOT_FOUND](#module_view/Pane..Pane+ITEM_NOT_FOUND) + * [.ITEM_FOUND_NO_SORT](#module_view/Pane..Pane+ITEM_FOUND_NO_SORT) + * [.ITEM_FOUND_NEEDS_SORT](#module_view/Pane..Pane+ITEM_FOUND_NEEDS_SORT) + * [.mergeFrom(other)](#module_view/Pane..Pane+mergeFrom) + * [.destroy()](#module_view/Pane..Pane+destroy) + * [.getViewList()](#module_view/Pane..Pane+getViewList) ⇒ Array.<File> + * [.getViewListSize()](#module_view/Pane..Pane+getViewListSize) ⇒ number + * [.findInViewList(fullPath)](#module_view/Pane..Pane+findInViewList) ⇒ number + * [.findInViewListAddedOrder(fullPath)](#module_view/Pane..Pane+findInViewListAddedOrder) ⇒ number + * [.findInViewListMRUOrder(fullPath)](#module_view/Pane..Pane+findInViewListMRUOrder) ⇒ number + * [.reorderItem(file, [index], [force])](#module_view/Pane..Pane+reorderItem) ⇒ number + * [.addToViewList(file, [index])](#module_view/Pane..Pane+addToViewList) ⇒ number + * [.addListToViewList(fileList)](#module_view/Pane..Pane+addListToViewList) ⇒ Array.<File> + * [.makeViewMostRecent(file)](#module_view/Pane..Pane+makeViewMostRecent) + * [.sortViewList(compareFn)](#module_view/Pane..Pane+sortViewList) + * [.swapViewListIndexes(index1, index2)](#module_view/Pane..Pane+swapViewListIndexes) ⇒ boolean + * [.traverseViewListByMRU(direction, [current])](#module_view/Pane..Pane+traverseViewListByMRU) ⇒ File + * [.showInterstitial(show)](#module_view/Pane..Pane+showInterstitial) + * [.getViewForPath(path)](#module_view/Pane..Pane+getViewForPath) ⇒ boolean + * [.addView(view, show)](#module_view/Pane..Pane+addView) + * [.showView(view)](#module_view/Pane..Pane+showView) + * [.updateLayout(forceRefresh)](#module_view/Pane..Pane+updateLayout) + * [.getCurrentlyViewedFile()](#module_view/Pane..Pane+getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor()](#module_view/Pane..Pane+getCurrentlyViewedEditor) ⇒ File + * [.getCurrentlyViewedPath()](#module_view/Pane..Pane+getCurrentlyViewedPath) ⇒ string + * [.destroyViewIfNotNeeded(view)](#module_view/Pane..Pane+destroyViewIfNotNeeded) + * [.removeView(file, suppressOpenNextFile, preventViewChange)](#module_view/Pane..Pane+removeView) ⇒ boolean + * [.removeViews(list)](#module_view/Pane..Pane+removeViews) ⇒ Array.<File> + * [.focus()](#module_view/Pane..Pane+focus) + * [.loadState(state)](#module_view/Pane..Pane+loadState) ⇒ jQuery.Promise + * [.saveState()](#module_view/Pane..Pane+saveState) ⇒ Object + * [.getScrollState()](#module_view/Pane..Pane+getScrollState) ⇒ Object + * [.restoreAndAdjustScrollState([state], [heightDelta])](#module_view/Pane..Pane+restoreAndAdjustScrollState) + + + +### view/Pane.Pane +**Kind**: inner class of [view/Pane](#module_view/Pane) **See**: [MainViewManager](MainViewManager) for more information -* [Pane](#Pane) - * [new Pane(id, $container)](#new_Pane_new) - * [.id](#Pane+id) : string - * [.$container](#Pane+$container) : JQuery - * [.$el](#Pane+$el) : JQuery - * [.$header](#Pane+$header) : JQuery - * [.$headerText](#Pane+$headerText) : JQuery - * [.$headerFlipViewBtn](#Pane+$headerFlipViewBtn) : JQuery - * [.$headerCloseBtn](#Pane+$headerCloseBtn) : JQuery - * [.$content](#Pane+$content) : JQuery - * [.ITEM_NOT_FOUND](#Pane+ITEM_NOT_FOUND) - * [.ITEM_FOUND_NO_SORT](#Pane+ITEM_FOUND_NO_SORT) - * [.ITEM_FOUND_NEEDS_SORT](#Pane+ITEM_FOUND_NEEDS_SORT) - * [.mergeFrom(other)](#Pane+mergeFrom) - * [.destroy()](#Pane+destroy) - * [.getViewList()](#Pane+getViewList) ⇒ Array.<File> - * [.getViewListSize()](#Pane+getViewListSize) ⇒ number - * [.findInViewList(fullPath)](#Pane+findInViewList) ⇒ number - * [.findInViewListAddedOrder(fullPath)](#Pane+findInViewListAddedOrder) ⇒ number - * [.findInViewListMRUOrder(fullPath)](#Pane+findInViewListMRUOrder) ⇒ number - * [.reorderItem(file, [index], [force])](#Pane+reorderItem) ⇒ number - * [.addToViewList(file, [index])](#Pane+addToViewList) ⇒ number - * [.addListToViewList(fileList)](#Pane+addListToViewList) ⇒ Array.<File> - * [.makeViewMostRecent(file)](#Pane+makeViewMostRecent) - * [.sortViewList(compareFn)](#Pane+sortViewList) - * [.swapViewListIndexes(index1, index2)](#Pane+swapViewListIndexes) ⇒ boolean - * [.traverseViewListByMRU(direction, [current])](#Pane+traverseViewListByMRU) ⇒ File - * [.showInterstitial(show)](#Pane+showInterstitial) - * [.getViewForPath(path)](#Pane+getViewForPath) ⇒ boolean - * [.addView(view, show)](#Pane+addView) - * [.showView(view)](#Pane+showView) - * [.updateLayout(forceRefresh)](#Pane+updateLayout) - * [.getCurrentlyViewedFile()](#Pane+getCurrentlyViewedFile) ⇒ File - * [.getCurrentlyViewedEditor()](#Pane+getCurrentlyViewedEditor) ⇒ File - * [.getCurrentlyViewedPath()](#Pane+getCurrentlyViewedPath) ⇒ string - * [.destroyViewIfNotNeeded(view)](#Pane+destroyViewIfNotNeeded) - * [.removeView(file, suppressOpenNextFile, preventViewChange)](#Pane+removeView) ⇒ boolean - * [.removeViews(list)](#Pane+removeViews) ⇒ Array.<File> - * [.focus()](#Pane+focus) - * [.loadState(state)](#Pane+loadState) ⇒ jQuery.Promise - * [.saveState()](#Pane+saveState) ⇒ Object - * [.getScrollState()](#Pane+getScrollState) ⇒ Object - * [.restoreAndAdjustScrollState([state], [heightDelta])](#Pane+restoreAndAdjustScrollState) - - - -### new Pane(id, $container) +* [.Pane](#module_view/Pane..Pane) + * [new Pane(id, $container)](#new_module_view/Pane..Pane_new) + * [.id](#module_view/Pane..Pane+id) : string + * [.$container](#module_view/Pane..Pane+$container) : JQuery + * [.$el](#module_view/Pane..Pane+$el) : JQuery + * [.$header](#module_view/Pane..Pane+$header) : JQuery + * [.$headerText](#module_view/Pane..Pane+$headerText) : JQuery + * [.$headerFlipViewBtn](#module_view/Pane..Pane+$headerFlipViewBtn) : JQuery + * [.$headerCloseBtn](#module_view/Pane..Pane+$headerCloseBtn) : JQuery + * [.$content](#module_view/Pane..Pane+$content) : JQuery + * [.ITEM_NOT_FOUND](#module_view/Pane..Pane+ITEM_NOT_FOUND) + * [.ITEM_FOUND_NO_SORT](#module_view/Pane..Pane+ITEM_FOUND_NO_SORT) + * [.ITEM_FOUND_NEEDS_SORT](#module_view/Pane..Pane+ITEM_FOUND_NEEDS_SORT) + * [.mergeFrom(other)](#module_view/Pane..Pane+mergeFrom) + * [.destroy()](#module_view/Pane..Pane+destroy) + * [.getViewList()](#module_view/Pane..Pane+getViewList) ⇒ Array.<File> + * [.getViewListSize()](#module_view/Pane..Pane+getViewListSize) ⇒ number + * [.findInViewList(fullPath)](#module_view/Pane..Pane+findInViewList) ⇒ number + * [.findInViewListAddedOrder(fullPath)](#module_view/Pane..Pane+findInViewListAddedOrder) ⇒ number + * [.findInViewListMRUOrder(fullPath)](#module_view/Pane..Pane+findInViewListMRUOrder) ⇒ number + * [.reorderItem(file, [index], [force])](#module_view/Pane..Pane+reorderItem) ⇒ number + * [.addToViewList(file, [index])](#module_view/Pane..Pane+addToViewList) ⇒ number + * [.addListToViewList(fileList)](#module_view/Pane..Pane+addListToViewList) ⇒ Array.<File> + * [.makeViewMostRecent(file)](#module_view/Pane..Pane+makeViewMostRecent) + * [.sortViewList(compareFn)](#module_view/Pane..Pane+sortViewList) + * [.swapViewListIndexes(index1, index2)](#module_view/Pane..Pane+swapViewListIndexes) ⇒ boolean + * [.traverseViewListByMRU(direction, [current])](#module_view/Pane..Pane+traverseViewListByMRU) ⇒ File + * [.showInterstitial(show)](#module_view/Pane..Pane+showInterstitial) + * [.getViewForPath(path)](#module_view/Pane..Pane+getViewForPath) ⇒ boolean + * [.addView(view, show)](#module_view/Pane..Pane+addView) + * [.showView(view)](#module_view/Pane..Pane+showView) + * [.updateLayout(forceRefresh)](#module_view/Pane..Pane+updateLayout) + * [.getCurrentlyViewedFile()](#module_view/Pane..Pane+getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor()](#module_view/Pane..Pane+getCurrentlyViewedEditor) ⇒ File + * [.getCurrentlyViewedPath()](#module_view/Pane..Pane+getCurrentlyViewedPath) ⇒ string + * [.destroyViewIfNotNeeded(view)](#module_view/Pane..Pane+destroyViewIfNotNeeded) + * [.removeView(file, suppressOpenNextFile, preventViewChange)](#module_view/Pane..Pane+removeView) ⇒ boolean + * [.removeViews(list)](#module_view/Pane..Pane+removeViews) ⇒ Array.<File> + * [.focus()](#module_view/Pane..Pane+focus) + * [.loadState(state)](#module_view/Pane..Pane+loadState) ⇒ jQuery.Promise + * [.saveState()](#module_view/Pane..Pane+saveState) ⇒ Object + * [.getScrollState()](#module_view/Pane..Pane+getScrollState) ⇒ Object + * [.restoreAndAdjustScrollState([state], [heightDelta])](#module_view/Pane..Pane+restoreAndAdjustScrollState) + + + +#### new Pane(id, $container) Pane Objects are constructed by the MainViewManager object when a Pane view is needed. @@ -64,145 +240,145 @@ Pane Objects are constructed by the MainViewManager object when a Pane view is n | id | string | The id to use to identify this pane. | | $container | jQuery | The parent jQuery container to place the pane view. | - + -### pane.id : string +#### pane.id : string id of the pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$container : JQuery +#### pane.$container : JQuery container where the pane lives -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$el : JQuery +#### pane.$el : JQuery the wrapped DOM node of this pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$header : JQuery +#### pane.$header : JQuery the wrapped DOM node container that contains name of current view and the switch view button, or informational string if there is no view -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerText : JQuery +#### pane.$headerText : JQuery the wrapped DOM node that contains name of current view, or informational string if there is no view -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerFlipViewBtn : JQuery +#### pane.$headerFlipViewBtn : JQuery the wrapped DOM node that is used to flip the view to another pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerCloseBtn : JQuery +#### pane.$headerCloseBtn : JQuery close button of the pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$content : JQuery +#### pane.$content : JQuery the wrapped DOM node that contains views -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.ITEM\_NOT\_FOUND +#### pane.ITEM\_NOT\_FOUND Return value from reorderItem when the Item was not found -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.ITEM\_FOUND\_NO\_SORT +#### pane.ITEM\_FOUND\_NO\_SORT Return value from reorderItem when the Item was found at its natural index and the workingset does not need to be resorted -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.ITEM\_FOUND\_NEEDS\_SORT +#### pane.ITEM\_FOUND\_NEEDS\_SORT Return value from reorderItem when the Item was found and reindexed and the workingset needs to be resorted -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.mergeFrom(other) +#### pane.mergeFrom(other) Merges the another Pane object's contents into this Pane -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | -| other | [Pane](#Pane) | Pane from which to copy | +| other | Pane | Pane from which to copy | - + -### pane.destroy() +#### pane.destroy() Removes the DOM node for the Pane, removes all event handlers and _resets all internal data structures -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.getViewList() ⇒ Array.<File> +#### pane.getViewList() ⇒ Array.<File> Returns a copy of the view file list -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.getViewListSize() ⇒ number +#### pane.getViewListSize() ⇒ number Returns the number of entries in the view file list -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.findInViewList(fullPath) ⇒ number +#### pane.findInViewList(fullPath) ⇒ number Returns the index of the item in the view file list -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - index of the item or -1 if not found | Param | Type | Description | | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.findInViewListAddedOrder(fullPath) ⇒ number +#### pane.findInViewListAddedOrder(fullPath) ⇒ number Returns the order in which the item was added -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - order of the item or -1 if not found | Param | Type | Description | | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.findInViewListMRUOrder(fullPath) ⇒ number +#### pane.findInViewListMRUOrder(fullPath) ⇒ number Returns the order in which the item was last used -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - order of the item or -1 if not found. 0 indicates most recently used, followed by 1 and so on... @@ -210,12 +386,12 @@ Returns the order in which the item was last used | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.reorderItem(file, [index], [force]) ⇒ number +#### pane.reorderItem(file, [index], [force]) ⇒ number reorders the specified file in the view list to the desired position -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - this function returns one of the following manifest constants: ITEM_NOT_FOUND : The request file object was not found ITEM_FOUND_NO_SORT : The request file object was found but it was already at the requested index @@ -227,13 +403,13 @@ reorders the specified file in the view list to the desired position | [index] | number | the new position of the item | | [force] | boolean | true to force the item into that position, false otherwise. (Requires an index be requested) | - + -### pane.addToViewList(file, [index]) ⇒ number +#### pane.addToViewList(file, [index]) ⇒ number Adds the given file to the end of the workingset, if it is not already in the list Does not change which document is currently open in the editor. Completes synchronously. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - index of where the item was added | Param | Type | Description | @@ -241,46 +417,46 @@ Does not change which document is currently open in the editor. Completes synchr | file | File | file to add | | [index] | number | position where to add the item | - + -### pane.addListToViewList(fileList) ⇒ Array.<File> +#### pane.addListToViewList(fileList) ⇒ Array.<File> Adds the given file list to the end of the workingset. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Array.<File> - list of files added to the list | Param | Type | | --- | --- | | fileList | Array.<File> | - + -### pane.makeViewMostRecent(file) +#### pane.makeViewMostRecent(file) Moves the specified file to the front of the MRU (Most Recently Used) list. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | file | File | The file to move to the front of the MRU list. | - + -### pane.sortViewList(compareFn) +#### pane.sortViewList(compareFn) Sorts items in the pane's view list. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | compareFn | function | The function used to compare items in the view list. | - + -### pane.swapViewListIndexes(index1, index2) ⇒ boolean +#### pane.swapViewListIndexes(index1, index2) ⇒ boolean Swaps two items in the file view list (used while dragging items in the working set view) -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - } true | Param | Type | Description | @@ -288,12 +464,12 @@ Swaps two items in the file view list (used while dragging items in the working | index1 | number | the index of the first item to swap | | index2 | number | the index of the second item to swap | - + -### pane.traverseViewListByMRU(direction, [current]) ⇒ File +#### pane.traverseViewListByMRU(direction, [current]) ⇒ File Traverses the list and returns the File object of the next item in the MRU order -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - The File object of the next item in the travesal order or null if there isn't one. | Param | Type | Description | @@ -301,103 +477,103 @@ Traverses the list and returns the File object of the next item in the MRU order | direction | number | Must be 1 or -1 to traverse forward or backward | | [current] | string | the fullPath of the item where traversal is to start. If this parameter is omitted then the path of the current view is used. If the current view is a temporary view then the first item in the MRU list is returned | - + -### pane.showInterstitial(show) +#### pane.showInterstitial(show) Shows the pane's interstitial page -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | show | boolean | show or hide the interstitial page | - + -### pane.getViewForPath(path) ⇒ boolean +#### pane.getViewForPath(path) ⇒ boolean retrieves the view object for the given path -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - show - show or hide the interstitial page | Param | Type | Description | | --- | --- | --- | | path | string | the fullPath of the view to retrieve | - + -### pane.addView(view, show) +#### pane.addView(view, show) Adds a view to the pane -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the View object to add | | show | boolean | true to show the view right away, false otherwise | - + -### pane.showView(view) +#### pane.showView(view) Swaps the current view with the requested view. If the interstitial page is shown, it is hidden. If the currentView is a temporary view, it is destroyed. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the to show | - + -### pane.updateLayout(forceRefresh) +#### pane.updateLayout(forceRefresh) Sets pane content height. Updates the layout causing the current view to redraw itself -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | forceRefresh | boolean | true to force a resize and refresh of the current view, false if just to resize forceRefresh is only used by Editor views to force a relayout of all editor DOM elements. Custom View implementations should just ignore this flag. | - + -### pane.getCurrentlyViewedFile() ⇒ File +#### pane.getCurrentlyViewedFile() ⇒ File Retrieves the File object of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - the File object of the current view or null if there isn't one - + -### pane.getCurrentlyViewedEditor() ⇒ File +#### pane.getCurrentlyViewedEditor() ⇒ File Retrieves the File object of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - the File object of the current view or null if there isn't one - + -### pane.getCurrentlyViewedPath() ⇒ string +#### pane.getCurrentlyViewedPath() ⇒ string Retrieves the path of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: string - the path of the current view or null if there isn't one - + -### pane.destroyViewIfNotNeeded(view) +#### pane.destroyViewIfNotNeeded(view) destroys the view if it isn't needed -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the view to destroy | - + -### pane.removeView(file, suppressOpenNextFile, preventViewChange) ⇒ boolean +#### pane.removeView(file, suppressOpenNextFile, preventViewChange) ⇒ boolean Removes the view and opens the next view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - true if the file was removed from the working set This function will remove a temporary view of a file but will return false in that case @@ -407,13 +583,13 @@ Removes the view and opens the next view | suppressOpenNextFile | boolean | suppresses opening the next file in MRU order | | preventViewChange | boolean | if suppressOpenNextFile is truthy, this flag can be used to prevent the current view from being destroyed. Ignored if suppressOpenNextFile is falsy | - + -### pane.removeViews(list) ⇒ Array.<File> +#### pane.removeViews(list) ⇒ Array.<File> Removes the specifed file from all internal lists, destroys the view of the file (if there is one) and shows the interstitial page if the current view is destroyed. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Array.<File> - Array of File objects removed from the working set. This function will remove temporary views but the file objects for those views will not be found in the result set. Only the file objects removed from the working set are returned. @@ -422,178 +598,47 @@ Removes the specifed file from all internal lists, destroys the view of the file | --- | --- | --- | | list | Array.<File> | Array of files to remove | - + -### pane.focus() +#### pane.focus() Gives focus to the last thing that had focus, the current view or the pane in that order -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.loadState(state) ⇒ jQuery.Promise +#### pane.loadState(state) ⇒ jQuery.Promise serializes the pane state from JSON -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: jQuery.Promise - A promise which resolves to \{fullPath:string, paneId:string} which can be passed as command data to FILE_OPEN | Param | Type | Description | | --- | --- | --- | | state | Object | the state to load | - + -### pane.saveState() ⇒ Object +#### pane.saveState() ⇒ Object Returns the JSON-ified state of the object so it can be serialize -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Object - state - the state to save - + -### pane.getScrollState() ⇒ Object +#### pane.getScrollState() ⇒ Object gets the current view's scroll state data -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Object - scroll state - the current scroll state - + -### pane.restoreAndAdjustScrollState([state], [heightDelta]) +#### pane.restoreAndAdjustScrollState([state], [heightDelta]) tells the current view to restore its scroll state from cached data and apply a height delta -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | [state] | Object | the current scroll state | | [heightDelta] | number | the amount to add or subtract from the state | - - -## \_ -Pane objects host views of files, editors, etc... Clients cannot access -Pane objects directly. Instead the implementation is protected by the -MainViewManager -- however View Factories are given a Pane object which -they can use to add views. References to Pane objects should not be kept -as they may be destroyed and removed from the DOM. - -To get a custom view, there are two components: - - 1) A View Factory - 2) A View Object - -View objects are anonymous object that have a particular interface. - -Views can be added to a pane but do not have to exist in the Pane object's view list. -Such views are "temporary views". Temporary views are not serialized with the Pane state -or reconstituted when the pane is serialized from disk. They are destroyed at the earliest -opportunity. - -Temporary views are added by calling `Pane.showView()` and passing it the view object. The view -will be destroyed when the next view is shown, the pane is mereged with another pane or the "Close All" -command is exectuted on the Pane. Temporary Editor Views do not contain any modifications and are -added to the workingset (and are no longer tempoary views) once the document has been modified. They -will remain in the working set until closed from that point on. - -Views that have a longer life span are added by calling addView to associate the view with a -filename in the _views object. These views are not destroyed until they are removed from the pane -by calling one of the following: removeView, removeViews, or _reset - -Pane Object Events: - - - viewListChange - Whenever there is a file change to a file in the working set. These 2 events: `DocumentManager.pathRemove` - and `DocumentManager.fileNameChange` will cause a `viewListChange` event so the WorkingSetView can update. - - - currentViewChange - Whenever the current view changes. - (e, newView:View, oldView:View) - - - viewDestroy - Whenever a view has been destroyed - (e, view:View) - -View Interface: - -The view is an anonymous object which has the following method signatures. see ImageViewer for an example or the sample -provided with Brackets `src/extensions/samples/BracketsConfigCentral` - -```js - { - $el:jQuery - getFile: function ():!File - updateLayout: function(forceRefresh:boolean) - destroy: function() - getScrollPos: function():*= - adjustScrollPos: function(state:Object=, heightDelta:number)= - notifyContainerChange: function()= - notifyVisibilityChange: function(boolean)= - focus:function()= - } -``` -When views are created they can be added to the pane by calling `pane.addView()`. -Views can be created and parented by attaching directly to `pane.$el` - - this._codeMirror = new CodeMirror(pane.$el, ...) - -Factories can create a view that's initially hidden by calling `pane.addView(view)` and passing `false` for the show parameter. -Hidden views can be later shown by calling `pane.showView(view)` - -`$el:jQuery!` - - property that stores the jQuery wrapped DOM element of the view. All views must have one so pane objects can manipulate the DOM - element when necessary (e.g. `showView`, `_reparent`, etc...) - -`getFile():File!` - - Called throughout the life of a View when the current file is queried by the system. - -`updateLayout(forceRefresh:boolean)` - - Called to notify the view that it should be resized to fit its parent container. This may be called several times - or only once. Views can ignore the `forceRefresh` flag. It is used for editor views to force a relayout of the editor - which probably isn't necessary for most views. Views should implement their html to be dynamic and not rely on this - function to be called whenever possible. - -`destroy()` - - Views must implement a destroy method to remove their DOM element at the very least. There is no default - implementation and views are hidden before this method is called. The Pane object doesn't make assumptions - about when it is safe to remove a node. In some instances other cleanup must take place before a the DOM - node is destroyed so the implementation details are left to the view. - - Views can implement a simple destroy by calling - - this.$el.remove() - - These members are optional and need not be implemented by Views - - getScrollPos() - adjustScrollPos() - - The system at various times will want to save and restore a view's scroll position. The data returned by `getScrollPos()` - is specific to the view and will be passed back to `adjustScrollPos()` when the scroll position needs to be restored. - - When Modal Bars are invoked, the system calls `getScrollPos()` so that the current scroll psotion of all visible Views can be cached. - That cached scroll position is later passed to `adjustScrollPos()` along with a height delta. The height delta is used to - scroll the view so that it doesn't appear to have "jumped" when invoking the Modal Bar. - - Height delta will be a positive when the Modal Bar is being shown and negative number when the Modal Bar is being hidden. - - `getViewState()` is another optional member that is used to cache a view's state when hiding or destroying a view or closing the project. - The data returned by this member is stored in `ViewStateManager` and is saved with the project. - - Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state - - var view = createIconView(file, pane); - view.restoreViewState(ViewStateManager.getViewState(file.fullPath)); - - Notifications - The following optional methods receive notifications from the Pane object when certain events take place which affect the view: - -`notifyContainerChange()` - - Optional Notification callback called when the container changes. The view can perform any synchronization or state update - it needs to do when its parent container changes. - -`notifyVisiblityChange()` - - Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or - state update it needs to do when its visiblity state changes. - -**Kind**: global variable diff --git a/docs/API-Reference/view/SidebarTabs.md b/docs/API-Reference/view/SidebarTabs.md new file mode 100644 index 000000000..fbbd4bdd6 --- /dev/null +++ b/docs/API-Reference/view/SidebarTabs.md @@ -0,0 +1,139 @@ +### Import : +```js +const SidebarTabs = brackets.getModule("view/SidebarTabs") +``` + + + +## view/SidebarTabs +SidebarTabs manages multiple tab panes within the sidebar. It inserts a +`#navTabBar` element after `#mainNavBar` and provides an API for registering +tabs, associating DOM content with tabs, and switching between them. + +Existing sidebar children that are not explicitly associated with a tab via +`addToTab` are treated as belonging to the default "Files" tab. This means +extensions that add DOM nodes to the sidebar will continue to work without +any code changes. + +Tab switching works purely by toggling the `.sidebar-tab-hidden` CSS class +(`display: none !important`). No DOM reparenting or detaching occurs, so +cached jQuery/DOM references held by extensions remain valid. + + +* [view/SidebarTabs](#module_view/SidebarTabs) + * [.SIDEBAR_TAB_FILES](#module_view/SidebarTabs..SIDEBAR_TAB_FILES) : string + * [.EVENT_TAB_ADDED](#module_view/SidebarTabs..EVENT_TAB_ADDED) : string + * [.EVENT_TAB_REMOVED](#module_view/SidebarTabs..EVENT_TAB_REMOVED) : string + * [.EVENT_TAB_CHANGED](#module_view/SidebarTabs..EVENT_TAB_CHANGED) : string + * [.addTab(id, label, iconClass, [options])](#module_view/SidebarTabs..addTab) + * [.addToTab(tabId, $content)](#module_view/SidebarTabs..addToTab) + * [.removeFromTab(tabId, $content)](#module_view/SidebarTabs..removeFromTab) + * [.removeTab(id)](#module_view/SidebarTabs..removeTab) ⇒ boolean + * [.setActiveTab(id)](#module_view/SidebarTabs..setActiveTab) + * [.getActiveTab()](#module_view/SidebarTabs..getActiveTab) ⇒ string + * [.getAllTabs()](#module_view/SidebarTabs..getAllTabs) ⇒ Array.<{id: string, label: string, iconClass: string, priority: number}> + + + +### view/SidebarTabs.SIDEBAR\_TAB\_FILES : string +The built-in Files tab id. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_ADDED : string +Fired when a new tab is registered via `addTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_REMOVED : string +Fired when a tab is removed via `removeTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_CHANGED : string +Fired when the active tab changes via `setActiveTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.addTab(id, label, iconClass, [options]) +Register a new sidebar tab. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| id | string | | Unique tab identifier | +| label | string | | Display text shown in the tab bar | +| iconClass | string | | FontAwesome (or other) icon class string | +| [options] | Object | | | +| [options.priority] | number | 100 | Lower values appear further left | + + + +### view/SidebarTabs.addToTab(tabId, $content) +Associate a DOM node (or jQuery element) with a tab. If the node is not +already a child of `#sidebar`, it is appended. If the tab is not the +currently active tab, the node is immediately hidden. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| tabId | string | The tab to associate with | +| $content | jQuery \| Element | DOM node or jQuery wrapper | + + + +### view/SidebarTabs.removeFromTab(tabId, $content) +Remove a DOM node's association with a tab. If the node was appended by +`addToTab` (was not originally in the sidebar) and is no longer +associated with any tab, it is also removed from the DOM. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| tabId | string | The tab to disassociate from | +| $content | jQuery \| Element | DOM node or jQuery wrapper | + + + +### view/SidebarTabs.removeTab(id) ⇒ boolean +Remove a tab entirely. Only succeeds if all content has been removed via +`removeFromTab` first. Returns false if content still exists. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) +**Returns**: boolean - true if removed, false if content still associated + +| Param | Type | Description | +| --- | --- | --- | +| id | string | The tab id to remove | + + + +### view/SidebarTabs.setActiveTab(id) +Switch the active sidebar tab. Shows nodes associated with the target +tab, hides all others. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| id | string | The tab id to activate | + + + +### view/SidebarTabs.getActiveTab() ⇒ string +Get the currently active tab id. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.getAllTabs() ⇒ Array.<{id: string, label: string, iconClass: string, priority: number}> +Get an array of all registered tab descriptors. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) diff --git a/docs/API-Reference/view/ViewStateManager.md b/docs/API-Reference/view/ViewStateManager.md index b888fee6a..e0092f2fd 100644 --- a/docs/API-Reference/view/ViewStateManager.md +++ b/docs/API-Reference/view/ViewStateManager.md @@ -3,9 +3,9 @@ const ViewStateManager = brackets.getModule("view/ViewStateManager") ``` - + -## \_ +## view/ViewStateManager ViewStateManager is a singleton for views to park their global viwe state. The state is saved with project data but the View or View Factory is responsible for restoring the view state when the view is created. @@ -16,43 +16,49 @@ for later use. Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state. Views determine what data is store in the view state and how to restore it. -**Kind**: global variable - -## reset() +* [view/ViewStateManager](#module_view/ViewStateManager) + * [.reset()](#module_view/ViewStateManager..reset) + * [.updateViewState(view, viewState)](#module_view/ViewStateManager..updateViewState) + * [.getViewState(file)](#module_view/ViewStateManager..getViewState) ⇒ \* + * [.addViewStates(viewStates)](#module_view/ViewStateManager..addViewStates) + + + +### view/ViewStateManager.reset() resets the view state cache -**Kind**: global function - +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) + -## updateViewState(view, viewState) +### view/ViewStateManager.updateViewState(view, viewState) Updates the view state for the specified view -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) | Param | Type | Description | | --- | --- | --- | | view | Object | the to save state | | viewState | \* | any data that the view needs to restore the view state. | - + -## getViewState(file) ⇒ \* +### view/ViewStateManager.getViewState(file) ⇒ \* gets the view state for the specified file -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) **Returns**: \* - whatever data that was saved earlier with a call setViewState | Param | Type | Description | | --- | --- | --- | | file | File | the file to record the view state for | - + -## addViewStates(viewStates) +### view/ViewStateManager.addViewStates(viewStates) adds an array of view states -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) | Param | Type | Description | | --- | --- | --- | diff --git a/docs/API-Reference/view/WorkspaceManager.md b/docs/API-Reference/view/WorkspaceManager.md index a98228275..8d39dbbaa 100644 --- a/docs/API-Reference/view/WorkspaceManager.md +++ b/docs/API-Reference/view/WorkspaceManager.md @@ -3,21 +3,9 @@ const WorkspaceManager = brackets.getModule("view/WorkspaceManager") ``` - + -## PANEL\_TYPE\_BOTTOM\_PANEL : string -Constant representing the type of bottom panel - -**Kind**: global variable - - -## PANEL\_TYPE\_PLUGIN\_PANEL : string -Constant representing the type of plugin panel - -**Kind**: global variable - - -## AppInit +## view/WorkspaceManager Manages layout of panels surrounding the editor area, and size of the editor area (but not its contents). Updates panel sizes when the window is resized. Maintains the max resizing limits for panels, based on @@ -28,32 +16,62 @@ Events: The 2nd arg is the available workspace height. The 3rd arg is a refreshHint flag for internal use (passed in to recomputeLayout) -**Kind**: global constant - -## EVENT\_WORKSPACE\_UPDATE\_LAYOUT +* [view/WorkspaceManager](#module_view/WorkspaceManager) + * _static_ + * [.PANEL_TYPE_BOTTOM_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_BOTTOM_PANEL) : string + * [.PANEL_TYPE_PLUGIN_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_PLUGIN_PANEL) : string + * _inner_ + * [.EVENT_WORKSPACE_UPDATE_LAYOUT](#module_view/WorkspaceManager..EVENT_WORKSPACE_UPDATE_LAYOUT) + * [.EVENT_WORKSPACE_PANEL_SHOWN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_SHOWN) + * [.EVENT_WORKSPACE_PANEL_HIDDEN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_HIDDEN) + * [.createBottomPanel(id, $panel, [minSize])](#module_view/WorkspaceManager..createBottomPanel) ⇒ Panel + * [.createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize])](#module_view/WorkspaceManager..createPluginPanel) ⇒ Panel + * [.getAllPanelIDs()](#module_view/WorkspaceManager..getAllPanelIDs) ⇒ Array + * [.getPanelForID(panelID)](#module_view/WorkspaceManager..getPanelForID) ⇒ Object + * [.recomputeLayout(refreshHint)](#module_view/WorkspaceManager..recomputeLayout) + * [.isPanelVisible(panelID)](#module_view/WorkspaceManager..isPanelVisible) ⇒ boolean + * [.setPluginPanelWidth(width)](#module_view/WorkspaceManager..setPluginPanelWidth) + * [.addEscapeKeyEventHandler(consumerName, eventHandler)](#module_view/WorkspaceManager..addEscapeKeyEventHandler) ⇒ boolean + * [.removeEscapeKeyEventHandler(consumerName)](#module_view/WorkspaceManager..removeEscapeKeyEventHandler) ⇒ boolean + + + +### view/WorkspaceManager.PANEL\_TYPE\_BOTTOM\_PANEL : string +Constant representing the type of bottom panel + +**Kind**: static property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.PANEL\_TYPE\_PLUGIN\_PANEL : string +Constant representing the type of plugin panel + +**Kind**: static property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.EVENT\_WORKSPACE\_UPDATE\_LAYOUT Event triggered when the workspace layout updates. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## EVENT\_WORKSPACE\_PANEL\_SHOWN +### view/WorkspaceManager.EVENT\_WORKSPACE\_PANEL\_SHOWN Event triggered when a panel is shown. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## EVENT\_WORKSPACE\_PANEL\_HIDDEN +### view/WorkspaceManager.EVENT\_WORKSPACE\_PANEL\_HIDDEN Event triggered when a panel is hidden. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## createBottomPanel(id, $panel, [minSize]) ⇒ Panel +### view/WorkspaceManager.createBottomPanel(id, $panel, [minSize]) ⇒ Panel Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible. The panel's size & visibility are automatically saved & restored as a view-state preference. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | @@ -61,14 +79,14 @@ The panel's size & visibility are automatically saved & restored as a view-state | $panel | jQueryObject | DOM content to use as the panel. Need not be in the document yet. Must have an id attribute, for use as a preferences key. | | [minSize] | number | Minimum height of panel in px. | - + -## createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize]) ⇒ Panel +### view/WorkspaceManager.createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize]) ⇒ Panel Creates a new resizable plugin panel associated with the given toolbar icon. Panel is initially invisible. The panel's size & visibility are automatically saved & restored. Only one panel can be associated with a toolbar icon. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | @@ -78,71 +96,71 @@ toolbar icon. | $toolbarIcon | jQueryObject | An icon that should be present in main-toolbar to associate this panel to. The panel will be shown only if the icon is visible on the toolbar and the user clicks on the icon. | | [initialSize] | number | Optional Initial size of panel in px. If not given, panel will use minsize or current size. | - + -## getAllPanelIDs() ⇒ Array +### view/WorkspaceManager.getAllPanelIDs() ⇒ Array Returns an array of all panel ID's -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: Array - List of ID's of all bottom panels - + -## getPanelForID(panelID) ⇒ Object +### view/WorkspaceManager.getPanelForID(panelID) ⇒ Object Gets the Panel interface for the given ID. Can return undefined if no panel with the ID is found. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: Object - Panel object for the ID or undefined | Param | Type | | --- | --- | | panelID | string | - + -## recomputeLayout(refreshHint) +### view/WorkspaceManager.recomputeLayout(refreshHint) Called when an external widget has appeared and needs some of the space occupied by the mainview manager -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | | refreshHint | boolean | true to refresh the editor, false if not | - + -## isPanelVisible(panelID) ⇒ boolean +### view/WorkspaceManager.isPanelVisible(panelID) ⇒ boolean Responsible to check if the panel is visible or not. Returns true if visible else false. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | | --- | | panelID | - + -## setPluginPanelWidth(width) +### view/WorkspaceManager.setPluginPanelWidth(width) Programmatically sets the plugin panel content width to the given value in pixels. The total toolbar width is adjusted to account for the plugin icons bar. Width is clamped to respect panel minWidth and max size (75% of window). No-op if no panel is currently visible. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | | width | number | Desired content width in pixels | - + -## addEscapeKeyEventHandler(consumerName, eventHandler) ⇒ boolean +### view/WorkspaceManager.addEscapeKeyEventHandler(consumerName, eventHandler) ⇒ boolean If any widgets related to the editor needs to handle the escape key event, add it here. returning true from the registered handler will prevent primary escape key toggle panel behavior of phoenix. Note that returning true will no stop the event bubbling, that has to be controlled with the event parameter forwarded to the handler. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: boolean - true if added | Param | Type | Description | @@ -150,12 +168,12 @@ will no stop the event bubbling, that has to be controlled with the event parame | consumerName | string | a unique name for your consumer | | eventHandler | function | If the eventHandler returns true for this callback, the escape key event will not lead to panel toggle default behavior. | - + -## removeEscapeKeyEventHandler(consumerName) ⇒ boolean +### view/WorkspaceManager.removeEscapeKeyEventHandler(consumerName) ⇒ boolean Removing the escape key event consumer. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: boolean - true if removed | Param | Type | Description | diff --git a/src/brackets.js b/src/brackets.js index 75aabed6d..79428dbc7 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -126,6 +126,8 @@ define(function (require, exports, module) { require("search/QuickOpenHelper"); require("file/FileUtils"); require("project/SidebarView"); + require("view/SidebarTabs"); + require("core-ai/main"); require("utils/Resizer"); require("LiveDevelopment/main"); require("utils/NodeConnection"); @@ -290,6 +292,7 @@ define(function (require, exports, module) { WorkspaceManager: require("view/WorkspaceManager"), SearchResultsView: require("search/SearchResultsView"), ScrollTrackMarkers: require("search/ScrollTrackMarkers"), + SidebarTabs: require("view/SidebarTabs"), WorkingSetView: require("project/WorkingSetView"), doneLoading: false }; diff --git a/src/core-ai/main.js b/src/core-ai/main.js new file mode 100644 index 000000000..0e826ee18 --- /dev/null +++ b/src/core-ai/main.js @@ -0,0 +1,44 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * Registers a placeholder AI sidebar tab. This serves as a starting point for + * AI assistant integration. The tab displays a placeholder message until an AI + * provider extension is installed. + */ +define(function (require, exports, module) { + + var AppInit = require("utils/AppInit"), + SidebarTabs = require("view/SidebarTabs"); + + AppInit.appReady(function () { + SidebarTabs.addTab("ai", "AI", "fa-solid fa-wand-magic-sparkles", { priority: 200 }); + + var $content = $( + '
' + + '
' + + '
AI Assistant
' + + '
Please add an AI provider to start using AI
' + + '
' + ); + + SidebarTabs.addToTab("ai", $content); + }); +}); diff --git a/src/styles/Extn-SidebarTabs.less b/src/styles/Extn-SidebarTabs.less new file mode 100644 index 000000000..94cdb7cac --- /dev/null +++ b/src/styles/Extn-SidebarTabs.less @@ -0,0 +1,110 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/* Sidebar tab bar — switches between tab panes within #sidebar */ + +#navTabBar { + display: none; + align-items: center; + background-color: @bc-sidebar-bg; + height: 2rem; + overflow: hidden; + user-select: none; + + &.has-tabs { + display: flex; + } +} + +.sidebar-tab { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0 0.75rem; + height: 100%; + cursor: pointer; + position: relative; + color: @project-panel-text-2; + font-size: 0.8rem; + letter-spacing: 0.3px; + transition: color 0.15s ease, background-color 0.15s ease; + + i { + font-size: 0.75rem; + } + + &:hover { + color: @project-panel-text-1; + background-color: rgba(255, 255, 255, 0.06); + } + + &.active { + color: @project-panel-text-1; + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background-color: @project-panel-text-2; + } + } +} + +/* Hide sidebar children not belonging to the active tab */ +.sidebar-tab-hidden { + display: none !important; +} + +/* AI tab placeholder content */ +.ai-tab-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .box-flex(1); + flex: 1; + color: @project-panel-text-2; + padding: 2rem; + text-align: center; + min-height: 0; + height: 100%; + + .ai-tab-icon { + font-size: 2.5rem; + margin-bottom: 1rem; + opacity: 0.6; + } + + .ai-tab-title { + font-size: 1rem; + font-weight: 600; + color: @project-panel-text-1; + margin-bottom: 0.5rem; + } + + .ai-tab-message { + font-size: 0.8rem; + line-height: 1.4; + opacity: 0.7; + } +} diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 7a44bcd56..063056665 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -46,6 +46,7 @@ @import "Extn-CSSColorPreview.less"; @import "Extn-CustomSnippets.less"; @import "Extn-CollapseFolders.less"; +@import "Extn-SidebarTabs.less"; @import "UserProfile.less"; @import "phoenix-pro.less"; diff --git a/src/view/MainViewFactory.js b/src/view/MainViewFactory.js index 7a8c56638..e951c63f5 100644 --- a/src/view/MainViewFactory.js +++ b/src/view/MainViewFactory.js @@ -59,6 +59,7 @@ * return createIconView(file, pane); * } *``` + * @module view/MainViewFactory */ define(function (require, exports, module) { diff --git a/src/view/MainViewManager.js b/src/view/MainViewManager.js index 9a437adfd..b6eca09f3 100644 --- a/src/view/MainViewManager.js +++ b/src/view/MainViewManager.js @@ -73,6 +73,7 @@ * * To listen for events, do something like this: (see EventDispatcher for details on this pattern) * `MainViewManager.on("eventname", handler);` + * @module view/MainViewManager */ define(function (require, exports, module) { @@ -677,8 +678,8 @@ define(function (require, exports, module) { /** - * Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. - * If the same file is open in multiple panes, all matching panes will be returned. + * Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. + * If the same file is open in multiple panes, all matching panes will be returned. * If not found in any panes, an empty array will be returned. * @param {string} fullPath - The full path of the file to search for. * @return {{paneId: string, editor: ?Editor}} An array of objects, each containing the pane ID and the corresponding editor, if present. @@ -1432,7 +1433,7 @@ define(function (require, exports, module) { * @param {!File} file - The file to close. * @param {Object} [optionsIn] - Optional parameters for the close operation. * @param {boolean} [optionsIn.noOpenNextFile] - If set to true, prevents opening the next file after closing. - * + * * This function does not fail if the file is not open. */ function _close(paneId, file, optionsIn) { diff --git a/src/view/Pane.js b/src/view/Pane.js index e1c7527fd..ea507d6a2 100644 --- a/src/view/Pane.js +++ b/src/view/Pane.js @@ -148,6 +148,7 @@ * * Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or * state update it needs to do when its visiblity state changes. + * @module view/Pane */ define(function (require, exports, module) { diff --git a/src/view/SidebarTabs.js b/src/view/SidebarTabs.js new file mode 100644 index 000000000..993d26162 --- /dev/null +++ b/src/view/SidebarTabs.js @@ -0,0 +1,493 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +// @INCLUDE_IN_API_DOCS + +/** + * SidebarTabs manages multiple tab panes within the sidebar. It inserts a + * `#navTabBar` element after `#mainNavBar` and provides an API for registering + * tabs, associating DOM content with tabs, and switching between them. + * + * Existing sidebar children that are not explicitly associated with a tab via + * `addToTab` are treated as belonging to the default "Files" tab. This means + * extensions that add DOM nodes to the sidebar will continue to work without + * any code changes. + * + * Tab switching works purely by toggling the `.sidebar-tab-hidden` CSS class + * (`display: none !important`). No DOM reparenting or detaching occurs, so + * cached jQuery/DOM references held by extensions remain valid. + * @module view/SidebarTabs + */ +define(function (require, exports, module) { + + const AppInit = require("utils/AppInit"), + EventDispatcher = require("utils/EventDispatcher"); + + // --- Constants ----------------------------------------------------------- + + /** + * The built-in Files tab id. + * @const {string} + */ + const SIDEBAR_TAB_FILES = "sidebar-tab-files"; + + // --- Events -------------------------------------------------------------- + + /** + * Fired when a new tab is registered via `addTab`. + * @const {string} + */ + const EVENT_TAB_ADDED = "tabAdded"; + + /** + * Fired when a tab is removed via `removeTab`. + * @const {string} + */ + const EVENT_TAB_REMOVED = "tabRemoved"; + + /** + * Fired when the active tab changes via `setActiveTab`. + * @const {string} + */ + const EVENT_TAB_CHANGED = "tabChanged"; + + // --- Private state ------------------------------------------------------- + + /** @type {jQuery} + * @private */ + let $navTabBar; + + /** @type {jQuery} + * @private*/ + let $sidebar; + + /** + * Ordered array of registered tab descriptors. + * Each entry: { id, label, iconClass, priority, $tabItem } + * @type {Array} + * @private + */ + const _tabs = []; + + /** + * Map from tabId -> array of DOM elements (not jQuery) associated with + * that tab via `addToTab`. + * @type {Object.>} + * @private + */ + const _tabContent = {}; + + /** + * Set of DOM elements that were appended to #sidebar by `addToTab` (i.e. + * they were NOT already children of #sidebar). Used so `removeFromTab` can + * decide whether to also detach the node from the DOM. + * @type {Set.} + * @private + */ + const _appendedNodes = new Set(); + + /** + * Currently active tab id. + * @type {string} + * @private + */ + let _activeTabId = SIDEBAR_TAB_FILES; + + // --- IDs to always exclude from visibility toggling ---------------------- + + const _EXCLUDED_IDS = { "mainNavBar": true, "navTabBar": true }; + + /** + * CSS classes that mark structural/resizer elements which must never be + * hidden by tab switching. + * @private + */ + const _EXCLUDED_CLASSES = ["horz-resizer", "vert-resizer"]; + + // --- Private helpers ----------------------------------------------------- + + /** + * Returns true if a sidebar child node should never be touched by tab + * switching (e.g. nav bars, resizer handles). + * @private + */ + function _isExcludedNode(node) { + if (_EXCLUDED_IDS[node.id]) { + return true; + } + for (let i = 0; i < _EXCLUDED_CLASSES.length; i++) { + if (node.classList.contains(_EXCLUDED_CLASSES[i])) { + return true; + } + } + return false; + } + + /** + * Rebuild the tab bar DOM to reflect current _tabs (sorted by priority). + * @private + */ + function _rebuildTabBar() { + $navTabBar.empty(); + _tabs.sort(function (a, b) { return a.priority - b.priority; }); + _tabs.forEach(function (tab) { + const $item = $(''); + if (tab.id === _activeTabId) { + $item.addClass("active"); + } + tab.$tabItem = $item; + $navTabBar.append($item); + }); + + // Show/hide the tab bar based on tab count + if (_tabs.length >= 2) { + $navTabBar.addClass("has-tabs"); + } else { + $navTabBar.removeClass("has-tabs"); + } + } + + /** + * Returns true if the given node is explicitly associated with the + * specified tab. + * @private + */ + function _isNodeInTab(node, tabId) { + return _tabContent[tabId] && _tabContent[tabId].indexOf(node) !== -1; + } + + /** + * Returns true if the given node is explicitly associated with ANY + * registered tab. + * @private + */ + function _isNodeInAnyTab(node) { + const tabIds = Object.keys(_tabContent); + for (let i = 0; i < tabIds.length; i++) { + if (_tabContent[tabIds[i]].indexOf(node) !== -1) { + return true; + } + } + return false; + } + + /** + * Apply visibility for the currently active tab. Hides/shows sidebar + * children as appropriate. + * + * A node can be associated with multiple tabs. It is visible if any of + * its associated tabs is the active tab. Unassociated nodes default to + * the files tab. + * @private + */ + function _applyTabVisibility() { + if (!$sidebar || !$sidebar.length) { + return; + } + + const children = $sidebar.children().toArray(); + + if (_activeTabId === SIDEBAR_TAB_FILES) { + // Files tab: show nodes that are in the files tab content OR + // unassociated (not in any tab). Hide nodes that are exclusively + // in other tabs. + const filesNodes = new Set(_tabContent[SIDEBAR_TAB_FILES] || []); + + children.forEach(function (child) { + if (_isExcludedNode(child)) { + return; // never touch these + } + if (filesNodes.has(child) || !_isNodeInAnyTab(child)) { + child.classList.remove("sidebar-tab-hidden"); + } else { + child.classList.add("sidebar-tab-hidden"); + } + }); + } else { + // Non-files tab: show nodes associated with this tab, hide + // everything else (except excluded nodes). + const activeNodes = new Set(_tabContent[_activeTabId] || []); + + children.forEach(function (child) { + if (_isExcludedNode(child)) { + return; + } + if (activeNodes.has(child)) { + child.classList.remove("sidebar-tab-hidden"); + } else { + child.classList.add("sidebar-tab-hidden"); + } + }); + } + } + + // --- Public API ---------------------------------------------------------- + + /** + * Register a new sidebar tab. + * + * @param {string} id Unique tab identifier + * @param {string} label Display text shown in the tab bar + * @param {string} iconClass FontAwesome (or other) icon class string + * @param {Object} [options] + * @param {number} [options.priority=100] Lower values appear further left + */ + function addTab(id, label, iconClass, options) { + options = options || {}; + + // Prevent duplicate registrations + for (let i = 0; i < _tabs.length; i++) { + if (_tabs[i].id === id) { + return; + } + } + + const tab = { + id: id, + label: label, + iconClass: iconClass, + priority: options.priority !== undefined ? options.priority : 100, + $tabItem: null + }; + _tabs.push(tab); + _tabContent[id] = _tabContent[id] || []; + + _rebuildTabBar(); + exports.trigger(EVENT_TAB_ADDED, id); + } + + /** + * Associate a DOM node (or jQuery element) with a tab. If the node is not + * already a child of `#sidebar`, it is appended. If the tab is not the + * currently active tab, the node is immediately hidden. + * + * @param {string} tabId The tab to associate with + * @param {jQuery|Element} $content DOM node or jQuery wrapper + */ + function addToTab(tabId, $content) { + const node = $content instanceof $ ? $content[0] : $content; + if (!node) { + return; + } + + // Ensure content array exists + if (!_tabContent[tabId]) { + _tabContent[tabId] = []; + } + + // Avoid duplicate association + if (_tabContent[tabId].indexOf(node) !== -1) { + return; + } + + _tabContent[tabId].push(node); + + // If not already in sidebar, append it + if (!$sidebar[0].contains(node)) { + $sidebar.append(node); + _appendedNodes.add(node); + } + + // Show/hide based on whether the node belongs to the active tab. + // A node may be in multiple tabs, so only hide it if none of its + // associated tabs is the currently active one. + if (tabId === _activeTabId || _isNodeInTab(node, _activeTabId)) { + node.classList.remove("sidebar-tab-hidden"); + } else { + node.classList.add("sidebar-tab-hidden"); + } + } + + /** + * Remove a DOM node's association with a tab. If the node was appended by + * `addToTab` (was not originally in the sidebar) and is no longer + * associated with any tab, it is also removed from the DOM. + * + * @param {string} tabId The tab to disassociate from + * @param {jQuery|Element} $content DOM node or jQuery wrapper + */ + function removeFromTab(tabId, $content) { + const node = $content instanceof $ ? $content[0] : $content; + if (!node || !_tabContent[tabId]) { + return; + } + + const idx = _tabContent[tabId].indexOf(node); + if (idx === -1) { + return; + } + + _tabContent[tabId].splice(idx, 1); + + if (_isNodeInAnyTab(node)) { + // Node is still in other tab(s) — re-evaluate its visibility + _applyTabVisibility(); + } else if (_appendedNodes.has(node)) { + // Node was appended by addToTab and is no longer in any tab — + // remove it from the DOM + $(node).remove(); + _appendedNodes.delete(node); + } else { + // Originally in sidebar and no longer in any tab — make it + // visible again so it reverts to default (files tab) behavior + node.classList.remove("sidebar-tab-hidden"); + } + } + + /** + * Remove a tab entirely. Only succeeds if all content has been removed via + * `removeFromTab` first. Returns false if content still exists. + * + * @param {string} id The tab id to remove + * @return {boolean} true if removed, false if content still associated + */ + function removeTab(id) { + if (id === SIDEBAR_TAB_FILES) { + return false; // cannot remove the built-in files tab + } + + if (_tabContent[id] && _tabContent[id].length > 0) { + return false; + } + + let removed = false; + for (let i = _tabs.length - 1; i >= 0; i--) { + if (_tabs[i].id === id) { + _tabs.splice(i, 1); + removed = true; + break; + } + } + + if (removed) { + delete _tabContent[id]; + + // If the removed tab was active, switch back to files + if (_activeTabId === id) { + _activeTabId = SIDEBAR_TAB_FILES; + } + + _rebuildTabBar(); + _applyTabVisibility(); + exports.trigger(EVENT_TAB_REMOVED, id); + } + + return removed; + } + + /** + * Switch the active sidebar tab. Shows nodes associated with the target + * tab, hides all others. + * + * @param {string} id The tab id to activate + */ + function setActiveTab(id) { + // Verify the tab exists + let found = false; + for (let i = 0; i < _tabs.length; i++) { + if (_tabs[i].id === id) { + found = true; + break; + } + } + if (!found) { + return; + } + + const previousTabId = _activeTabId; + _activeTabId = id; + + // Update active class on tab items + $navTabBar.find(".sidebar-tab").removeClass("active"); + $navTabBar.find('.sidebar-tab[data-tab-id="' + id + '"]').addClass("active"); + + _applyTabVisibility(); + + if (previousTabId !== id) { + exports.trigger(EVENT_TAB_CHANGED, id, previousTabId); + } + } + + /** + * Get the currently active tab id. + * @return {string} + */ + function getActiveTab() { + return _activeTabId; + } + + /** + * Get an array of all registered tab descriptors. + * @return {Array.<{id: string, label: string, iconClass: string, priority: number}>} + */ + function getAllTabs() { + return _tabs.map(function (tab) { + return { + id: tab.id, + label: tab.label, + iconClass: tab.iconClass, + priority: tab.priority + }; + }); + } + + // --- Initialization ------------------------------------------------------ + + AppInit.htmlReady(function () { + $sidebar = $("#sidebar"); + + // Create the tab bar and insert after #mainNavBar + $navTabBar = $(''); + $sidebar.find("#mainNavBar").after($navTabBar); + + // Register the built-in Files tab + addTab(SIDEBAR_TAB_FILES, "Files", "fa-solid fa-folder", { priority: 0 }); + + // Set up click handler for tab switching + $navTabBar.on("click", ".sidebar-tab", function () { + const tabId = $(this).attr("data-tab-id"); + if (tabId) { + setActiveTab(tabId); + } + }); + }); + + // --- Make this module an EventDispatcher ---------------------------------- + + EventDispatcher.makeEventDispatcher(exports); + + // --- Exports ------------------------------------------------------------- + + exports.SIDEBAR_TAB_FILES = SIDEBAR_TAB_FILES; + exports.EVENT_TAB_ADDED = EVENT_TAB_ADDED; + exports.EVENT_TAB_REMOVED = EVENT_TAB_REMOVED; + exports.EVENT_TAB_CHANGED = EVENT_TAB_CHANGED; + + exports.addTab = addTab; + exports.addToTab = addToTab; + exports.removeFromTab = removeFromTab; + exports.removeTab = removeTab; + exports.setActiveTab = setActiveTab; + exports.getActiveTab = getActiveTab; + exports.getAllTabs = getAllTabs; +}); diff --git a/src/view/ViewStateManager.js b/src/view/ViewStateManager.js index d4a371831..687baabb6 100644 --- a/src/view/ViewStateManager.js +++ b/src/view/ViewStateManager.js @@ -31,6 +31,7 @@ * * Views or View Factories are responsible for restoring the view state when the view of that file is created * by recalling the cached state. Views determine what data is store in the view state and how to restore it. + * @module view/ViewStateManager */ define(function (require, exports, module) { diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index da0bf1417..c56bbb4d9 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -31,6 +31,7 @@ * `workspaceUpdateLayout` When workspace size changes for any reason (including panel show/hide panel resize, or the window resize). * The 2nd arg is the available workspace height. * The 3rd arg is a refreshHint flag for internal use (passed in to recomputeLayout) + * @module view/WorkspaceManager */ define(function (require, exports, module) { diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index f2dd6c590..7c061cf05 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -94,6 +94,7 @@ define(function (require, exports, module) { require("spec/PreferencesManager-integ-test"); require("spec/MainViewFactory-integ-test"); require("spec/MainViewManager-integ-test"); + require("spec/SidebarTabs-integ-test"); require("spec/Keyboard-nav-integ-test"); require("spec/Menu-integ-test"); require("spec/ProjectManager-integ-test"); diff --git a/test/spec/SidebarTabs-integ-test.js b/test/spec/SidebarTabs-integ-test.js new file mode 100644 index 000000000..792898598 --- /dev/null +++ b/test/spec/SidebarTabs-integ-test.js @@ -0,0 +1,458 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global describe, it, expect, beforeAll, afterAll, beforeEach, awaitsFor */ + +define(function (require, exports, module) { + + const SpecRunnerUtils = require("spec/SpecRunnerUtils"); + + let SidebarTabs, + testWindow, + brackets, + _$; + + // Test tab constants + const TEST_TAB_ID = "sidebar-tab-test"; + const TEST_TAB_ID_2 = "sidebar-tab-test-2"; + + describe("mainview:SidebarTabs", function () { + + beforeAll(async function () { + testWindow = await SpecRunnerUtils.createTestWindowAndRun(); + brackets = testWindow.brackets; + SidebarTabs = brackets.test.SidebarTabs; + _$ = testWindow.$; + }, 30000); + + afterAll(async function () { + // Reset to files tab + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + + // Remove any test tabs that may still exist + const allTabs = SidebarTabs.getAllTabs(); + allTabs.forEach(function (tab) { + if (tab.id !== SidebarTabs.SIDEBAR_TAB_FILES) { + // Clear content first so removeTab succeeds + // (skip tabs that have content - they belong to other extensions) + } + }); + + SidebarTabs = null; + testWindow = null; + brackets = null; + _$ = null; + await SpecRunnerUtils.closeTestWindow(); + }, 30000); + + beforeEach(function () { + // Reset to files tab before each test + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + + // Remove any leftover test tabs + SidebarTabs.removeTab(TEST_TAB_ID); + SidebarTabs.removeTab(TEST_TAB_ID_2); + }); + + describe("initial state", function () { + + it("should have #navTabBar as child of #sidebar after #mainNavBar", function () { + const $navTabBar = _$("#navTabBar"); + expect($navTabBar.length).toBe(1); + expect($navTabBar.parent().attr("id")).toBe("sidebar"); + // Should come after #mainNavBar + const $mainNavBar = _$("#mainNavBar"); + expect($mainNavBar.length).toBe(1); + expect($navTabBar.prev().attr("id")).toBe("mainNavBar"); + }); + + it("should have files tab as active tab", function () { + expect(SidebarTabs.getActiveTab()).toBe(SidebarTabs.SIDEBAR_TAB_FILES); + }); + + it("should include at least the files tab in getAllTabs", function () { + const allTabs = SidebarTabs.getAllTabs(); + expect(allTabs.length).toBeGreaterThanOrEqual(1); + const filesTab = allTabs.find(function (t) { + return t.id === SidebarTabs.SIDEBAR_TAB_FILES; + }); + expect(filesTab).toBeDefined(); + }); + + it("should have tab bar visible with has-tabs when multiple tabs exist", function () { + const $navTabBar = _$("#navTabBar"); + const allTabs = SidebarTabs.getAllTabs(); + if (allTabs.length >= 2) { + expect($navTabBar.hasClass("has-tabs")).toBe(true); + } + }); + }); + + describe("addTab and removeTab", function () { + + it("should increase tab count when adding a new tab", function () { + const initialCount = SidebarTabs.getAllTabs().length; + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + expect(SidebarTabs.getAllTabs().length).toBe(initialCount + 1); + }); + + it("should not increase count on duplicate addTab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const countAfterFirst = SidebarTabs.getAllTabs().length; + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab Dup", "fa-solid fa-flask"); + expect(SidebarTabs.getAllTabs().length).toBe(countAfterFirst); + }); + + it("should return false when removing the built-in files tab", function () { + const result = SidebarTabs.removeTab(SidebarTabs.SIDEBAR_TAB_FILES); + expect(result).toBe(false); + }); + + it("should return false when removing a tab that has content", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const $content = _$('
Content
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + const result = SidebarTabs.removeTab(TEST_TAB_ID); + expect(result).toBe(false); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + }); + + it("should return true when removing an empty tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const result = SidebarTabs.removeTab(TEST_TAB_ID); + expect(result).toBe(true); + }); + + it("should no longer include tab in getAllTabs after removeTab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.removeTab(TEST_TAB_ID); + const allTabs = SidebarTabs.getAllTabs(); + const found = allTabs.find(function (t) { return t.id === TEST_TAB_ID; }); + expect(found).toBeUndefined(); + }); + + it("should fire EVENT_TAB_ADDED on addTab", function () { + let firedId = null; + function handler(event, id) { + firedId = id; + } + SidebarTabs.on(SidebarTabs.EVENT_TAB_ADDED, handler); + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.off(SidebarTabs.EVENT_TAB_ADDED, handler); + + expect(firedId).toBe(TEST_TAB_ID); + }); + + it("should fire EVENT_TAB_REMOVED on removeTab", function () { + let firedId = null; + function handler(event, id) { + firedId = id; + } + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.on(SidebarTabs.EVENT_TAB_REMOVED, handler); + SidebarTabs.removeTab(TEST_TAB_ID); + SidebarTabs.off(SidebarTabs.EVENT_TAB_REMOVED, handler); + + expect(firedId).toBe(TEST_TAB_ID); + }); + }); + + describe("addToTab and removeFromTab", function () { + + it("should append content node to #sidebar", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const $content = _$('
Hello
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + expect(_$("#sidebar").find("#test-sidebar-content").length).toBe(1); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + }); + + it("should hide content added to a non-active tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + // Active tab is FILES, so content added to TEST_TAB_ID should be hidden + const $content = _$('
Hidden
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + expect($content.hasClass("sidebar-tab-hidden")).toBe(true); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + }); + + it("should show content added to the active tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.setActiveTab(TEST_TAB_ID); + + const $content = _$('
Visible
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + expect($content.hasClass("sidebar-tab-hidden")).toBe(false); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + }); + + it("should remove node from DOM when removeFromTab leaves it in no tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const $content = _$('
Remove me
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + expect(_$("#sidebar").find("#test-remove-content").length).toBe(1); + + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + expect(_$("#sidebar").find("#test-remove-content").length).toBe(0); + }); + + it("should keep node in DOM when removeFromTab still leaves it in another tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.addTab(TEST_TAB_ID_2, "Test Tab 2", "fa-solid fa-flask"); + const $content = _$('
Multi
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + SidebarTabs.addToTab(TEST_TAB_ID_2, $content); + + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + expect(_$("#sidebar").find("#test-multi-content").length).toBe(1); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID_2, $content); + }); + }); + + describe("setActiveTab", function () { + + it("should set getActiveTab to the new tab id", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.setActiveTab(TEST_TAB_ID); + + expect(SidebarTabs.getActiveTab()).toBe(TEST_TAB_ID); + }); + + it("should show existing sidebar children when switching to files tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.setActiveTab(TEST_TAB_ID); + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + + // Working set and project files should be visible + const $openFilesContainer = _$("#open-files-container"); + const $projectFilesContainer = _$("#project-files-container"); + if ($openFilesContainer.length) { + expect($openFilesContainer.hasClass("sidebar-tab-hidden")).toBe(false); + } + if ($projectFilesContainer.length) { + expect($projectFilesContainer.hasClass("sidebar-tab-hidden")).toBe(false); + } + }); + + it("should hide existing sidebar children when switching to custom tab", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const $content = _$('
Custom
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + SidebarTabs.setActiveTab(TEST_TAB_ID); + + // Existing sidebar children (not associated with any tab) should be hidden + const $openFilesContainer = _$("#open-files-container"); + if ($openFilesContainer.length) { + expect($openFilesContainer.hasClass("sidebar-tab-hidden")).toBe(true); + } + + // Custom tab content should be visible + expect($content.hasClass("sidebar-tab-hidden")).toBe(false); + + // Clean up + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + }); + + it("should restore sidebar children when switching back to files", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const $content = _$('
SwitchBack
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + + // Switch to custom tab then back + SidebarTabs.setActiveTab(TEST_TAB_ID); + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + + // Sidebar children should be visible again + const $openFilesContainer = _$("#open-files-container"); + if ($openFilesContainer.length) { + expect($openFilesContainer.hasClass("sidebar-tab-hidden")).toBe(false); + } + // Custom content should be hidden + expect($content.hasClass("sidebar-tab-hidden")).toBe(true); + + // Clean up + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + }); + + it("should fire EVENT_TAB_CHANGED with newTabId and previousTabId", function () { + let eventArgs = null; + function handler(event, newTabId, previousTabId) { + eventArgs = { newTabId: newTabId, previousTabId: previousTabId }; + } + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.on(SidebarTabs.EVENT_TAB_CHANGED, handler); + SidebarTabs.setActiveTab(TEST_TAB_ID); + SidebarTabs.off(SidebarTabs.EVENT_TAB_CHANGED, handler); + + expect(eventArgs).not.toBeNull(); + expect(eventArgs.newTabId).toBe(TEST_TAB_ID); + expect(eventArgs.previousTabId).toBe(SidebarTabs.SIDEBAR_TAB_FILES); + }); + + it("should do nothing when setActiveTab is called with unknown id", function () { + const currentTab = SidebarTabs.getActiveTab(); + SidebarTabs.setActiveTab("nonexistent-tab-id"); + expect(SidebarTabs.getActiveTab()).toBe(currentTab); + }); + + it("should never hide #mainNavBar during any switch", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + + SidebarTabs.setActiveTab(TEST_TAB_ID); + const $mainNavBar = _$("#mainNavBar"); + expect($mainNavBar.hasClass("sidebar-tab-hidden")).toBe(false); + + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + expect($mainNavBar.hasClass("sidebar-tab-hidden")).toBe(false); + }); + + it("should never hide #navTabBar during any switch", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + + SidebarTabs.setActiveTab(TEST_TAB_ID); + const $navTabBar = _$("#navTabBar"); + expect($navTabBar.hasClass("sidebar-tab-hidden")).toBe(false); + + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + expect($navTabBar.hasClass("sidebar-tab-hidden")).toBe(false); + }); + + it("should never hide resizer elements during any switch", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + + SidebarTabs.setActiveTab(TEST_TAB_ID); + const $horzResizers = _$("#sidebar .horz-resizer"); + const $vertResizers = _$("#sidebar .vert-resizer"); + $horzResizers.each(function (i, el) { + expect(_$(el).hasClass("sidebar-tab-hidden")).toBe(false); + }); + $vertResizers.each(function (i, el) { + expect(_$(el).hasClass("sidebar-tab-hidden")).toBe(false); + }); + + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + $horzResizers.each(function (i, el) { + expect(_$(el).hasClass("sidebar-tab-hidden")).toBe(false); + }); + $vertResizers.each(function (i, el) { + expect(_$(el).hasClass("sidebar-tab-hidden")).toBe(false); + }); + }); + }); + + describe("multi-tab content", function () { + + it("should show node associated with two tabs when either is active", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.addTab(TEST_TAB_ID_2, "Test Tab 2", "fa-solid fa-flask"); + const $content = _$('
Multi
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + SidebarTabs.addToTab(TEST_TAB_ID_2, $content); + + SidebarTabs.setActiveTab(TEST_TAB_ID); + expect($content.hasClass("sidebar-tab-hidden")).toBe(false); + + SidebarTabs.setActiveTab(TEST_TAB_ID_2); + expect($content.hasClass("sidebar-tab-hidden")).toBe(false); + + // Clean up + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + SidebarTabs.removeFromTab(TEST_TAB_ID_2, $content); + }); + + it("should keep node visible after removing from one tab if other tab is active", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.addTab(TEST_TAB_ID_2, "Test Tab 2", "fa-solid fa-flask"); + const $content = _$('
Multi
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + SidebarTabs.addToTab(TEST_TAB_ID_2, $content); + + SidebarTabs.setActiveTab(TEST_TAB_ID_2); + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + // Still in TEST_TAB_ID_2 which is active, so visible + expect($content.hasClass("sidebar-tab-hidden")).toBe(false); + + // Clean up + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); + SidebarTabs.removeFromTab(TEST_TAB_ID_2, $content); + }); + + it("should remove appended node from DOM when removed from all tabs", function () { + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + SidebarTabs.addTab(TEST_TAB_ID_2, "Test Tab 2", "fa-solid fa-flask"); + const $content = _$('
Multi
'); + SidebarTabs.addToTab(TEST_TAB_ID, $content); + SidebarTabs.addToTab(TEST_TAB_ID_2, $content); + + SidebarTabs.removeFromTab(TEST_TAB_ID, $content); + SidebarTabs.removeFromTab(TEST_TAB_ID_2, $content); + + // Node was appended by addToTab, so it should be removed from DOM + expect(_$("#sidebar").find("#test-multi-remove-all").length).toBe(0); + }); + }); + + describe("tab bar visibility", function () { + + it("should have has-tabs class when >= 2 tabs exist", function () { + const $navTabBar = _$("#navTabBar"); + // There should already be at least the files tab; add one more + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + expect(SidebarTabs.getAllTabs().length).toBeGreaterThanOrEqual(2); + expect($navTabBar.hasClass("has-tabs")).toBe(true); + }); + + it("should lose has-tabs class when only 1 tab remains", function () { + const $navTabBar = _$("#navTabBar"); + // Add our own test tab so we have a known removable tab + SidebarTabs.addTab(TEST_TAB_ID, "Test Tab", "fa-solid fa-flask"); + const countWithTestTab = SidebarTabs.getAllTabs().length; + expect($navTabBar.hasClass("has-tabs")).toBe(true); + + // Remove only our test tab + SidebarTabs.removeTab(TEST_TAB_ID); + const countAfterRemove = SidebarTabs.getAllTabs().length; + expect(countAfterRemove).toBe(countWithTestTab - 1); + + // has-tabs should accurately reflect whether >= 2 tabs remain + expect($navTabBar.hasClass("has-tabs")).toBe(countAfterRemove >= 2); + }); + }); + }); +});