UNPKG

flex-sdk

Version:

A Node.js module to download and "install" the latest open source Apache/Adobe Flex SDK.

1,621 lines (1,510 loc) 86.1 kB
/* AIRSourceViewer.js - Revision: 1.5 */ /* ADOBE SYSTEMS INCORPORATED Copyright 2007-2008 Adobe Systems Incorporated. All Rights Reserved. NOTICE: Adobe permits you to modify and distribute this file only in accordance with the terms of Adobe AIR SDK license agreement. You may have received this file from a source other than Adobe. Nonetheless, you may modify or distribute this file only in accordance with such agreement. */ // @the Source Viewer uses the <air> namespace: if (typeof air == 'undefined') {air = {}}; air.SourceViewer = function() { throw ( new Error ( "\n\n" + "You cannot instantiate the 'air.SourceViewer' class. " + "Instead, use 'air.SourceViewer.getDefault()' to retrieve the " + "class' unique instance." + "\n\n" )); } air.SourceViewer.getDefault = function() { // The Source Viewer only works in the AIR application sandbox: if (typeof window.runtime == "undefined") { throw ( new Error ( "\n\n" + "The Source Browser module can only work inside the <application " + "sandbox>. Please include the SourceViewer.js file in the " + "application's main *.html file." + "\n\n" )); } // @return the existing instance if there is one; creating it otherwise: var context = arguments.callee; if (context.instance) { return context.instance }; /** * CLASS * air.SourceViewer * DESCRIPTION * A self-contained module to embed in HTML-based AIR applications. * It will display a customizable selection of source files in * an expandable tree structure. The user will be able * to select a file to view its source code. * SAMPLE USAGE * var viewer1 = air.SourceViewer.getDefault(); * viewer1.viewSource(); * * var viewer2 = air.SourceViewer.getDefault(); * var oConfig = { exclude: ['/icons', '/images'] }; * viewer2.setup(oConfig); * viewer2.viewSource(); * * @class * @public * @singleton */ function _SourceViewer() { /** * CONSTANTS * Private members that aren't ment to be altered in any way. * @private * @constant */ // @AIR runtime: var HTMLLoader = window.runtime.flash.html.HTMLLoader; var NativeWindowInitOptions = window.runtime.flash.display. NativeWindowInitOptions; var Rectangle = window.runtime.flash.geom.Rectangle; var NativeWindowType = window.runtime.flash.display. NativeWindowType; var File = window.runtime.flash.filesystem.File; var FileStream = window.runtime.flash.filesystem. FileStream; var Screen = window.runtime.flash.display.Screen; // @application var TEXT_EXTENSIONS = ['txt', 'xml', 'mxml', 'htm', 'html', 'js', 'as', 'css', 'properties', 'config', 'ini', 'bat', 'readme']; var IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif']; var TEXT_TYPE = "text"; var IMAGE_TYPE = "image"; // @events var WINDOW_CREATED_EVENT = 'windowCreatedEvent'; var FILES_LIST_READY_EVENT = 'filesListReadyEvent'; var FILE_LISTED_EVENT = 'fileListedEvent'; var FOLDER_CHECKED_EVENT = 'folderCheckedEvent'; var FOLDER_FIRST_CLICKED_EVENT = 'folderFirstClickedEvent'; var FOLDER_STATE_CHANGED_EVENT = 'folderStateChanged'; var ITEM_MOUSE_OVER_EVENT = 'itemMouseOverEvent'; var ITEM_MOUSE_OUT_EVENT = 'itemMouseOutEvent'; var ITEM_MOUSE_CLICK_EVENT = 'itemMouseClickEvent'; var FILE_ITEM_CLICKED = 'fileItemClicked'; var FILE_CONTENT_READY_EVENT = 'fileContentReady'; // @strings var CANNOT_READ_TEXT_MESSAGE = 'Cannot retrieve text content from ' + 'this filetype.'; var IO_ERROR_MESSAGE = 'An IO Error occured while trying to ' + 'read this text.'; var ADOBE_TOKEN = 'ADOBE'; var COPYRIGHT_TOKEN = '&copy;'; var AIR_TOKEN = 'AIR'; var TRADEMARK_TOKEN = '&trade;'; var APP_LEGAL_NAME = 'HTML View Source Framework'; var APP_VERSION_MAJOR_MINOR = '1.5'; var TREE_DESCRIPTION_MESSAGE = 'Select a source file in the tree to '+ 'see its content in the right pane:'; var COPYRIGHT_MESSAGE = ''; /** * Flag to raise when the Source Viewer's window is open. * @field * @private */ var isMainWindowOpen; /** * The DOMProvider instance shared by all application components. It * is instantiated by WindowsManager.makeMainWindow(). * @field * @private */ var domProvider; /** * The CSSProvider instance shared by all application components. It * is instantiated by WindowsManager.makeMainWindow(). * @field * @private */ var cssProvider; /** * The UIBuilder instance shared by all application components. It * is instantiated by WindowsManager.makeMainWindow(). * @field * @private */ var uiBuilder; /** * The EventManager instance shared by all application components. * @field * @private */ var eventManager = new EventManager(); /** * The WindowsManager instance shared by all application components. * @field * @private */ var windowManager = new WindowsManager(); /** * The LayoutProvider instance shared by all application components. * @field * @private */ var layoutProvider = new LayoutProvider(); /** * The FileSystemWalker instance shared by all application components. * @field * @private */ var fileSystemWalker = new FileSystemWalker(); /** * The RequestedFilesRegistry instance shared by all application * components. * @field * @private */ var requestedFilesRegistry = new RequestedFilesRegistry(); /** * A hash that gets populated with references to DOM Elements * representing application's UI main sections. Namely, the hash's * structure is: * - header { HTML DOM element } * A reference to the DOM Element representing the header * (top most section) of the application's UI * - sidebar { HTML DOM element } * A reference to the DOM Element representing the sidebar * (left hand section) of the application's UI * - tree { HTML DOM element } * The <ul> HTML DOM Element that represents the root * of the tree UI component (the one that displays * clickable entries for applications files and folders) * - contentArea { HTML DOM element } * A reference to the DOM Element representing the content * area (right hand section) of the application's UI. * - ruler { HTML DOM element } * A reference to the DOM Element representing the * scrollable ruller that displays line numbers * - sourceArea { HTML DOM element } * The <div> HTML DOM Element that actually contains the * text of the file being displayed. * - footer { HTML DOM element } * A reference to the DOM Element representing the footer * (bottom most section) of the application's UI. * @field * @private */ var ui; /** * The main method to be called by the client programmer. Opens the * Source Viewer UI and lists the first level of source files. * @method * @public */ this.viewSource = function() { if(isMainWindowOpen){ return }; var windowCreatedHandler = function(event) { initUI(event.body.window.document); eventManager.removeListener( WINDOW_CREATED_EVENT, windowCreatedHandler ); }; eventManager.addListener( WINDOW_CREATED_EVENT, windowCreatedHandler ); windowManager.makeMainWindow (function(oWindow) {}); } /** * Holds the configuration object provided bythe client programmer. * @field * @private */ var oConfig = {}; /** * Holds the user preference regarding Source Viewer's modal state. * A value of "true" will make our window modal, whereas a value of * "false" will leave it a regular window. Default is "true". * @field * @private */ var isToBeModal = false; /** * Holds the user preference regarding a default file that is to be * displayed by the Source Viewer when it starts. Will contain an * application's root relative file path. * @field * @private */ var defaultFilePath; /** * There is a list of file extensions that are 'recognized', and hence, * displayed. Should the user also want some other file extensions, he * must declare them here. * @field * @private */ var typesToAdd = []; /** * There is a list of file extensions that are 'recognized', and hence, * displayed. Should the user want some of these file extensions * removed, he must declare them here. * Note: * The "typesToRemove" exclusion list superseedes the "typesToAdd" * addition list. * @field * @private */ var typesToRemove = []; /** * Holds the user preference regarding the color scheme to be used for * the Source Viewer's UI. There are currently two color schemes * available: * - professionalBlue * - nightScape * @field * @private */ var userColorScheme; /** * Holds the user preference regarding the initial [x, y] position of * the Source Viewer's window on the screen. This is expected to be an * array of the form [x, y]. * @field * @private */ var initPosition; /** * Also part of the public API. Transmits the settings to the internal * core. * @method * @public * @param cfg { Object } * Object literal containing settings for the Source Viewer. * Currently only supports: * - exclude { Array } * An app root relative array of paths. Files or folders * starting with one of these paths will not show in the tree. * - modal { Boolean } * Whether the Source viewer's window should be modal to the * host application's window. */ this.setup = function (cfg) { oConfig = cfg; // @collect user's modal preference: if ((typeof oConfig['modal'] != "undefined") && oConfig['modal'] !== null) { isToBeModal = oConfig['modal']? true: false; } // @collect user's default file preference: if ((typeof oConfig['defaultFile'] != "undefined") && oConfig['defaultFile'] !== null) { defaultFilePath = oConfig['defaultFile']; } // @collect user's file extensions list preference: if((typeof oConfig['typesToAdd'] != "undefined") && oConfig['typesToAdd'] !== null) { typesToAdd = oConfig['typesToAdd']; } if((typeof oConfig['typesToRemove'] != "undefined") && oConfig['typesToRemove'] !== null) { typesToRemove = oConfig['typesToRemove']; } var existingList = {}; for(var i=0; i<TEXT_EXTENSIONS.length; i++) { var ext = TEXT_EXTENSIONS[i]; existingList['text.'+ext] = 'text'; } for(var j=0; j<IMAGE_EXTENSIONS.length; j++) { var ext = IMAGE_EXTENSIONS[j]; existingList['image.'+ext] = 'image'; } for(var k=0; k<typesToAdd.length; k++) { var newType = typesToAdd[k]; var match1 = newType.match(/(?:^text\.(\w+)$)/); if(match1) { existingList['text.' + match1[1]] = 'text'; } var match2 = newType.match(/(?:^image\.(\w+)$)/); if(match2) { existingList['image.' + match2[1]] = 'text'; } } for(var l=0; l<typesToRemove.length; l++) { var type = typesToRemove[l]; var match3 = type.match(/(?:^text\.(\w+)$)/); if(match3) { delete existingList['text.' + match3[1]] }; var match4 = type.match(/(?:^image\.(\w+)$)/); if(match4) { delete existingList['image.' + match4[1]] }; } TEXT_EXTENSIONS = []; IMAGE_EXTENSIONS = []; for (var key in existingList) { var value = existingList[key]; var extension = key.substr(key.indexOf('.')+1); if (value == 'text') { TEXT_EXTENSIONS.push(extension); } else if(value == 'image') { IMAGE_EXTENSIONS.push(extension); } } // @collect user's color scheme preference: if((typeof oConfig['colorScheme'] != "undefined") && oConfig['colorScheme'] !== null) { userColorScheme = oConfig['colorScheme']; } // @collect user's initial window position preference: if( typeof oConfig['initialPosition'] != "undefined" && oConfig['initialPosition'] !== null && oConfig['initialPosition'].length == 2 && !isNaN(parseInt(oConfig['initialPosition'][0])) && !isNaN(parseInt(oConfig['initialPosition'][1])) ) { initPosition = oConfig['initialPosition']; } } /** * Checks whether the given 'file' is to be hidden according to config * regulations. * @method * @private * @param fileUrl { String } * The file url to check. */ function isFileToBeHidden (file) { var relUrl = Utils.getRelativePath(file); var toHide = oConfig.exclude; if (toHide) { for(var i=0; i<toHide.length; i++) { var testVal = toHide[i]; testVal = testVal.replace(/\\/g, '/'); testVal = testVal.replace(/\/$/g, ''); if (testVal[0] != '/') { testVal = '/' + testVal }; if(testVal == relUrl) { return true }; if(relUrl.indexOf(testVal) == 0) { if (relUrl[testVal.length] == '/') { return true; } } } } return false; } /** * Initializes the application UI. * @method * @private * @param oDocument { HTML Document Object } * The document object to be used by classes responsible with UI * creation. */ function initUI (oDocument) { cssProvider = new CSSProvider(oDocument); cssProvider.changeColorScheme(userColorScheme); domProvider = new DOMProvider(oDocument); uiBuilder = new UIBuilder(domProvider); ui = uiBuilder.createMainLayout(); populateTree(ui.tree); // @attempt to load default file's content, if there is one: if (typeof defaultFilePath != 'undefined') { var segments = String(defaultFilePath).split('/'); var urlSegments = []; for(var i=0; i<segments.length; i++) { var sgm = segments[i]; if (!/\w/.test(sgm)) { continue }; urlSegments.push(sgm); }; var relURL = urlSegments.join('/'); var appDir = File.applicationDirectory; var file = appDir.resolvePath(relURL); // @the path must finally point to a file: if(file.exists) { if(!file.isDirectory) { if (!isFileToBeHidden(file)) { fileSystemWalker.queryFileContent(file); } } } } } /** * Fills the tree with the first level of files. * @method * @private * @param treeRoot { HTML Element } * An HTML Element representing the root of the tree. */ function populateTree (treeRoot) { var fileListReadyHandler = function(event){ uiBuilder.displayFilesList(treeRoot, event.body.filesList); eventManager.removeListener( FILES_LIST_READY_EVENT, fileListReadyHandler ); } eventManager.addListener( FILES_LIST_READY_EVENT, fileListReadyHandler ); fileSystemWalker.makeInitialQuery(); } /** * Clears all data that would naturally persist over sessions. * @method * @private */ function purge() { uiBuilder.unsetClickableItems(); eventManager.removeListenersFor (FOLDER_STATE_CHANGED_EVENT); requestedFilesRegistry.reset(); context.instance = null; delete context.instance; } /** * CLASS * WindowsManager * DESCRIPTION * Handles window creation and manipulation for this application. * SAMPLE USAGE * N/A (internal use only) * @class * @private */ function WindowsManager () { /** * Returns the default display options for a newly created window. * @method * @private * @return { NativeWindowInitOptions } * An object specifying display options for a new window. */ function getDefWindowOptions () { var options = new NativeWindowInitOptions(); options.type = NativeWindowType.UTILITY; return options; } /** * Returns the screen the host application is currently in (we call * "host application" the application that has launched the Source * Viewer). * If the host application spans more than a single screen, the one * displaying a larger area of the host application UI will be * considered. * If the host application almost equally spans two screns, or if it * spans more than two screens, the primary screen will be returned. * Needless to say, the single screen is returned for single screen * hardware configurations. * @method * @private * @return { Screen } The screen object. */ function getCurrentScreen () { // Get all screens on which our host application is displayed: var hostBounds = window.nativeWindow.bounds; var screens = Screen.getScreensForRectangle(hostBounds); // Get the most important screen showing a portion of the app: var largestArea = 0; var mostImportantScreen = null; var screenOffset = -1; for (var i=0; i<screens.length; i++) { var screen = screens[i]; var intersection = screen.bounds.intersection(hostBounds); var area = intersection.width * intersection.height; if (area > largestArea) { largestArea = area; mostImportantScreen = screen; screenOffset = i; } } return mostImportantScreen; } /** * Will translate the absolute, agnostic coordinates of the given * rectangle into a relative, screen-dependant set of coordinates. * * This is required, as screen coordinates may drammatically vary * based on that screen geographic position -- to the left or to the * right of the primary screen, for instance. * @method * @private * @return { Rectangle } The translated coordinates. */ function positionRectangleOnScreen (screen, rectangle) { // Adjust the 'x' coordinate: if ((rectangle.x + screen.visibleBounds.x) < screen.visibleBounds.x) { rectangle.x = screen.visibleBounds.x; } else { var leftPoint = screen.visibleBounds.x + rectangle.x; var rightPoint = leftPoint + rectangle.width; if (rightPoint <= screen.visibleBounds.width + screen.visibleBounds.x) { rectangle.x = leftPoint; } else { var adjustedLeftPoint = screen.visibleBounds.width - rectangle.width + screen.visibleBounds.x; rectangle.x = Math.max(adjustedLeftPoint, screen.visibleBounds.x); } } // Adjust the 'y' coordinate: if (rectangle.y < screen.visibleBounds.y) { rectangle.y = screen.visibleBounds.y; } else if(rectangle.y + rectangle.height > screen.visibleBounds.height) { rectangle.y = screen.visibleBounds.height - rectangle.height; } return rectangle; } /** * Centers the given rectangle on the given screen. * @method * @private * @see positionRectangleOnScreen * @return { Rectangle} The centered rectangle */ function centerRectangleOnScreen (screen, rectangle) { rectangle.x = Math.floor(screen.visibleBounds.width - rectangle.width) / 2; rectangle.y = Math.floor(screen.visibleBounds.height - rectangle.height) / 2; return positionRectangleOnScreen(screen, rectangle); } /** * Returns the default display boundaries for a newly created * window. * @method * @private * @return { Rectangle } * A rectangle defining the boundaries of this new window. */ function getDefaultBoundaries () { return new Rectangle (0, 0, 800, 600); } /** * Creates the main window of the application. * @method * @public */ this.makeMainWindow = function () { var screen = getCurrentScreen(); var bounds = getDefaultBoundaries(); if (initPosition) { bounds.x = initPosition[0]; bounds.y = initPosition[1]; bounds = positionRectangleOnScreen (screen, bounds); } else { bounds = centerRectangleOnScreen(screen, bounds); } var htmlLoader = HTMLLoader.createRootWindow ( true, getDefWindowOptions(), false, bounds ); var domInitHandler = function() { isMainWindowOpen = true; if(isToBeModal) { makeWindowModal (htmlLoader.window, self); } else { // @close our window before parent window gets closed: var parWinClosingHandler = function() { self.nativeWindow.removeEventListener('closing', parWinClosingHandler); self.nativeWindow.removeEventListener('activate', parWindowActivateHandler); if(htmlLoader && htmlLoader.window && htmlLoader.window.nativeWindow) { WindowsManager.closeWindow( htmlLoader.window.nativeWindow); } } self.nativeWindow.addEventListener ('closing', parWinClosingHandler); // @activate our window when parent gets activated: var parWindowActivateHandler = function() { if(htmlLoader && htmlLoader.window && htmlLoader.window.nativeWindow) { htmlLoader.window.nativeWindow. orderInFrontOf(self.nativeWindow); } } self.nativeWindow.addEventListener ('activate', parWindowActivateHandler); } htmlLoader.window.nativeWindow.addEventListener ( 'close', function(evt) { isMainWindowOpen = false; } ); var unconditionedClosingHandler = function (event) { if (!event.isdefaultPrevented) { purge(); } htmlLoader.window.nativeWindow.removeEventListener ( 'closing', unconditionedClosingHandler ); } htmlLoader.window.nativeWindow.addEventListener ( 'closing', unconditionedClosingHandler ); var event = eventManager.createEvent( WINDOW_CREATED_EVENT, {'window': htmlLoader.window} ); eventManager.fireEvent(event); // @this is a once-in-a-lifetime run: htmlLoader.removeEventListener('htmlDOMInitialize', domInitHandler); } htmlLoader.addEventListener('htmlDOMInitialize',domInitHandler); try{ //since AIR1.5 the htmlLoader will not allow string load in app sandbox //surrounded with try/catch in case we are running in older runtime htmlLoader.placeLoadStringContentInApplicationSandbox= true; }catch(e){} htmlLoader.loadString(''); } /** * Makes a window modal to a certain parent window. * @method * @private * @param oWindow { Object Window } * The window to be made modal. * @param oParentWindow { Object Window } * The parent of the modal window. Any attempt to access the * parent while the modal window is open will fail. */ function makeWindowModal (oWindow, oParentWindow) { // @prevent parent window closing: var closingHandler = function (event) { if (isMainWindowOpen) { event.preventDefault(); return; }; oParentWindow.nativeWindow.removeEventListener( 'closing', closingHandler ); } oParentWindow.nativeWindow.addEventListener ( 'closing', closingHandler ); // @prevent parent window minimizing or maximizing: var stateChangingHandler = function (event) { if (isMainWindowOpen) { event.preventDefault(); return; }; oParentWindow.nativeWindow.removeEventListener( 'displayStateChanging', stateChangingHandler ); } oParentWindow.nativeWindow.addEventListener ( 'displayStateChanging', stateChangingHandler ); // @prevent parent window moving: var movingHandler = function(event) { if (isMainWindowOpen) { event.preventDefault(); return; }; oParentWindow.nativeWindow.removeEventListener( 'moving', movingHandler ); } oParentWindow.nativeWindow.addEventListener ( 'moving', movingHandler ); // @prevent parent window resizing: var resizingHandler = function(event) { if(isMainWindowOpen) { event.preventDefault(); return; }; oParentWindow.nativeWindow.removeEventListener( 'resizing', resizingHandler ); } oParentWindow.nativeWindow.addEventListener ( 'resizing', resizingHandler ); // @make sure parent window will stay behind the modal window: var ensureProperOrder = function() { if (oWindow && oWindow.nativeWindow && oParentWindow && oParentWindow.nativeWindow) { oWindow.nativeWindow.activate(); oParentWindow.nativeWindow .orderInBackOf(oWindow.nativeWindow); } } // @works by default on Windows, hacked on Mac: var osString = runtime.flash.system.Capabilities.os; if(osString.indexOf('Windows') != -1) { oWindow.nativeWindow.addEventListener ('deactivate', ensureProperOrder, false); } else { var EPO_INTERVAL = window.setInterval(ensureProperOrder, 0); } // @prevent user from interacting with the parent's content: var parentContentBlocker; var parentDomProvider = new DOMProvider(oParentWindow.document); var parentLayoutProvider = new LayoutProvider(); parentContentBlocker = parentDomProvider.makeDiv(); parentLayoutProvider.setupBox(parentContentBlocker); parentLayoutProvider.setupStretched(parentContentBlocker); parentContentBlocker.style.backgroundColor = 'black'; parentContentBlocker.style.opacity = '0.1'; var bodyElement = parentContentBlocker.parentNode; var localCSSProvider = new CSSProvider(oParentWindow.document); // @restore parent windows' state: var modalWindowClosingHandler = function(evt) { if (!evt.isDefaultPrevented()) { window.clearInterval(EPO_INTERVAL); if(parentContentBlocker) { var parEl = parentContentBlocker.parentNode; parEl.removeChild(parentContentBlocker); } oWindow.nativeWindow.removeEventListener('closing', modalWindowClosingHandler); } } oWindow.nativeWindow.addEventListener ('closing', modalWindowClosingHandler); } } /** * Programmatically closes the given native window, while still allowing * it to opt out, via preventing the default behavior of the 'closing' * event. * @method * @public * @static * @param oWindow { NativeWindow } * The instance of the NativeWindow class that is to be closed. * @return * True, if the given window has been closed; false otherwise. */ WindowsManager.closeWindow = function (oWindow) { var closeEvent = new runtime.flash.events.Event ( runtime.flash.events.Event.CLOSING, true, true ); oWindow.dispatchEvent(closeEvent); if (!closeEvent.isDefaultPrevented()){ oWindow.close(); return true; } else { return false; } } /** * CLASS * UIBuilder * DESCRIPTION * Private class that handles the application's layout creation. * SAMPLE USAGE * N/A (internal use only) * @class * @private * @param oDomProvider { DOMProvider } * An instance of the DOMProvider class. Required, in order to be * able to build the layout blocks. */ function UIBuilder (oDomProvider) { // @let private methods see own class' instance: var that = this; /** * Updates the title shown by the Source Viewer window. * @method * @private * @param fileUrl { String } * The title to show. */ this.updateTitle = function(file) { var relPath = Utils.getRelativePath(file); var oDocument = dProvider.getClientDocument (); oDocument.title = APP_LEGAL_NAME + " - " + relPath; } /** * Custom initialization for the class UIBuilder. * @method * @private */ function init() { eventManager.addListener(FOLDER_STATE_CHANGED_EVENT, function (event) { uiBuilder.toggleItemContent(event.body.folder); } ) var fileContentReadyHandler = function (event) { var content = event.body.content; var type = event.body.type? event.body.type : 'text'; if (type == 'text') { uiBuilder.showText(content); } else if (type == 'image'){ uiBuilder.showImage(content); } that.updateTitle(event.body.file); } eventManager.addListener ( FILE_CONTENT_READY_EVENT, fileContentReadyHandler); } /** * Displays the content of the selected file item inside the source * area element. * @method * @public * @param content { String } * The content to be displayed. * @param oDocument { HTML Document Object } * The document object to display the content in. * @param uid { String } * The id for an HTML Element that will host provided content. */ this.showText = function(content) { var oDocument = dProvider.getClientDocument(); var el = oDocument.getElementById('sourceCodeArea'); var txtNode = (el.getElementsByTagName('span')[0]|| dProvider.makeText(' ', el, 'sourceCodeText')).firstChild; txtNode.nodeValue = content; if(el.style.visibility != 'visible') { el.style.visibility = 'visible'; } var noContentText = oDocument.getElementById('srcAreaBgText'); if (noContentText.style.visibility != 'hidden') { noContentText.style.visibility = 'hidden'; } var ruler = oDocument.getElementById('lineNoRuler'); uiBuilder.initRuler (ruler, content); if(ruler.style.visibility != 'visible') { ruler.style.visibility = 'visible'; } var img; while (img = el.getElementsByTagName('img')[0]){ el.removeChild(img); } el.scrollTop = 0; el.scrollLeft = 0; ruler.scrollTop = 0; } /** * Displays the selected file item, provided it is an image. * @method * @public * @param { String } * The application root relative url of the image to display. */ this.showImage = function(url) { var oDocument = dProvider.getClientDocument(); var el = oDocument.getElementById('sourceCodeArea'); el.innerHTML = ""; var ruler = oDocument.getElementById('lineNoRuler'); ruler.innerHTML = ""; var img = dProvider.makeElement ('img', el, 'imageContent'); img.src = url; el.scrollTop = 0; el.scrollLeft = 0; } /** * The DOM provider instance used for building UI elements. * @field * @private */ var dProvider = oDomProvider; /** * Creates an item in the files list on the left. * @method * @public * @param parentEl { HTML Element } * The HTML element to build the new item in. Both 'ul' or 'li' * elements can be specified here. * @param text { String } * The text to display inside the newly created item (i.e, file * name). * @param className { String } * The css class to apply to the newly created element. * @return { HTML Element } * The 'li' element created */ this.makeItem = function(parentEl, text, className) { var item = makeTreeItem(parentEl, text, className); return item; } /** * Transparently creates a generic tree item, regardless of the * actual HTML Element given as parent. * @method * @private * @param parentEl { HTML Element } * The HTML element to build the new item in. Both 'ul' or 'li' * elements can be specified here. * @param text { String } * The text to display inside the newly created item (i.e, file * name). * @param className { String } * The css class to apply to the newly created element. */ function makeTreeItem (parentEl, text, className) { var isUL = parentEl.nodeName && parentEl.nodeName.toLowerCase() == 'ul'; var isLI = parentEl.nodeName && parentEl.nodeName.toLowerCase() == 'li'; var clsName = className? className : 'item'; var item; if(isUL) {item = dProvider.makeElement('li', parentEl, clsName)} else if(isLI) { var wrapper = parentEl.getElementsByTagName('ul')[0] || dProvider.makeElement('ul', parentEl); item = dProvider.makeElement('li', wrapper, clsName); } var textClsName = className? className+'Text' : 'itemText'; var txt = dProvider.makeText(text, item, textClsName); txt.addEventListener('mouseover', function (event){ var evt = eventManager.createEvent ( ITEM_MOUSE_OVER_EVENT, { htmlElement: event.target } ); eventManager.fireEvent(evt); }, false); txt.addEventListener('mouseout', function (event){ var evt = eventManager.createEvent ( ITEM_MOUSE_OUT_EVENT, { htmlElement: event.target } ); eventManager.fireEvent(evt); }, false); txt.addEventListener('click', function( event ) { var evt = eventManager.createEvent ( ITEM_MOUSE_CLICK_EVENT, { htmlElement: event.target } ); eventManager.fireEvent(evt); }, false); return item; } /** * Possibly returns a tree item that matches the given path. * @method * @public * @param treeRoot { DOM Element } * The root, <ul> DOM Element of the tree to search. * @param path { String } * The path to look for. It is expected to be an application * root relative path. Illegal or absolute paths will fail * silently. * @return { DOM Element } * The <li> DOM Element that matches the given path, or null in * case of failure. */ this.getTreeItemByPath = function (treeRoot, path) { var getItemsLabel = function(item) { var sp = item.getElementsByTagName('span')[0]; var txt = sp.innerText; txt = txt.replace(/^\[([^\]]+)\]$/, '$1'); return txt; } var getChildByLabel = function(parentEl, childLabel) { var children = parentEl.childNodes; for (var i=0; i<children.length; i++) { var child = children.item(i); if(child.nodeType != 1) { continue }; if(child.nodeName.toLowerCase() != 'li') { continue }; var labelValue = getItemsLabel(child); if (labelValue == childLabel) { return child; } } } var segments = path.split('/'); segments.reverse(); var current = treeRoot; do { if(segments.length == 0) { break }; var segment = segments.pop(); if(segment.length == 0) { continue }; var item = getChildByLabel(current, segment); if(item) { current = item } else { break }; } while(true); return (current && (segments.length == 0))? current: null; } /** * Displays all given files as sibling items in the tree. * @method * @public * @param parentEl { HTML Element } * The HTML element to build the list in. This can be either a * 'li' node or the root, 'ul' node. * Note: * Any required wrapping is done transparently. * @param files { Array } * An array containing File objects that are to be * listed. */ this.displayFilesList = function(parentEl, files) { this.removeProgressIndicator(parentEl); for(var i=0; i<files.length; i++) { var file = files[i]; var class_name = 'leaf'; var name = file.name; if(file.isDirectory) { name = '[' +name+ ']'; class_name = 'branch'; } var item = this.makeItem(parentEl, name, class_name); var event = eventManager.createEvent(FILE_LISTED_EVENT, {'file': file, 'item': item}); eventManager.fireEvent(event); } } /** * Adds visual clues that the given item has children i.e., a * different CSS style, a progress indicator. * @method * @private * @param item { HTML Element } * The element that is to be marked as non empty. */ this.markItemAsNonEmpty = function(item) { item.className = 'nonEmptyBranch'; var textEl = item.getElementsByTagName('span')[0]; textEl.className = 'nonEmptyBranchText'; this.addProgressIndicator(item); this.toggleItemContent(item); this.setupAsClickableFolderItem(textEl); } /** * Maintains a list with all clickable items. * @field * @private */ var clickableItems = []; /** * Explicitelly unsets all items previously set as clickable. * @method * @public */ this.unsetClickableItems = function() { for(var i=0; i<clickableItems.length; i++) { var item = clickableItems[i]; item.onclick = ""; } clickableItems = []; } /** * Sets the given item as a clickable folder, i.e., clicking on it * will fetch its children, and will collapse/expand it afterwards. * @method * @public * @param item { HTML Element } * The element that is to be setup as a clickable folder. */ this.setupAsClickableFolderItem = function(item) { var cb = function(DOMEvent) { var el = DOMEvent.target; var notYetExpanded = hasProgressIndicator(el.parentNode); if(notYetExpanded) { var event = eventManager.createEvent( FOLDER_FIRST_CLICKED_EVENT, {'folder': el.parentNode} ); eventManager.fireEvent(event); } else { var event = eventManager.createEvent( FOLDER_STATE_CHANGED_EVENT, {'folder': el.parentNode}); eventManager.fireEvent(event); } } item.onclick = cb; clickableItems.push(item); } /** * Sets the given file as clickable, i.e., clicking on it will * display its content in the right pane. * @method * @private * @param item { HTML Element } * The element that is to be setup as a clickable file. * @param file { File } * The file object associated to the item to setup. */ this.setupAsClickableFileItem = function(item, file) { var fileItemClickHandler = function(){ var event = eventManager.createEvent(FILE_ITEM_CLICKED, { 'item': item, 'file': file }); eventManager.fireEvent(event); } item.onclick = fileItemClickHandler; clickableItems.push(item); } /** * Sets up visual feedback in response to user interacting with the * tree items (i.e., mouse-hovering an item, etc). * @method * @private * @see CSSProvider.dynamicCSS */ function hookItemsVisualFeedback() { var current = arguments.callee; // @visual effect for mouse over on tree items. var hoverCb = function(event) { var el = event.body.htmlElement; if (current.lastClicked && current.lastClicked === el) { return; } cssProvider.setStyle(el, 'background-color', 'darkNeutral'); }; eventManager.addListener(ITEM_MOUSE_OVER_EVENT, hoverCb); // @visual effect for mouse out on tree items. var outCb = function(event) { var el = event.body.htmlElement; if (current.lastClicked && current.lastClicked === el) { return; } el.style.backgroundColor = 'transparent'; }; eventManager.addListener (ITEM_MOUSE_OUT_EVENT, outCb); // @visual effect for mouse click on tree items. var clickCb = function(event) { var el = event.body.htmlElement; var old = current.lastClicked; if (old) { old.style.backgroundColor = 'transparent'; } cssProvider.setStyle(el, 'background-color', 'lighterColorAccent'); current.lastClicked = el; }; eventManager.addListener(ITEM_MOUSE_CLICK_EVENT, clickCb); } /** * Alternatively collapses or expands the children of a tree item. * @method * @public * @param { HTML Element } * The item to be collapsed or expanded (based on its current * state). */ this.toggleItemContent = function(item) { var wrapper = item.getElementsByTagName('ul')[0]; if (wrapper) { var visible = wrapper.style.display == 'none'? false: true; wrapper.style.display = visible? 'none' : 'block'; } } /** * Adds a special, temporary child to the given item, indicating * that its actual children are being loaded in background. * @method * @public * @param item { HTML Element } * The element to signalize background loading for. */ this.addProgressIndicator = function(item) { if (!hasProgressIndicator(item)) { makeTreeItem (item, 'loading...', 'progress'); } } /** * Removes the 'progress indicator' from the given item, should it * have one. * @method * @public * @param item { HTML Element } * The element to remove the progress indicator from. */ this.removeProgressIndicator = function(item) { var children = item.getElementsByTagName('span'); for(var i=0; i<children.length; i++) { var child = children[i]; var value = child.innerHTML; if (value == 'loading...') { var itemToRemove = child.parentNode; var _parent = itemToRemove.parentNode; _parent.removeChild(itemToRemove); return; } } } /** * Checks whether a given item currently presents a 'progress * indicator' instead of its actual content. * @method * @private * @param item { HTML Element } * The element to be checked. * @return { Boolean } * True if the element has a 'progress indicator' in place, * false otherwise. */ function hasProgressIndicator (item) { var firstWrapper = item.getElementsByTagName('ul')[0]; if(firstWrapper) { var firstTextEl = firstWrapper. getElementsByTagName('span')[0]; if (firstTextEl) { var value = firstTextEl.innerHTML; if (value == 'loading...') {return true}; } } return false; } /** * Creates the header of the application UI. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element } * The HTML Element holding the UI header. */ function createHeader (parentEl) { var header = dProvider.makeDiv( parentEl, 'rcHeader' ); layoutProvider.setupBox(header, {h: 3}) layoutProvider.setupStretched(header, { bottom: -1 }) dProvider.makeText(ADOBE_TOKEN, header, 'hdrToken'); dProvider.makeText(COPYRIGHT_TOKEN, header, 'hdrToken'); dProvider.makeText(AIR_TOKEN, header, 'hdrToken'); dProvider.makeText(TRADEMARK_TOKEN, header, 'hdrToken'); dProvider.makeText(APP_LEGAL_NAME, header, 'hdrToken'); dProvider.makeText( APP_VERSION_MAJOR_MINOR, header, 'vToken' ); return header; } /** * Creates the left side of the application UI -- the side bar that * contains the tree list showing files and folders. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element } * The HTML Element holding the UI sidebar. */ function createSideBar (parentEl) { var left = dProvider.makeDiv( parentEl, 'rcTree' ); layoutProvider.setupBox(left, {w: 13.85}); layoutProvider.setupStretched(left, {top: 3.2, bottom: 2, right: -1}); dProvider.makeText(TREE_DESCRIPTION_MESSAGE, left, 'listDescr'); var lstBackground = dProvider.makeDiv(left, 'listBackground'); layoutProvider.setupBox(lstBackground); layoutProvider.setupStretched(lstBackground, {top:3, right:0.5, bottom:1, left:0.5}); return left; } /** * Creates the tree list that displays source files. * @method * @private * @param parentEl { HTML Element } * The HTML element to build the tree in. * @return { HTML Element } * The HTML Element holding the UI tree's root. */ function createTree (parentEl) { var root = dProvider.makeElement('ul', parentEl, 'tree'); layoutProvider.setupBox(root); layoutProvider.setupStretched(root, {top:2.5, right:1, bottom:0.5, left: 1}); root.style.overflow = 'auto'; return root; } /** * Creates the right side of the application UI -- the area that * displays selected file's content. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element} * The HTML Element holding the UI content area. */ function createContentArea (parentEl) { var right = dProvider.makeDiv( parentEl, 'rcContent' ); layoutProvider.setupBox(right); layoutProvider.setupStretched(right, {top: 3.2, left: 14, bottom: 2}); var txt = dProvider.makeText('no content to display', right, 'noContent'); layoutProvider.setupBox(txt, {w:10, h:1}); layoutProvider.setupCentered(txt); txt.setAttribute('id', 'srcAreaBgText'); return right; } /** * Creates the ruler showing line numbering for displayed file's * content. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element } * The HTML Element holding the UI ruler */ function createRuler(parentEl) { var lnNumb = dProvider.makeDiv(parentEl, 'ruler'); layoutProvider.setupBox (lnNumb, {w:4}); layoutProvider.setupStretched (lnNumb, {left:0.5, top:0.5, right:-1, bottom:0.8 }); lnNumb.setAttribute('id', 'lineNoRuler'); return lnNumb; } /** * Creates the UI element that will display the selected source * file's source code. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element } * The HTML Element holding the UI source area. */ function createSourceArea(parentEl) { var srcArea = dProvider.makeDiv(parentEl, 'srcCodeArea'); layoutProvider.setupBox (srcArea); layoutProvider.setupStretched (srcArea, {left:4.5, top:0.5, right:0.35, bottom:0.8, }); srcArea.setAttribute('id', 'sourceCodeArea'); return srcArea; } /** * Creates the footer of the application UI. * @method * @private * @param parentEl { HTML Element } * The HTML Element to build in. * @return { HTML Element } * The HTML Element holding the UI footer area. */ function createFooter (parentEl) { var footer = dProvider.makeDiv( parentEl, 'rcFooter' ); layoutProvider.setupBox(footer, {h: 2}); layoutProvider.setupStretched(footer, {top: -1}); dProvider.makeText(COPYRIGHT_MESSAGE, footer, 'copyrightText'); return footer; } /** * Creates the application user interface. * @method * @public * @param parentEl { HTML Element } * The HTML Element to build in. * @return { Object } * A hash with all UI elements created. */ this.createMainLayout = function(parentEl) { var header = createHeader(parentEl); var sidebar = createSideBar(parentEl); var tree = createTree(sidebar); this.addProgressIndicator (tree); var contentArea = createContentArea(parentEl); var ruler = createRuler(contentArea); var sourceArea = createSourceArea(contentArea); linkScrollable(ruler, sourceArea); var footer = createFooter(parentEl); cssProvider.applyCSS(); hookItemsVisualFeedback(); return { 'header' : header, 'sidebar' : sidebar, 'tree' : tree, 'contentArea' : contentArea, 'ruler' : ruler, 'sourceArea' : sourceArea, 'footer' : footer }; } /** * Initiates the linked ruler that shows line numbers for the file * content being displayed. * @method * @public * @param parentEl { HTML Element } * The HTML Element to build in. * @assocText { String } * A formatted string to show line numbers for. The ruler will * add a new number for each occurence of the new line * character in this string. */ this.initRuler = function(parentEl, assocText) { var count = 0; var index = 0 var str = ''; domProvider.destroyContent(parentEl); do { var searchIndex = assocText.indexOf("\n", index); count++; str += count + "\n"; index = searchIndex + 1; if (searchIndex == -1) { str+="EOF"; break; }; } while (true); dProvider.makeText(str, parentEl, 'rulerText'); } /** * Links two elements, so that they scroll together. * @method * @private * @param scrollable { HTML Element } * The target element to be linked. * @param related { HTML Element } * The source element to link with. * @param proxy { Function } * An optional function that specifies how the scroll value of * the 'related' element applies to the 'scrollable' element. * Default is to just pass the 'scrollTop' value from 'related' * to 'scrollable'. */ function linkScrollable (scrollable, related, proxy) { // @private function; scrolls a given element. var scrollElement = function(element, offset) { element.scrollTop = offset; } related.addEventListener('scroll', function() { scrollElement (scrollable, Utils.isFunction(proxy)? proxy.call(this, related.scrollTop): related.scrollTop ); }, false ); } // @perform custom initialization of this class. init(); } /** * CLASS * FileSystemWalker * DESCRIPTION * Private class that encapsulates functionality related to file * system traversal (i.e., recursively iterating through a folder's * children). * SAMPLE USAGE * N/A (internal use only) * @class * @private */ function FileSystemWalker() { // @let private methods see own class' instance: var that = this; /** * Keeps a record of all listed files. * @field * @private */ var listedFiles = {}; /** * Custom initialization for the