diff --git a/.gitignore b/.gitignore index 43506668..3a342fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ npm-debug.log *.log -/plugins/**/*.css +/plugins/vorlon/**/*.css tsconfig.json /Plugins/obj **/node_modules @@ -11,9 +11,8 @@ bin/ GLE id�es.txt sync.bat Plugins/Vorlon/plugins/remoteDebugging.zip -<<<<<<< HEAD .settings/launch.json -======= *.dat ->>>>>>> 8e38a9097a969c97676d00f30898bb85b7de6c5c Server/public/stylesheets/style.css +vorlon +/Server/public/stylesheets/style.css diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..ee84dc00 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,40 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + // ONLY "node" and "mono" are supported, change "type" to switch. + "configurations": [ + { + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Launch Server/server.js", + // Type of configuration. Possible values: "node", "mono". + "type": "node", + // Workspace relative or absolute path to the program. + "program": "Server/server.js", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": [], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": ".", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Optional arguments passed to the runtime executable. + "runtimeArgs": ["--nolazy"], + // Environment variables passed to the program. + "env": { }, + // Use JavaScript source maps (if they exist). + "sourceMaps": false, + // If JavaScript source maps are enabled, the generated code is expected in this directory. + "outDir": null + }, + { + "name": "Attach", + "type": "node", + // TCP/IP address. Default is "localhost". + "address": "localhost", + // Port to attach to. + "port": 5858, + "sourceMaps": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..d14ae151 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +// Place your settings in this file to overwrite default and user settings. +{ + + "editor.fontSize": 15, +} \ No newline at end of file diff --git a/Plugins/Vorlon/.gitignore b/Plugins/Vorlon/.gitignore index ce7afb1e..6f34efdb 100644 --- a/Plugins/Vorlon/.gitignore +++ b/Plugins/Vorlon/.gitignore @@ -1,3 +1,5 @@ *.js *.js.map -*.min.js \ No newline at end of file +*.min.js +!verge.min.js +!res.min.js \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.1.d.ts b/Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.2.d.ts similarity index 93% rename from Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.1.d.ts rename to Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.2.d.ts index d2025f5b..30d14580 100644 --- a/Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.1.d.ts +++ b/Plugins/Vorlon/plugins/babylonInspector/libs/babylon.2.2.d.ts @@ -54,6 +54,10 @@ declare module BABYLON { private static _ALPHA_DISABLE; private static _ALPHA_ADD; private static _ALPHA_COMBINE; + private static _ALPHA_SUBTRACT; + private static _ALPHA_MULTIPLY; + private static _ALPHA_MAXIMIZED; + private static _ALPHA_ONEONE; private static _DELAYLOADSTATE_NONE; private static _DELAYLOADSTATE_LOADED; private static _DELAYLOADSTATE_LOADING; @@ -66,8 +70,12 @@ declare module BABYLON { private static _TEXTURETYPE_UNSIGNED_INT; private static _TEXTURETYPE_FLOAT; static ALPHA_DISABLE: number; + static ALPHA_ONEONE: number; static ALPHA_ADD: number; static ALPHA_COMBINE: number; + static ALPHA_SUBTRACT: number; + static ALPHA_MULTIPLY: number; + static ALPHA_MAXIMIZED: number; static DELAYLOADSTATE_NONE: number; static DELAYLOADSTATE_LOADED: number; static DELAYLOADSTATE_LOADING: number; @@ -88,6 +96,7 @@ declare module BABYLON { isPointerLock: boolean; cullBackFaces: boolean; renderEvenInBackground: boolean; + enableOfflineSupport: boolean; scenes: Scene[]; _gl: WebGLRenderingContext; private _renderingCanvas; @@ -238,6 +247,8 @@ declare module BABYLON { setArray4(uniform: WebGLUniformLocation, array: number[]): void; setMatrices(uniform: WebGLUniformLocation, matrices: Float32Array): void; setMatrix(uniform: WebGLUniformLocation, matrix: Matrix): void; + setMatrix3x3(uniform: WebGLUniformLocation, matrix: Float32Array): void; + setMatrix2x2(uniform: WebGLUniformLocation, matrix: Float32Array): void; setFloat(uniform: WebGLUniformLocation, value: number): void; setFloat2(uniform: WebGLUniformLocation, x: number, y: number): void; setFloat3(uniform: WebGLUniformLocation, x: number, y: number, z: number): void; @@ -257,7 +268,8 @@ declare module BABYLON { wipeCaches(): void; setSamplingMode(texture: WebGLTexture, samplingMode: number): void; createTexture(url: string, noMipmap: boolean, invertY: boolean, scene: Scene, samplingMode?: number, onLoad?: () => void, onError?: () => void, buffer?: any): WebGLTexture; - createRawTexture(data: ArrayBufferView, width: number, height: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number): WebGLTexture; + updateRawTexture(texture: WebGLTexture, data: ArrayBufferView, format: number, invertY: boolean, compression?: string): void; + createRawTexture(data: ArrayBufferView, width: number, height: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression?: string): WebGLTexture; createDynamicTexture(width: number, height: number, generateMipMaps: boolean, samplingMode: number, forceExponantOfTwo?: boolean): WebGLTexture; updateTextureSamplingMode(samplingMode: number, texture: WebGLTexture): void; updateDynamicTexture(texture: WebGLTexture, canvas: HTMLCanvasElement, invertY: boolean): void; @@ -282,6 +294,7 @@ declare module BABYLON { static isSupported(): boolean; } } + interface Window { mozIndexedDB(func: any): any; webkitIndexedDB(func: any): any; @@ -291,7 +304,6 @@ interface Window { IDBKeyRange(func: any): any; webkitIDBKeyRange(func: any): any; msIDBKeyRange(func: any): any; - URL: HTMLURL; webkitURL: HTMLURL; webkitRequestAnimationFrame(func: any): any; mozRequestAnimationFrame(func: any): any; @@ -311,14 +323,12 @@ interface Document { webkitCancelFullScreen(): void; mozCancelFullScreen(): void; msCancelFullScreen(): void; - webkitIsFullScreen: boolean; mozFullScreen: boolean; msIsFullScreen: boolean; fullscreen: boolean; mozPointerLockElement: HTMLElement; msPointerLockElement: HTMLElement; webkitPointerLockElement: HTMLElement; - pointerLockElement: HTMLElement; } interface HTMLCanvasElement { requestPointerLock(): void; @@ -352,6 +362,7 @@ interface WebGLTexture { _cachedCoordinatesMode: number; _cachedWrapU: number; _cachedWrapV: number; + _isDisabled: boolean; } interface WebGLBuffer { references: number; @@ -359,8 +370,6 @@ interface WebGLBuffer { is32Bits: boolean; } interface MouseEvent { - movementX: number; - movementY: number; mozMovementX: number; mozMovementY: number; webkitMovementX: number; @@ -381,6 +390,7 @@ interface Screen { orientation: string; mozOrientation: string; } + declare module BABYLON { /** * Node is the basic class for all scene objects (Mesh, Light Camera). @@ -452,6 +462,7 @@ declare module BABYLON { _setReady(state: boolean): void; } } + declare module BABYLON { interface IDisposable { dispose(): void; @@ -622,12 +633,14 @@ declare module BABYLON { private _renderTargets; _activeParticleSystems: SmartArray; private _activeSkeletons; + private _softwareSkinnedMeshes; _activeBones: number; private _renderingManager; private _physicsEngine; _activeAnimatables: Animatable[]; private _transformMatrix; private _pickWithRayInverseMatrix; + private _edgesRenderers; private _boundingBoxRenderer; private _outlineRenderer; private _viewMatrix; @@ -645,6 +658,7 @@ declare module BABYLON { constructor(engine: Engine); debugLayer: DebugLayer; workerCollisions: boolean; + SelectionOctree: Octree; /** * The mesh that is currently under the pointer. * @return {BABYLON.AbstractMesh} mesh under the pointer/mouse cursor or null if none. @@ -755,6 +769,7 @@ declare module BABYLON { * @return {BABYLON.Material|null} the material or null if none found. */ getMaterialByName(name: string): Material; + getLensFlareSystemByName(name: string): LensFlareSystem; getCameraByID(id: string): Camera; getCameraByUniqueID(uniqueId: number): Camera; /** @@ -825,6 +840,7 @@ declare module BABYLON { * @return {BABYLON.Node|null} the node found or null if not found at all. */ getLastEntryByID(id: string): Node; + getNodeByID(id: string): Node; getNodeByName(name: string): Node; getMeshByName(name: string): AbstractMesh; getSoundByName(name: string): Sound; @@ -877,6 +893,7 @@ declare module BABYLON { getMaterialByTags(tagsQuery: string, forEach?: (material: Material) => void): Material[]; } } + declare module BABYLON { class Action { triggerOptions: any; @@ -896,6 +913,7 @@ declare module BABYLON { _getEffectiveTarget(target: any, propertyPath: string): any; } } + declare module BABYLON { /** * ActionEvent is the event beint sent when an action is triggered. @@ -906,6 +924,7 @@ declare module BABYLON { pointerY: number; meshUnderPointer: AbstractMesh; sourceEvent: any; + additionalData: any; /** * @constructor * @param source The mesh that triggered the action. @@ -914,13 +933,13 @@ declare module BABYLON { * @param meshUnderPointer The mesh that is currently pointed at (can be null) * @param sourceEvent the original (browser) event that triggered the ActionEvent */ - constructor(source: AbstractMesh, pointerX: number, pointerY: number, meshUnderPointer: AbstractMesh, sourceEvent?: any); + constructor(source: AbstractMesh, pointerX: number, pointerY: number, meshUnderPointer: AbstractMesh, sourceEvent?: any, additionalData?: any); /** * Helper function to auto-create an ActionEvent from a source mesh. * @param source the source mesh that triggered the event * @param evt {Event} The original (browser) event */ - static CreateNew(source: AbstractMesh, evt?: Event): ActionEvent; + static CreateNew(source: AbstractMesh, evt?: Event, additionalData?: any): ActionEvent; /** * Helper function to auto-create an ActionEvent from a scene. If triggered by a mesh use ActionEvent.CreateNew * @param scene the scene where the event occurred @@ -1002,6 +1021,7 @@ declare module BABYLON { _getProperty(propertyPath: string): string; } } + declare module BABYLON { class Condition { _actionManager: ActionManager; @@ -1044,6 +1064,7 @@ declare module BABYLON { isValid(): boolean; } } + declare module BABYLON { class SwitchBooleanAction extends Action { propertyPath: string; @@ -1127,6 +1148,7 @@ declare module BABYLON { execute(): void; } } + declare module BABYLON { class InterpolateValueAction extends Action { propertyPath: string; @@ -1140,6 +1162,7 @@ declare module BABYLON { execute(): void; } } + declare module BABYLON { class Animatable { target: any; @@ -1157,12 +1180,14 @@ declare module BABYLON { constructor(scene: Scene, target: any, fromFrame?: number, toFrame?: number, loopAnimation?: boolean, speedRatio?: number, onAnimationEnd?: any, animations?: any); appendAnimations(target: any, animations: Animation[]): void; getAnimationByTargetProperty(property: string): Animation; + reset(): void; pause(): void; restart(): void; stop(): void; _animate(delay: number): boolean; } } + declare module BABYLON { class Animation { name: string; @@ -1178,8 +1203,10 @@ declare module BABYLON { private _easingFunction; targetPropertyPath: string[]; currentFrame: number; - static CreateAndStartAnimation(name: string, mesh: AbstractMesh, tartgetProperty: string, framePerSecond: number, totalFrame: number, from: any, to: any, loopMode?: number): Animatable; + allowMatricesInterpolation: boolean; + static CreateAndStartAnimation(name: string, mesh: AbstractMesh, targetProperty: string, framePerSecond: number, totalFrame: number, from: any, to: any, loopMode?: number, easingFunction?: EasingFunction): Animatable; constructor(name: string, targetProperty: string, framePerSecond: number, dataType: number, loopMode?: number); + reset(): void; isStopped(): boolean; getKeys(): any[]; getEasingFunction(): IEasingFunction; @@ -1215,6 +1242,7 @@ declare module BABYLON { static ANIMATIONLOOPMODE_CONSTANT: number; } } + declare module BABYLON { interface IEasingFunction { ease(gradient: number): number; @@ -1286,6 +1314,7 @@ declare module BABYLON { easeInCore(gradient: number): number; } } + declare module BABYLON { class Analyser { SMOOTHING: number; @@ -1319,6 +1348,7 @@ declare module BABYLON { dispose(): void; } } + declare module BABYLON { class AudioEngine { private _audioContext; @@ -1336,6 +1366,7 @@ declare module BABYLON { connectToAnalyser(analyser: Analyser): void; } } + declare module BABYLON { class Sound { name: string; @@ -1424,6 +1455,7 @@ declare module BABYLON { private _onRegisterAfterWorldMatrixUpdate(connectedMesh); } } + declare module BABYLON { class SoundTrack { private _audioEngine; @@ -1445,8 +1477,9 @@ declare module BABYLON { connectToAnalyser(analyser: Analyser): void; } } + declare module BABYLON { - class Bone { + class Bone extends Node { name: string; children: Bone[]; animations: Animation[]; @@ -1469,6 +1502,7 @@ declare module BABYLON { markAsDirty(): void; } } + declare module BABYLON { class Skeleton { name: string; @@ -1481,25 +1515,16 @@ declare module BABYLON { private _identity; constructor(name: string, id: string, scene: Scene); getTransformMatrices(): Float32Array; + getScene(): Scene; _markAsDirty(): void; prepare(): void; getAnimatables(): IAnimatable[]; clone(name: string, id: string): Skeleton; } } + declare module BABYLON { - class AnaglyphFreeCamera extends FreeCamera { - constructor(name: string, position: Vector3, eyeSpace: number, scene: Scene); - } - class AnaglyphArcRotateCamera extends ArcRotateCamera { - constructor(name: string, alpha: number, beta: number, radius: number, target: any, eyeSpace: number, scene: Scene); - } - class AnaglyphGamepadCamera extends GamepadCamera { - constructor(name: string, position: Vector3, eyeSpace: number, scene: Scene); - } -} -declare module BABYLON { - class ArcRotateCamera extends Camera { + class ArcRotateCamera extends TargetCamera { alpha: number; beta: number; radius: number; @@ -1513,9 +1538,13 @@ declare module BABYLON { upperBetaLimit: number; lowerRadiusLimit: any; upperRadiusLimit: any; - angularSensibility: number; + angularSensibilityX: number; + angularSensibilityY: number; wheelPrecision: number; pinchPrecision: number; + panningSensibility: number; + inertialPanningX: number; + inertialPanningY: number; keysUp: number[]; keysDown: number[]; keysLeft: number[]; @@ -1525,8 +1554,9 @@ declare module BABYLON { pinchInwards: boolean; allowUpsideDown: boolean; private _keys; - private _viewMatrix; + _viewMatrix: Matrix; private _attachedElement; + private _onContextMenu; private _onPointerDown; private _onPointerUp; private _onPointerMove; @@ -1535,10 +1565,14 @@ declare module BABYLON { private _onKeyDown; private _onKeyUp; private _onLostFocus; - private _reset; + _reset: () => void; private _onGestureStart; private _onGesture; private _MSGestureHandler; + private _localDirection; + private _transformedDirection; + private _isRightClick; + private _isCtrlPushed; onCollide: (collidedMesh: AbstractMesh) => void; checkCollisions: boolean; collisionRadius: Vector3; @@ -1550,20 +1584,22 @@ declare module BABYLON { private _previousBeta; private _previousRadius; private _collisionTriggered; + angularSensibility: number; constructor(name: string, alpha: number, beta: number, radius: number, target: any, scene: Scene); _getTargetPosition(): Vector3; _initCache(): void; _updateCache(ignoreParentClass?: boolean): void; _isSynchronizedViewMatrix(): boolean; - attachControl(element: HTMLElement, noPreventDefault?: boolean): void; + attachControl(element: HTMLElement, noPreventDefault?: boolean, useCtrlForPanning?: boolean): void; detachControl(element: HTMLElement): void; _checkInputs(): void; private _checkLimits(); setPosition(position: Vector3): void; + setTarget(target: Vector3): void; _getViewMatrix(): Matrix; private _onCollisionPositionChange; - zoomOn(meshes?: AbstractMesh[]): void; - focusOn(meshesOrMinMaxVectorAndDistance: any): void; + zoomOn(meshes?: AbstractMesh[], doNotUpdateMaxZ?: boolean): void; + focusOn(meshesOrMinMaxVectorAndDistance: any, doNotUpdateMaxZ?: boolean): void; /** * @override * Override Camera.createRigCamera @@ -1576,6 +1612,7 @@ declare module BABYLON { _updateRigCameras(): void; } } + declare module BABYLON { class VRCameraMetrics { hResolution: number; @@ -1680,6 +1717,7 @@ declare module BABYLON { _updateRigCameras(): void; } } + declare module BABYLON { class DeviceOrientationCamera extends FreeCamera { private _offsetX; @@ -1698,6 +1736,7 @@ declare module BABYLON { _checkInputs(): void; } } + declare module BABYLON { class FollowCamera extends TargetCamera { radius: number; @@ -1711,7 +1750,18 @@ declare module BABYLON { private follow(cameraTarget); _checkInputs(): void; } + class ArcFollowCamera extends TargetCamera { + alpha: number; + beta: number; + radius: number; + target: AbstractMesh; + private _cartesianCoordinates; + constructor(name: string, alpha: number, beta: number, radius: number, target: AbstractMesh, scene: Scene); + private follow(); + _checkInputs(): void; + } } + declare module BABYLON { class FreeCamera extends TargetCamera { ellipsoid: Vector3; @@ -1750,6 +1800,7 @@ declare module BABYLON { _updatePosition(): void; } } + declare module BABYLON { class GamepadCamera extends FreeCamera { private _gamepad; @@ -1762,6 +1813,28 @@ declare module BABYLON { dispose(): void; } } + +declare module BABYLON { + class AnaglyphFreeCamera extends FreeCamera { + constructor(name: string, position: Vector3, interaxialDistance: number, scene: Scene); + } + class AnaglyphArcRotateCamera extends ArcRotateCamera { + constructor(name: string, alpha: number, beta: number, radius: number, target: any, interaxialDistance: number, scene: Scene); + } + class AnaglyphGamepadCamera extends GamepadCamera { + constructor(name: string, position: Vector3, interaxialDistance: number, scene: Scene); + } + class StereoscopicFreeCamera extends FreeCamera { + constructor(name: string, position: Vector3, interaxialDistance: number, isSideBySide: boolean, scene: Scene); + } + class StereoscopicArcRotateCamera extends ArcRotateCamera { + constructor(name: string, alpha: number, beta: number, radius: number, target: any, interaxialDistance: number, isSideBySide: boolean, scene: Scene); + } + class StereoscopicGamepadCamera extends GamepadCamera { + constructor(name: string, position: Vector3, interaxialDistance: number, isSideBySide: boolean, scene: Scene); + } +} + declare module BABYLON { class TargetCamera extends Camera { cameraDirection: Vector3; @@ -1783,6 +1856,7 @@ declare module BABYLON { _reset: () => void; _waitingLockedTargetId: string; constructor(name: string, position: Vector3, scene: Scene); + getFrontPosition(distance: number): Vector3; _getLockedTargetPosition(): Vector3; _initCache(): void; _updateCache(ignoreParentClass?: boolean): void; @@ -1808,6 +1882,7 @@ declare module BABYLON { private _getRigCamPosition(halfSpace, result); } } + declare module BABYLON { class TouchCamera extends FreeCamera { private _offsetX; @@ -1826,59 +1901,19 @@ declare module BABYLON { _checkInputs(): void; } } + declare module BABYLON { class VirtualJoysticksCamera extends FreeCamera { private _leftjoystick; private _rightjoystick; constructor(name: string, position: Vector3, scene: Scene); + getLeftJoystick(): VirtualJoystick; + getRightJoystick(): VirtualJoystick; _checkInputs(): void; dispose(): void; } } -declare module BABYLON { - class VRCamera extends FreeCamera { - private _leftCamera; - private _rightCamera; - private _offsetOrientation; - private _deviceOrientationHandler; - constructor(name: string, position: Vector3, scene: Scene, compensateDistorsion?: boolean); - _update(): void; - _updateCamera(camera: FreeCamera): void; - _onOrientationEvent(evt: DeviceOrientationEvent): void; - attachControl(element: HTMLElement, noPreventDefault?: boolean): void; - detachControl(element: HTMLElement): void; - } -} -declare module BABYLON { - class VRDeviceOrientationFreeCamera extends FreeCamera { - _alpha: number; - _beta: number; - _gamma: number; - private _offsetOrientation; - private _deviceOrientationHandler; - constructor(name: string, position: Vector3, scene: Scene, compensateDistorsion?: boolean); - _onOrientationEvent(evt: DeviceOrientationEvent): void; - attachControl(element: HTMLElement, noPreventDefault?: boolean): void; - detachControl(element: HTMLElement): void; - } -} -declare var HMDVRDevice: any; -declare var PositionSensorVRDevice: any; -declare module BABYLON { - class WebVRFreeCamera extends FreeCamera { - _hmdDevice: any; - _sensorDevice: any; - _cacheState: any; - _cacheQuaternion: Quaternion; - _cacheRotation: Vector3; - _vrEnabled: boolean; - constructor(name: string, position: Vector3, scene: Scene, compensateDistorsion?: boolean); - private _getWebVRDevices(devices); - _checkInputs(): void; - attachControl(element: HTMLElement, noPreventDefault?: boolean): void; - detachControl(element: HTMLElement): void; - } -} + declare module BABYLON { class Collider { radius: Vector3; @@ -1915,6 +1950,7 @@ declare module BABYLON { _getResponse(pos: Vector3, vel: Vector3): void; } } + declare module BABYLON { var CollisionWorker: string; interface ICollisionCoordinator { @@ -2050,6 +2086,7 @@ declare module BABYLON { private _collideWithWorld(position, velocity, collider, maximumRetry, finalPosition, excludedMesh?); } } + declare module BABYLON { var WorkerIncluded: boolean; class CollisionCache { @@ -2091,6 +2128,7 @@ declare module BABYLON { onCollision(payload: CollidePayload): void; } } + declare module BABYLON { class IntersectionInfo { bu: number; @@ -2109,112 +2147,11 @@ declare module BABYLON { bv: number; faceId: number; subMeshId: number; - getNormal(useWorldCoordinates?: boolean): Vector3; + getNormal(useWorldCoordinates?: boolean, useVerticesNormals?: boolean): Vector3; getTextureCoordinates(): Vector2; } } -declare module BABYLON { - class BoundingBox { - minimum: Vector3; - maximum: Vector3; - vectors: Vector3[]; - center: Vector3; - extendSize: Vector3; - directions: Vector3[]; - vectorsWorld: Vector3[]; - minimumWorld: Vector3; - maximumWorld: Vector3; - private _worldMatrix; - constructor(minimum: Vector3, maximum: Vector3); - getWorldMatrix(): Matrix; - _update(world: Matrix): void; - isInFrustum(frustumPlanes: Plane[]): boolean; - isCompletelyInFrustum(frustumPlanes: Plane[]): boolean; - intersectsPoint(point: Vector3): boolean; - intersectsSphere(sphere: BoundingSphere): boolean; - intersectsMinMax(min: Vector3, max: Vector3): boolean; - static Intersects(box0: BoundingBox, box1: BoundingBox): boolean; - static IntersectsSphere(minPoint: Vector3, maxPoint: Vector3, sphereCenter: Vector3, sphereRadius: number): boolean; - static IsCompletelyInFrustum(boundingVectors: Vector3[], frustumPlanes: Plane[]): boolean; - static IsInFrustum(boundingVectors: Vector3[], frustumPlanes: Plane[]): boolean; - } -} -declare module BABYLON { - class BoundingInfo { - minimum: Vector3; - maximum: Vector3; - boundingBox: BoundingBox; - boundingSphere: BoundingSphere; - constructor(minimum: Vector3, maximum: Vector3); - _update(world: Matrix): void; - isInFrustum(frustumPlanes: Plane[]): boolean; - isCompletelyInFrustum(frustumPlanes: Plane[]): boolean; - _checkCollision(collider: Collider): boolean; - intersectsPoint(point: Vector3): boolean; - intersects(boundingInfo: BoundingInfo, precise: boolean): boolean; - } -} -declare module BABYLON { - class BoundingSphere { - minimum: Vector3; - maximum: Vector3; - center: Vector3; - radius: number; - centerWorld: Vector3; - radiusWorld: number; - private _tempRadiusVector; - constructor(minimum: Vector3, maximum: Vector3); - _update(world: Matrix): void; - isInFrustum(frustumPlanes: Plane[]): boolean; - intersectsPoint(point: Vector3): boolean; - static Intersects(sphere0: BoundingSphere, sphere1: BoundingSphere): boolean; - } -} -declare module BABYLON { - interface IOctreeContainer { - blocks: Array>; - } - class Octree { - maxDepth: number; - blocks: Array>; - dynamicContent: T[]; - private _maxBlockCapacity; - private _selectionContent; - private _creationFunc; - constructor(creationFunc: (entry: T, block: OctreeBlock) => void, maxBlockCapacity?: number, maxDepth?: number); - update(worldMin: Vector3, worldMax: Vector3, entries: T[]): void; - addMesh(entry: T): void; - select(frustumPlanes: Plane[], allowDuplicate?: boolean): SmartArray; - intersects(sphereCenter: Vector3, sphereRadius: number, allowDuplicate?: boolean): SmartArray; - intersectsRay(ray: Ray): SmartArray; - static _CreateBlocks(worldMin: Vector3, worldMax: Vector3, entries: T[], maxBlockCapacity: number, currentDepth: number, maxDepth: number, target: IOctreeContainer, creationFunc: (entry: T, block: OctreeBlock) => void): void; - static CreationFuncForMeshes: (entry: AbstractMesh, block: OctreeBlock) => void; - static CreationFuncForSubMeshes: (entry: SubMesh, block: OctreeBlock) => void; - } -} -declare module BABYLON { - class OctreeBlock { - entries: T[]; - blocks: Array>; - private _depth; - private _maxDepth; - private _capacity; - private _minPoint; - private _maxPoint; - private _boundingVectors; - private _creationFunc; - constructor(minPoint: Vector3, maxPoint: Vector3, capacity: number, depth: number, maxDepth: number, creationFunc: (entry: T, block: OctreeBlock) => void); - capacity: number; - minPoint: Vector3; - maxPoint: Vector3; - addEntry(entry: T): void; - addEntries(entries: T[]): void; - select(frustumPlanes: Plane[], selection: SmartArray, allowDuplicate?: boolean): void; - intersects(sphereCenter: Vector3, sphereRadius: number, selection: SmartArray, allowDuplicate?: boolean): void; - intersectsRay(ray: Ray, selection: SmartArray): void; - createInnerBlocks(): void; - } -} + declare module BABYLON { class DebugLayer { private _scene; @@ -2270,6 +2207,7 @@ declare module BABYLON { private _displayStats(); } } + declare module BABYLON { class Layer { name: string; @@ -2288,6 +2226,7 @@ declare module BABYLON { dispose(): void; } } + declare module BABYLON { class LensFlare { size: number; @@ -2299,12 +2238,14 @@ declare module BABYLON { dispose: () => void; } } + declare module BABYLON { class LensFlareSystem { name: string; lensFlares: LensFlare[]; borderLimit: number; meshesSelectionPredicate: (mesh: Mesh) => boolean; + layerMask: number; private _scene; private _emitter; private _vertexDeclaration; @@ -2319,6 +2260,7 @@ declare module BABYLON { isEnabled: boolean; getScene(): Scene; getEmitter(): any; + setEmitter(newEmitter: any): void; getEmitterPosition(): Vector3; computeEffectivePosition(globalViewport: Viewport): boolean; _isVisible(): boolean; @@ -2326,6 +2268,67 @@ declare module BABYLON { dispose(): void; } } + +declare module BABYLON { + class BoundingBox { + minimum: Vector3; + maximum: Vector3; + vectors: Vector3[]; + center: Vector3; + extendSize: Vector3; + directions: Vector3[]; + vectorsWorld: Vector3[]; + minimumWorld: Vector3; + maximumWorld: Vector3; + private _worldMatrix; + constructor(minimum: Vector3, maximum: Vector3); + getWorldMatrix(): Matrix; + _update(world: Matrix): void; + isInFrustum(frustumPlanes: Plane[]): boolean; + isCompletelyInFrustum(frustumPlanes: Plane[]): boolean; + intersectsPoint(point: Vector3): boolean; + intersectsSphere(sphere: BoundingSphere): boolean; + intersectsMinMax(min: Vector3, max: Vector3): boolean; + static Intersects(box0: BoundingBox, box1: BoundingBox): boolean; + static IntersectsSphere(minPoint: Vector3, maxPoint: Vector3, sphereCenter: Vector3, sphereRadius: number): boolean; + static IsCompletelyInFrustum(boundingVectors: Vector3[], frustumPlanes: Plane[]): boolean; + static IsInFrustum(boundingVectors: Vector3[], frustumPlanes: Plane[]): boolean; + } +} + +declare module BABYLON { + class BoundingInfo { + minimum: Vector3; + maximum: Vector3; + boundingBox: BoundingBox; + boundingSphere: BoundingSphere; + constructor(minimum: Vector3, maximum: Vector3); + _update(world: Matrix): void; + isInFrustum(frustumPlanes: Plane[]): boolean; + isCompletelyInFrustum(frustumPlanes: Plane[]): boolean; + _checkCollision(collider: Collider): boolean; + intersectsPoint(point: Vector3): boolean; + intersects(boundingInfo: BoundingInfo, precise: boolean): boolean; + } +} + +declare module BABYLON { + class BoundingSphere { + minimum: Vector3; + maximum: Vector3; + center: Vector3; + radius: number; + centerWorld: Vector3; + radiusWorld: number; + private _tempRadiusVector; + constructor(minimum: Vector3, maximum: Vector3); + _update(world: Matrix): void; + isInFrustum(frustumPlanes: Plane[]): boolean; + intersectsPoint(point: Vector3): boolean; + static Intersects(sphere0: BoundingSphere, sphere1: BoundingSphere): boolean; + } +} + declare module BABYLON { class DirectionalLight extends Light implements IShadowLight { direction: Vector3; @@ -2345,6 +2348,7 @@ declare module BABYLON { _getWorldMatrix(): Matrix; } } + declare module BABYLON { class HemisphericLight extends Light { direction: Vector3; @@ -2357,6 +2361,7 @@ declare module BABYLON { _getWorldMatrix(): Matrix; } } + declare module BABYLON { interface IShadowLight { position: Vector3; @@ -2393,6 +2398,7 @@ declare module BABYLON { dispose(): void; } } + declare module BABYLON { class PointLight extends Light { position: Vector3; @@ -2405,6 +2411,7 @@ declare module BABYLON { _getWorldMatrix(): Matrix; } } + declare module BABYLON { class SpotLight extends Light implements IShadowLight { position: Vector3; @@ -2425,57 +2432,7 @@ declare module BABYLON { _getWorldMatrix(): Matrix; } } -declare module BABYLON { - class ShadowGenerator { - private static _FILTER_NONE; - private static _FILTER_VARIANCESHADOWMAP; - private static _FILTER_POISSONSAMPLING; - private static _FILTER_BLURVARIANCESHADOWMAP; - static FILTER_NONE: number; - static FILTER_VARIANCESHADOWMAP: number; - static FILTER_POISSONSAMPLING: number; - static FILTER_BLURVARIANCESHADOWMAP: number; - private _filter; - blurScale: number; - private _blurBoxOffset; - private _bias; - bias: number; - blurBoxOffset: number; - filter: number; - useVarianceShadowMap: boolean; - usePoissonSampling: boolean; - useBlurVarianceShadowMap: boolean; - private _light; - private _scene; - private _shadowMap; - private _shadowMap2; - private _darkness; - private _transparencyShadow; - private _effect; - private _viewMatrix; - private _projectionMatrix; - private _transformMatrix; - private _worldViewProjection; - private _cachedPosition; - private _cachedDirection; - private _cachedDefines; - private _currentRenderID; - private _downSamplePostprocess; - private _boxBlurPostprocess; - private _mapSize; - constructor(mapSize: number, light: IShadowLight); - isReady(subMesh: SubMesh, useInstances: boolean): boolean; - getShadowMap(): RenderTargetTexture; - getShadowMapForRendering(): RenderTargetTexture; - getLight(): IShadowLight; - getTransformMatrix(): Matrix; - getDarkness(): number; - setDarkness(darkness: number): void; - setTransparencyShadow(hasShadow: boolean): void; - private _packHalf(depth); - dispose(): void; - } -} + declare module BABYLON { interface ISceneLoaderPlugin { extensions: string; @@ -2507,8 +2464,7 @@ declare module BABYLON { static Append(rootUrl: string, sceneFilename: any, scene: Scene, onsuccess?: (scene: Scene) => void, progressCallBack?: any, onerror?: (scene: Scene) => void): void; } } -declare module BABYLON.Internals { -} + declare module BABYLON { class EffectFallbacks { private _defines; @@ -2561,18 +2517,22 @@ declare module BABYLON { setArray4(uniformName: string, array: number[]): Effect; setMatrices(uniformName: string, matrices: Float32Array): Effect; setMatrix(uniformName: string, matrix: Matrix): Effect; + setMatrix3x3(uniformName: string, matrix: Float32Array): Effect; + setMatrix2x2(uniformname: string, matrix: Float32Array): Effect; setFloat(uniformName: string, value: number): Effect; setBool(uniformName: string, bool: boolean): Effect; setVector2(uniformName: string, vector2: Vector2): Effect; setFloat2(uniformName: string, x: number, y: number): Effect; setVector3(uniformName: string, vector3: Vector3): Effect; setFloat3(uniformName: string, x: number, y: number, z: number): Effect; + setVector4(uniformName: string, vector4: Vector4): Effect; setFloat4(uniformName: string, x: number, y: number, z: number, w: number): Effect; setColor3(uniformName: string, color3: Color3): Effect; setColor4(uniformName: string, color3: Color3, alpha: number): Effect; static ShadersStore: {}; } } + declare module BABYLON { class Material { name: string; @@ -2593,10 +2553,13 @@ declare module BABYLON { onDispose: () => void; onBind: (material: Material, mesh: Mesh) => void; getRenderTargetTextures: () => SmartArray; + alphaMode: number; + disableDepthWrite: boolean; _effect: Effect; _wasPreviouslyReady: boolean; private _scene; private _fillMode; + private _cachedDepthWriteState; pointSize: number; zOffset: number; wireframe: boolean; @@ -2614,9 +2577,11 @@ declare module BABYLON { bind(world: Matrix, mesh?: Mesh): void; bindOnlyWorldMatrix(world: Matrix): void; unbind(): void; + clone(name: string): Material; dispose(forceDisposeEffect?: boolean): void; } } + declare module BABYLON { class MultiMaterial extends Material { subMaterials: Material[]; @@ -2626,6 +2591,7 @@ declare module BABYLON { clone(name: string): MultiMaterial; } } + declare module BABYLON { class ShaderMaterial extends Material { private _shaderPath; @@ -2637,7 +2603,10 @@ declare module BABYLON { private _colors4; private _vectors2; private _vectors3; + private _vectors4; private _matrices; + private _matrices3x3; + private _matrices2x2; private _cachedWorldViewMatrix; private _renderId; constructor(name: string, scene: Scene, shaderPath: any, options: any); @@ -2651,13 +2620,18 @@ declare module BABYLON { setColor4(name: string, value: Color4): ShaderMaterial; setVector2(name: string, value: Vector2): ShaderMaterial; setVector3(name: string, value: Vector3): ShaderMaterial; + setVector4(name: string, value: Vector4): ShaderMaterial; setMatrix(name: string, value: Matrix): ShaderMaterial; + setMatrix3x3(name: string, value: Float32Array): ShaderMaterial; + setMatrix2x2(name: string, value: Float32Array): ShaderMaterial; isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean; bindOnlyWorldMatrix(world: Matrix): void; bind(world: Matrix, mesh?: Mesh): void; + clone(name: string): ShaderMaterial; dispose(forceDisposeEffect?: boolean): void; } } + declare module BABYLON { class FresnelParameters { isEnabled: boolean; @@ -2680,19 +2654,24 @@ declare module BABYLON { specularPower: number; emissiveColor: Color3; useAlphaFromDiffuseTexture: boolean; + useEmissiveAsIllumination: boolean; + useReflectionFresnelFromSpecular: boolean; useSpecularOverAlpha: boolean; fogEnabled: boolean; + roughness: number; diffuseFresnelParameters: FresnelParameters; opacityFresnelParameters: FresnelParameters; reflectionFresnelParameters: FresnelParameters; emissiveFresnelParameters: FresnelParameters; - private _cachedDefines; + useGlossinessFromSpecularMapAlpha: boolean; private _renderTargets; private _worldViewProjectionMatrix; private _globalAmbientColor; private _scaledDiffuse; private _scaledSpecular; private _renderId; + private _defines; + private _cachedDefines; constructor(name: string, scene: Scene); needAlphaBlending(): boolean; needAlphaTesting(): boolean; @@ -2715,424 +2694,112 @@ declare module BABYLON { static FresnelEnabled: boolean; } } + declare module BABYLON { - class BaseTexture { - name: string; - delayLoadState: number; - hasAlpha: boolean; - getAlphaFromRGB: boolean; - level: number; - isCube: boolean; - isRenderTarget: boolean; - animations: Animation[]; - onDispose: () => void; - coordinatesIndex: number; - coordinatesMode: number; - wrapU: number; - wrapV: number; - anisotropicFilteringLevel: number; - _cachedAnisotropicFilteringLevel: number; - private _scene; - _texture: WebGLTexture; - constructor(scene: Scene); - getScene(): Scene; - getTextureMatrix(): Matrix; - getReflectionTextureMatrix(): Matrix; - getInternalTexture(): WebGLTexture; - isReady(): boolean; - getSize(): ISize; - getBaseSize(): ISize; - scale(ratio: number): void; - canRescale: boolean; - _removeFromCache(url: string, noMipmap: boolean): void; - _getFromCache(url: string, noMipmap: boolean, sampling?: number): WebGLTexture; - delayLoad(): void; - releaseInternalTexture(): void; - clone(): BaseTexture; - dispose(): void; - } -} -declare module BABYLON { - class CubeTexture extends BaseTexture { - url: string; - coordinatesMode: number; - private _noMipmap; - private _extensions; - private _textureMatrix; - constructor(rootUrl: string, scene: Scene, extensions?: string[], noMipmap?: boolean); - clone(): CubeTexture; - delayLoad(): void; - getReflectionTextureMatrix(): Matrix; - } -} -declare module BABYLON { - class DynamicTexture extends Texture { - private _generateMipMaps; - private _canvas; - private _context; - constructor(name: string, options: any, scene: Scene, generateMipMaps: boolean, samplingMode?: number); - canRescale: boolean; - scale(ratio: number): void; - getContext(): CanvasRenderingContext2D; - clear(): void; - update(invertY?: boolean): void; - drawText(text: string, x: number, y: number, font: string, color: string, clearColor: string, invertY?: boolean, update?: boolean): void; - clone(): DynamicTexture; - } -} -declare module BABYLON { - class MirrorTexture extends RenderTargetTexture { - mirrorPlane: Plane; - private _transformMatrix; - private _mirrorMatrix; - private _savedViewMatrix; - constructor(name: string, size: number, scene: Scene, generateMipMaps?: boolean); - clone(): MirrorTexture; + class Color3 { + r: number; + g: number; + b: number; + constructor(r?: number, g?: number, b?: number); + toString(): string; + toArray(array: number[], index?: number): Color3; + toColor4(alpha?: number): Color4; + asArray(): number[]; + toLuminance(): number; + multiply(otherColor: Color3): Color3; + multiplyToRef(otherColor: Color3, result: Color3): Color3; + equals(otherColor: Color3): boolean; + equalsFloats(r: number, g: number, b: number): boolean; + scale(scale: number): Color3; + scaleToRef(scale: number, result: Color3): Color3; + add(otherColor: Color3): Color3; + addToRef(otherColor: Color3, result: Color3): Color3; + subtract(otherColor: Color3): Color3; + subtractToRef(otherColor: Color3, result: Color3): Color3; + clone(): Color3; + copyFrom(source: Color3): Color3; + copyFromFloats(r: number, g: number, b: number): Color3; + toHexString(): string; + static FromHexString(hex: string): Color3; + static FromArray(array: number[], offset?: number): Color3; + static FromInts(r: number, g: number, b: number): Color3; + static Lerp(start: Color3, end: Color3, amount: number): Color3; + static Red(): Color3; + static Green(): Color3; + static Blue(): Color3; + static Black(): Color3; + static White(): Color3; + static Purple(): Color3; + static Magenta(): Color3; + static Yellow(): Color3; + static Gray(): Color3; } -} -declare module BABYLON { - class RawTexture extends Texture { - constructor(data: ArrayBufferView, width: number, height: number, format: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number); - static CreateLuminanceTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; - static CreateLuminanceAlphaTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; - static CreateAlphaTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; - static CreateRGBTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; - static CreateRGBATexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; + class Color4 { + r: number; + g: number; + b: number; + a: number; + constructor(r: number, g: number, b: number, a: number); + addInPlace(right: any): Color4; + asArray(): number[]; + toArray(array: number[], index?: number): Color4; + add(right: Color4): Color4; + subtract(right: Color4): Color4; + subtractToRef(right: Color4, result: Color4): Color4; + scale(scale: number): Color4; + scaleToRef(scale: number, result: Color4): Color4; + toString(): string; + clone(): Color4; + copyFrom(source: Color4): Color4; + toHexString(): string; + static FromHexString(hex: string): Color4; + static Lerp(left: Color4, right: Color4, amount: number): Color4; + static LerpToRef(left: Color4, right: Color4, amount: number, result: Color4): void; + static FromArray(array: number[], offset?: number): Color4; + static FromInts(r: number, g: number, b: number, a: number): Color4; } -} -declare module BABYLON { - class RenderTargetTexture extends Texture { - renderList: AbstractMesh[]; - renderParticles: boolean; - renderSprites: boolean; - coordinatesMode: number; - onBeforeRender: () => void; - onAfterRender: () => void; - onAfterUnbind: () => void; - onClear: (engine: Engine) => void; - activeCamera: Camera; - customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray, beforeTransparents?: () => void) => void; - private _size; - _generateMipMaps: boolean; - private _renderingManager; - _waitingRenderList: string[]; - private _doNotChangeAspectRatio; - private _currentRefreshId; - private _refreshRate; - constructor(name: string, size: any, scene: Scene, generateMipMaps?: boolean, doNotChangeAspectRatio?: boolean, type?: number); - resetRefreshCounter(): void; - refreshRate: number; - _shouldRender(): boolean; - isReady(): boolean; - getRenderSize(): number; - canRescale: boolean; - scale(ratio: number): void; - resize(size: any, generateMipMaps?: boolean): void; - render(useCameraPostProcess?: boolean, dumpForDebug?: boolean): void; - clone(): RenderTargetTexture; - } -} -declare module BABYLON { - class Texture extends BaseTexture { - static NEAREST_SAMPLINGMODE: number; - static BILINEAR_SAMPLINGMODE: number; - static TRILINEAR_SAMPLINGMODE: number; - static EXPLICIT_MODE: number; - static SPHERICAL_MODE: number; - static PLANAR_MODE: number; - static CUBIC_MODE: number; - static PROJECTION_MODE: number; - static SKYBOX_MODE: number; - static CLAMP_ADDRESSMODE: number; - static WRAP_ADDRESSMODE: number; - static MIRROR_ADDRESSMODE: number; - url: string; - uOffset: number; - vOffset: number; - uScale: number; - vScale: number; - uAng: number; - vAng: number; - wAng: number; - private _noMipmap; - _invertY: boolean; - private _rowGenerationMatrix; - private _cachedTextureMatrix; - private _projectionModeMatrix; - private _t0; - private _t1; - private _t2; - private _cachedUOffset; - private _cachedVOffset; - private _cachedUScale; - private _cachedVScale; - private _cachedUAng; - private _cachedVAng; - private _cachedWAng; - private _cachedCoordinatesMode; - _samplingMode: number; - private _buffer; - private _deleteBuffer; - constructor(url: string, scene: Scene, noMipmap?: boolean, invertY?: boolean, samplingMode?: number, onLoad?: () => void, onError?: () => void, buffer?: any, deleteBuffer?: boolean); - delayLoad(): void; - updateSamplingMode(samplingMode: number): void; - private _prepareRowForTextureGeneration(x, y, z, t); - getTextureMatrix(): Matrix; - getReflectionTextureMatrix(): Matrix; - clone(): Texture; - static CreateFromBase64String(data: string, name: string, scene: Scene, noMipmap?: boolean, invertY?: boolean, samplingMode?: number, onLoad?: () => void, onError?: () => void): Texture; - } -} -declare module BABYLON { - class VideoTexture extends Texture { - video: HTMLVideoElement; - private _autoLaunch; - private _lastUpdate; - constructor(name: string, urls: string[], scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number); - update(): boolean; - } -} -declare module BABYLON { - class CustomProceduralTexture extends ProceduralTexture { - private _animate; - private _time; - private _config; - private _texturePath; - constructor(name: string, texturePath: any, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - private loadJson(jsonUrl); - isReady(): boolean; - render(useCameraPostProcess?: boolean): void; - updateTextures(): void; - updateShaderUniforms(): void; - animate: boolean; - } -} -declare module BABYLON { - class ProceduralTexture extends Texture { - private _size; - _generateMipMaps: boolean; - private _doNotChangeAspectRatio; - private _currentRefreshId; - private _refreshRate; - private _vertexBuffer; - private _indexBuffer; - private _effect; - private _vertexDeclaration; - private _vertexStrideSize; - private _uniforms; - private _samplers; - private _fragment; - _textures: Texture[]; - private _floats; - private _floatsArrays; - private _colors3; - private _colors4; - private _vectors2; - private _vectors3; - private _matrices; - private _fallbackTexture; - private _fallbackTextureUsed; - constructor(name: string, size: any, fragment: any, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - reset(): void; - isReady(): boolean; - resetRefreshCounter(): void; - setFragment(fragment: any): void; - refreshRate: number; - _shouldRender(): boolean; - getRenderSize(): number; - resize(size: any, generateMipMaps: any): void; - private _checkUniform(uniformName); - setTexture(name: string, texture: Texture): ProceduralTexture; - setFloat(name: string, value: number): ProceduralTexture; - setFloats(name: string, value: number[]): ProceduralTexture; - setColor3(name: string, value: Color3): ProceduralTexture; - setColor4(name: string, value: Color4): ProceduralTexture; - setVector2(name: string, value: Vector2): ProceduralTexture; - setVector3(name: string, value: Vector3): ProceduralTexture; - setMatrix(name: string, value: Matrix): ProceduralTexture; - render(useCameraPostProcess?: boolean): void; - clone(): ProceduralTexture; - dispose(): void; - } -} -declare module BABYLON { - class WoodProceduralTexture extends ProceduralTexture { - private _ampScale; - private _woodColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - ampScale: number; - woodColor: Color3; - } - class FireProceduralTexture extends ProceduralTexture { - private _time; - private _speed; - private _autoGenerateTime; - private _fireColors; - private _alphaThreshold; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - render(useCameraPostProcess?: boolean): void; - static PurpleFireColors: Color3[]; - static GreenFireColors: Color3[]; - static RedFireColors: Color3[]; - static BlueFireColors: Color3[]; - fireColors: Color3[]; - time: number; - speed: Vector2; - alphaThreshold: number; - } - class CloudProceduralTexture extends ProceduralTexture { - private _skyColor; - private _cloudColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - skyColor: Color3; - cloudColor: Color3; - } - class GrassProceduralTexture extends ProceduralTexture { - private _grassColors; - private _herb1; - private _herb2; - private _herb3; - private _groundColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - grassColors: Color3[]; - groundColor: Color3; - } - class RoadProceduralTexture extends ProceduralTexture { - private _roadColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - roadColor: Color3; - } - class BrickProceduralTexture extends ProceduralTexture { - private _numberOfBricksHeight; - private _numberOfBricksWidth; - private _jointColor; - private _brickColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - numberOfBricksHeight: number; - numberOfBricksWidth: number; - jointColor: Color3; - brickColor: Color3; - } - class MarbleProceduralTexture extends ProceduralTexture { - private _numberOfTilesHeight; - private _numberOfTilesWidth; - private _amplitude; - private _marbleColor; - private _jointColor; - constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); - updateShaderUniforms(): void; - numberOfTilesHeight: number; - numberOfTilesWidth: number; - jointColor: Color3; - marbleColor: Color3; - } -} -declare module BABYLON { - class Color3 { - r: number; - g: number; - b: number; - constructor(r?: number, g?: number, b?: number); - toString(): string; - toArray(array: number[], index?: number): Color3; - toColor4(alpha?: number): Color4; - asArray(): number[]; - toLuminance(): number; - multiply(otherColor: Color3): Color3; - multiplyToRef(otherColor: Color3, result: Color3): Color3; - equals(otherColor: Color3): boolean; - scale(scale: number): Color3; - scaleToRef(scale: number, result: Color3): Color3; - add(otherColor: Color3): Color3; - addToRef(otherColor: Color3, result: Color3): Color3; - subtract(otherColor: Color3): Color3; - subtractToRef(otherColor: Color3, result: Color3): Color3; - clone(): Color3; - copyFrom(source: Color3): Color3; - copyFromFloats(r: number, g: number, b: number): Color3; - static FromArray(array: number[], offset?: number): Color3; - static FromInts(r: number, g: number, b: number): Color3; - static Lerp(start: Color3, end: Color3, amount: number): Color3; - static Red(): Color3; - static Green(): Color3; - static Blue(): Color3; - static Black(): Color3; - static White(): Color3; - static Purple(): Color3; - static Magenta(): Color3; - static Yellow(): Color3; - static Gray(): Color3; - } - class Color4 { - r: number; - g: number; - b: number; - a: number; - constructor(r: number, g: number, b: number, a: number); - addInPlace(right: any): Color4; - asArray(): number[]; - toArray(array: number[], index?: number): Color4; - add(right: Color4): Color4; - subtract(right: Color4): Color4; - subtractToRef(right: Color4, result: Color4): Color4; - scale(scale: number): Color4; - scaleToRef(scale: number, result: Color4): Color4; - toString(): string; - clone(): Color4; - copyFrom(source: Color4): Color4; - static Lerp(left: Color4, right: Color4, amount: number): Color4; - static LerpToRef(left: Color4, right: Color4, amount: number, result: Color4): void; - static FromArray(array: number[], offset?: number): Color4; - static FromInts(r: number, g: number, b: number, a: number): Color4; - } - class Vector2 { - x: number; - y: number; - constructor(x: number, y: number); - toString(): string; - toArray(array: number[], index?: number): Vector2; - asArray(): number[]; - copyFrom(source: Vector2): Vector2; - copyFromFloats(x: number, y: number): Vector2; - add(otherVector: Vector2): Vector2; - addVector3(otherVector: Vector3): Vector2; - subtract(otherVector: Vector2): Vector2; - subtractInPlace(otherVector: Vector2): Vector2; - multiplyInPlace(otherVector: Vector2): Vector2; - multiply(otherVector: Vector2): Vector2; - multiplyToRef(otherVector: Vector2, result: Vector2): Vector2; - multiplyByFloats(x: number, y: number): Vector2; - divide(otherVector: Vector2): Vector2; - divideToRef(otherVector: Vector2, result: Vector2): Vector2; - negate(): Vector2; - scaleInPlace(scale: number): Vector2; - scale(scale: number): Vector2; - equals(otherVector: Vector2): boolean; - equalsWithEpsilon(otherVector: Vector2): boolean; - length(): number; - lengthSquared(): number; - normalize(): Vector2; - clone(): Vector2; - static Zero(): Vector2; - static FromArray(array: number[], offset?: number): Vector2; - static FromArrayToRef(array: number[], offset: number, result: Vector2): void; - static CatmullRom(value1: Vector2, value2: Vector2, value3: Vector2, value4: Vector2, amount: number): Vector2; - static Clamp(value: Vector2, min: Vector2, max: Vector2): Vector2; - static Hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number): Vector2; - static Lerp(start: Vector2, end: Vector2, amount: number): Vector2; - static Dot(left: Vector2, right: Vector2): number; - static Normalize(vector: Vector2): Vector2; - static Minimize(left: Vector2, right: Vector2): Vector2; - static Maximize(left: Vector2, right: Vector2): Vector2; - static Transform(vector: Vector2, transformation: Matrix): Vector2; - static Distance(value1: Vector2, value2: Vector2): number; - static DistanceSquared(value1: Vector2, value2: Vector2): number; + class Vector2 { + x: number; + y: number; + constructor(x: number, y: number); + toString(): string; + toArray(array: number[], index?: number): Vector2; + asArray(): number[]; + copyFrom(source: Vector2): Vector2; + copyFromFloats(x: number, y: number): Vector2; + add(otherVector: Vector2): Vector2; + addVector3(otherVector: Vector3): Vector2; + subtract(otherVector: Vector2): Vector2; + subtractInPlace(otherVector: Vector2): Vector2; + multiplyInPlace(otherVector: Vector2): Vector2; + multiply(otherVector: Vector2): Vector2; + multiplyToRef(otherVector: Vector2, result: Vector2): Vector2; + multiplyByFloats(x: number, y: number): Vector2; + divide(otherVector: Vector2): Vector2; + divideToRef(otherVector: Vector2, result: Vector2): Vector2; + negate(): Vector2; + scaleInPlace(scale: number): Vector2; + scale(scale: number): Vector2; + equals(otherVector: Vector2): boolean; + equalsWithEpsilon(otherVector: Vector2, epsilon?: number): boolean; + length(): number; + lengthSquared(): number; + normalize(): Vector2; + clone(): Vector2; + static Zero(): Vector2; + static FromArray(array: number[], offset?: number): Vector2; + static FromArrayToRef(array: number[], offset: number, result: Vector2): void; + static CatmullRom(value1: Vector2, value2: Vector2, value3: Vector2, value4: Vector2, amount: number): Vector2; + static Clamp(value: Vector2, min: Vector2, max: Vector2): Vector2; + static Hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number): Vector2; + static Lerp(start: Vector2, end: Vector2, amount: number): Vector2; + static Dot(left: Vector2, right: Vector2): number; + static Normalize(vector: Vector2): Vector2; + static Minimize(left: Vector2, right: Vector2): Vector2; + static Maximize(left: Vector2, right: Vector2): Vector2; + static Transform(vector: Vector2, transformation: Matrix): Vector2; + static Distance(value1: Vector2, value2: Vector2): number; + static DistanceSquared(value1: Vector2, value2: Vector2): number; } class Vector3 { x: number; @@ -3156,7 +2823,7 @@ declare module BABYLON { scale(scale: number): Vector3; scaleToRef(scale: number, result: Vector3): void; equals(otherVector: Vector3): boolean; - equalsWithEpsilon(otherVector: Vector3): boolean; + equalsWithEpsilon(otherVector: Vector3, epsilon?: number): boolean; equalsToFloats(x: number, y: number, z: number): boolean; multiplyInPlace(otherVector: Vector3): Vector3; multiply(otherVector: Vector3): Vector3; @@ -3174,6 +2841,7 @@ declare module BABYLON { copyFromFloats(x: number, y: number, z: number): Vector3; static GetClipFactor(vector0: Vector3, vector1: Vector3, axis: Vector3, size: any): number; static FromArray(array: number[], offset?: number): Vector3; + static FromFloatArray(array: Float32Array, offset?: number): Vector3; static FromArrayToRef(array: number[], offset: number, result: Vector3): void; static FromFloatArrayToRef(array: Float32Array, offset: number, result: Vector3): void; static FromFloatsToRef(x: number, y: number, z: number, result: Vector3): void; @@ -3210,6 +2878,10 @@ declare module BABYLON { * to something in order to rotate it from its local system to the given target system. */ static RotationFromAxis(axis1: Vector3, axis2: Vector3, axis3: Vector3): Vector3; + /** + * The same than RotationFromAxis but updates the passed ref Vector3 parameter. + */ + static RotationFromAxisToRef(axis1: Vector3, axis2: Vector3, axis3: Vector3, ref: Vector3): void; } class Vector4 { x: number; @@ -3233,7 +2905,7 @@ declare module BABYLON { scale(scale: number): Vector4; scaleToRef(scale: number, result: Vector4): void; equals(otherVector: Vector4): boolean; - equalsWithEpsilon(otherVector: Vector4): boolean; + equalsWithEpsilon(otherVector: Vector4, epsilon?: number): boolean; equalsToFloats(x: number, y: number, z: number, w: number): boolean; multiplyInPlace(otherVector: Vector4): Vector4; multiply(otherVector: Vector4): Vector4; @@ -3308,6 +2980,10 @@ declare module BABYLON { toArray(): Float32Array; asArray(): Float32Array; invert(): Matrix; + reset(): Matrix; + add(other: Matrix): Matrix; + addToRef(other: Matrix, result: Matrix): Matrix; + addToSelf(other: Matrix): Matrix; invertToRef(other: Matrix): Matrix; invertToRefSIMD(other: Matrix): Matrix; setTranslation(vector3: Vector3): Matrix; @@ -3322,6 +2998,7 @@ declare module BABYLON { decompose(scale: Vector3, rotation: Quaternion, translation: Vector3): boolean; static FromArray(array: number[], offset?: number): Matrix; static FromArrayToRef(array: number[], offset: number, result: Matrix): void; + static FromFloat32ArrayToRefScaled(array: Float32Array, offset: number, scale: number, result: Matrix): void; static FromValuesToRef(initialM11: number, initialM12: number, initialM13: number, initialM14: number, initialM21: number, initialM22: number, initialM23: number, initialM24: number, initialM31: number, initialM32: number, initialM33: number, initialM34: number, initialM41: number, initialM42: number, initialM43: number, initialM44: number, result: Matrix): void; static FromValues(initialM11: number, initialM12: number, initialM13: number, initialM14: number, initialM21: number, initialM22: number, initialM23: number, initialM24: number, initialM31: number, initialM32: number, initialM33: number, initialM34: number, initialM41: number, initialM42: number, initialM43: number, initialM44: number): Matrix; static Compose(scale: Vector3, rotation: Quaternion, translation: Vector3): Matrix; @@ -3353,6 +3030,8 @@ declare module BABYLON { static PerspectiveFovLH(fov: number, aspect: number, znear: number, zfar: number): Matrix; static PerspectiveFovLHToRef(fov: number, aspect: number, znear: number, zfar: number, result: Matrix, fovMode?: number): void; static GetFinalMatrix(viewport: Viewport, world: Matrix, view: Matrix, projection: Matrix, zmin: number, zmax: number): Matrix; + static GetAsMatrix2x2(matrix: Matrix): Float32Array; + static GetAsMatrix3x3(matrix: Matrix): Float32Array; static Transpose(matrix: Matrix): Matrix; static Reflection(plane: Plane): Matrix; static ReflectionToRef(plane: Plane, result: Matrix): void; @@ -3482,7 +3161,14 @@ declare module BABYLON { private _tangents; private _normals; private _binormals; - constructor(path: Vector3[], firstNormal?: Vector3); + private _raw; + /** + * new Path3D(path, normal, raw) + * path : an array of Vector3, the curve axis of the Path3D + * normal (optional) : Vector3, the first wanted normal to the curve. Ex (0, 1, 0) for a vertical normal. + * raw (optional, default false) : boolean, if true the returned Path3D isn't normalized. Useful to depict path acceleration or speed. + */ + constructor(path: Vector3[], firstNormal?: Vector3, raw?: boolean); getCurve(): Vector3[]; getTangents(): Vector3[]; getNormals(): Vector3[]; @@ -3506,26 +3192,171 @@ declare module BABYLON { continue(curve: Curve3): Curve3; private _computeLength(path); } - class PositionNormalVertex { - position: Vector3; - normal: Vector3; - constructor(position?: Vector3, normal?: Vector3); - clone(): PositionNormalVertex; + class PositionNormalVertex { + position: Vector3; + normal: Vector3; + constructor(position?: Vector3, normal?: Vector3); + clone(): PositionNormalVertex; + } + class PositionNormalTextureVertex { + position: Vector3; + normal: Vector3; + uv: Vector2; + constructor(position?: Vector3, normal?: Vector3, uv?: Vector2); + clone(): PositionNormalTextureVertex; + } + class SIMDHelper { + private static _isEnabled; + static IsEnabled: boolean; + static DisableSIMD(): void; + static EnableSIMD(): void; + } +} + +declare module BABYLON { + class Particle { + position: Vector3; + direction: Vector3; + color: Color4; + colorStep: Color4; + lifeTime: number; + age: number; + size: number; + angle: number; + angularSpeed: number; + copyTo(other: Particle): void; + } +} + +declare module BABYLON { + class ParticleSystem implements IDisposable { + name: string; + static BLENDMODE_ONEONE: number; + static BLENDMODE_STANDARD: number; + id: string; + renderingGroupId: number; + emitter: any; + emitRate: number; + manualEmitCount: number; + updateSpeed: number; + targetStopDuration: number; + disposeOnStop: boolean; + minEmitPower: number; + maxEmitPower: number; + minLifeTime: number; + maxLifeTime: number; + minSize: number; + maxSize: number; + minAngularSpeed: number; + maxAngularSpeed: number; + particleTexture: Texture; + layerMask: number; + onDispose: () => void; + updateFunction: (particles: Particle[]) => void; + blendMode: number; + forceDepthWrite: boolean; + gravity: Vector3; + direction1: Vector3; + direction2: Vector3; + minEmitBox: Vector3; + maxEmitBox: Vector3; + color1: Color4; + color2: Color4; + colorDead: Color4; + textureMask: Color4; + startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3) => void; + startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3) => void; + private particles; + private _capacity; + private _scene; + private _vertexDeclaration; + private _vertexStrideSize; + private _stockParticles; + private _newPartsExcess; + private _vertexBuffer; + private _indexBuffer; + private _vertices; + private _effect; + private _customEffect; + private _cachedDefines; + private _scaledColorStep; + private _colorDiff; + private _scaledDirection; + private _scaledGravity; + private _currentRenderId; + private _alive; + private _started; + private _stopped; + private _actualFrame; + private _scaledUpdateSpeed; + constructor(name: string, capacity: number, scene: Scene, customEffect?: Effect); + recycleParticle(particle: Particle): void; + getCapacity(): number; + isAlive(): boolean; + isStarted(): boolean; + start(): void; + stop(): void; + _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void; + private _update(newParticles); + private _getEffect(); + animate(): void; + render(): number; + dispose(): void; + clone(name: string, newEmitter: any): ParticleSystem; + } +} + +declare module BABYLON { + interface IPhysicsEnginePlugin { + initialize(iterations?: number): any; + setGravity(gravity: Vector3): void; + runOneStep(delta: number): void; + registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; + registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; + unregisterMesh(mesh: AbstractMesh): any; + applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; + createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; + dispose(): void; + isSupported(): boolean; + updateBodyPosition(mesh: AbstractMesh): void; + } + interface PhysicsBodyCreationOptions { + mass: number; + friction: number; + restitution: number; } - class PositionNormalTextureVertex { - position: Vector3; - normal: Vector3; - uv: Vector2; - constructor(position?: Vector3, normal?: Vector3, uv?: Vector2); - clone(): PositionNormalTextureVertex; + interface PhysicsCompoundBodyPart { + mesh: Mesh; + impostor: number; } - class SIMDHelper { - private static _isEnabled; - static IsEnabled: boolean; - static DisableSIMD(): void; - static EnableSIMD(): void; + class PhysicsEngine { + gravity: Vector3; + private _currentPlugin; + constructor(plugin?: IPhysicsEnginePlugin); + _initialize(gravity?: Vector3): void; + _runOneStep(delta: number): void; + _setGravity(gravity: Vector3): void; + _registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; + _registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; + _unregisterMesh(mesh: AbstractMesh): void; + _applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; + _createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; + _updateBodyPosition(mesh: AbstractMesh): void; + dispose(): void; + isSupported(): boolean; + static NoImpostor: number; + static SphereImpostor: number; + static BoxImpostor: number; + static PlaneImpostor: number; + static MeshImpostor: number; + static CapsuleImpostor: number; + static ConeImpostor: number; + static CylinderImpostor: number; + static ConvexHullImpostor: number; + static Epsilon: number; } } + declare module BABYLON { class AbstractMesh extends Node implements IDisposable { private static _BILLBOARDMODE_NONE; @@ -3567,6 +3398,7 @@ declare module BABYLON { hasVertexAlpha: boolean; useVertexColors: boolean; applyFog: boolean; + computeBonesUsingShaders: boolean; useOctreeForRenderingSelection: boolean; useOctreeForPicking: boolean; useOctreeForCollisions: boolean; @@ -3583,12 +3415,18 @@ declare module BABYLON { private _oldPositionForCollisions; private _diffPositionForCollisions; private _newPositionForCollisions; + onCollide: (collidedMesh: AbstractMesh) => void; + private _meshToBoneReferal; + edgesWidth: number; + edgesColor: Color4; + _edgesRenderer: EdgesRenderer; private _localScaling; private _localRotation; private _localTranslation; private _localBillboard; private _localPivotScaling; private _localPivotScalingRotation; + private _localMeshReferalTransform; private _localWorld; _worldMatrix: Matrix; private _rotateYByPI; @@ -3608,7 +3446,10 @@ declare module BABYLON { private _onAfterWorldMatrixUpdate; private _isWorldMatrixFrozen; _waitingActions: any; + _waitingFreezeWorldMatrix: boolean; constructor(name: string, scene: Scene); + disableEdgesRendering(): void; + enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): void; isBlocked: boolean; getLOD(camera: Camera): AbstractMesh; getTotalVertices(): number; @@ -3681,6 +3522,8 @@ declare module BABYLON { getPositionExpressedInLocalSpace(): Vector3; locallyTranslate(vector3: Vector3): void; lookAt(targetPoint: Vector3, yawCor: number, pitchCor: number, rollCor: number): void; + attachToBone(bone: Bone, affectedMesh: AbstractMesh): void; + detachFromBone(): void; isInFrustum(frustumPlanes: Plane[]): boolean; isCompletelyInFrustum(camera?: Camera): boolean; intersectsMesh(mesh: AbstractMesh, precise?: boolean): boolean; @@ -3713,6 +3556,7 @@ declare module BABYLON { dispose(doNotRecurse?: boolean): void; } } + declare module BABYLON { class CSG { private polygons; @@ -3738,6 +3582,7 @@ declare module BABYLON { toMesh(name: string, material: Material, scene: Scene, keepSubMeshes: boolean): Mesh; } } + declare module BABYLON { class Geometry implements IGetSetVerticesData { id: string; @@ -3894,6 +3739,7 @@ declare module BABYLON { } } } + declare module BABYLON { class GroundMesh extends Mesh { generateOctree: boolean; @@ -3901,10 +3747,11 @@ declare module BABYLON { _subdivisions: number; constructor(name: string, scene: Scene); subdivisions: number; - optimize(chunksCount: number): void; + optimize(chunksCount: number, octreeBlocksSize?: number): void; getHeightAtCoordinates(x: number, z: number): number; } } + declare module BABYLON { /** * Creates an instance based on a source mesh. @@ -3933,15 +3780,13 @@ declare module BABYLON { dispose(doNotRecurse?: boolean): void; } } + declare module BABYLON { class LinesMesh extends Mesh { color: Color3; alpha: number; private _colorShader; - private _ib; - private _indicesLength; - private _indices; - constructor(name: string, scene: Scene, updatable?: boolean); + constructor(name: string, scene: Scene, parent?: Node, source?: Mesh, doNotCloneChildren?: boolean); material: Material; isPickable: boolean; checkCollisions: boolean; @@ -3949,8 +3794,10 @@ declare module BABYLON { _draw(subMesh: SubMesh, fillMode: number, instancesCount?: number): void; intersects(ray: Ray, fastCheck?: boolean): any; dispose(doNotRecurse?: boolean): void; + clone(name: string, newParent?: Node, doNotCloneChildren?: boolean): LinesMesh; } } + declare module BABYLON { class _InstancesBatch { mustReturn: boolean; @@ -3995,6 +3842,8 @@ declare module BABYLON { private _preActivateId; private _sideOrientation; private _areNormalsFrozen; + private _sourcePositions; + private _sourceNormals; /** * @constructor * @param {string} name - The value used by scene.getMeshByName() to do a lookup. @@ -4060,7 +3909,7 @@ declare module BABYLON { _getInstancesRenderList(subMeshId: number): _InstancesBatch; _renderWithInstances(subMesh: SubMesh, fillMode: number, batch: _InstancesBatch, effect: Effect, engine: Engine): void; _processRendering(subMesh: SubMesh, effect: Effect, fillMode: number, batch: _InstancesBatch, hardwareInstancedRendering: boolean, onBeforeDraw: (isInstance: boolean, world: Matrix) => void): void; - render(subMesh: SubMesh): void; + render(subMesh: SubMesh, enableAlphaMode: boolean): void; getEmittedParticleSystems(): ParticleSystem[]; getHierarchyEmittedParticleSystems(): ParticleSystem[]; getChildren(): Node[]; @@ -4069,6 +3918,7 @@ declare module BABYLON { setMaterialByID(id: string): void; getAnimatables(): IAnimatable[]; bakeTransformIntoVertices(transform: Matrix): void; + bakeCurrentTransformIntoVertices(): void; _resetPointsArrayCache(): void; _generatePointsArray(): boolean; clone(name: string, newParent?: Node, doNotCloneChildren?: boolean): Mesh; @@ -4076,6 +3926,7 @@ declare module BABYLON { applyDisplacementMap(url: string, minHeight: number, maxHeight: number, onSuccess?: (mesh: Mesh) => void): void; applyDisplacementMapFromBuffer(buffer: Uint8Array, heightMapWidth: number, heightMapHeight: number, minHeight: number, maxHeight: number): void; convertToFlatShadedMesh(): void; + flipFaces(flipNormals?: boolean): void; createInstance(name: string): InstancedMesh; synchronizeInstances(): void; /** @@ -4097,7 +3948,24 @@ declare module BABYLON { static CreateRibbon(name: string, pathArray: Vector3[][], closeArray: boolean, closePath: boolean, offset: number, scene: Scene, updatable?: boolean, sideOrientation?: number, ribbonInstance?: Mesh): Mesh; static CreateDisc(name: string, radius: number, tessellation: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; static CreateBox(name: string, size: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; - static CreateSphere(name: string, segments: number, diameter: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; + static CreateBox(name: string, options: { + width?: number; + height?: number; + depth?: number; + faceUV?: Vector4[]; + faceColors?: Color4[]; + sideOrientation?: number; + updatable?: boolean; + }, scene: Scene): Mesh; + static CreateSphere(name: string, segments: number, diameter: number, scene?: Scene, updatable?: boolean, sideOrientation?: number): Mesh; + static CreateSphere(name: string, options: { + segments?: number; + diameterX?: number; + diameterY?: number; + diameterZ?: number; + sideOrientation?: number; + updatable?: boolean; + }, scene: any): Mesh; static CreateCylinder(name: string, height: number, diameterTop: number, diameterBottom: number, tessellation: number, subdivisions: any, scene: Scene, updatable?: any, sideOrientation?: number): Mesh; static CreateTorus(name: string, diameter: number, thickness: number, tessellation: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; static CreateTorusKnot(name: string, radius: number, tube: number, radialSegments: number, tubularSegments: number, p: number, q: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; @@ -4108,7 +3976,20 @@ declare module BABYLON { private static _ExtrudeShapeGeneric(name, shape, curve, scale, rotation, scaleFunction, rotateFunction, rbCA, rbCP, cap, custom, scene, updtbl, side, instance); static CreateLathe(name: string, shape: Vector3[], radius: number, tessellation: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; static CreatePlane(name: string, size: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh; + static CreatePlane(name: string, options: { + width?: number; + height?: number; + sideOrientation?: number; + updatable?: boolean; + }, scene: Scene): Mesh; static CreateGround(name: string, width: number, height: number, subdivisions: number, scene: Scene, updatable?: boolean): Mesh; + static CreateGround(name: string, options: { + width?: number; + height?: number; + subdivisions?: number; + sideOrientation?: number; + updatable?: boolean; + }, scene: any): Mesh; static CreateTiledGround(name: string, xmin: number, zmin: number, xmax: number, zmax: number, subdivisions: { w: number; h: number; @@ -4121,6 +4002,11 @@ declare module BABYLON { (i: number, distance: number): number; }, cap: number, scene: Scene, updatable?: boolean, sideOrientation?: number, tubeInstance?: Mesh): Mesh; static CreateDecal(name: string, sourceMesh: AbstractMesh, position: Vector3, normal: Vector3, size: Vector3, angle?: number): Mesh; + /** + * Update the vertex buffers by applying transformation from the bones + * @param {skeleton} skeleton to apply + */ + applySkeleton(skeleton: Skeleton): Mesh; static MinMax(meshes: AbstractMesh[]): { min: Vector3; max: Vector3; @@ -4136,6 +4022,7 @@ declare module BABYLON { static MergeMeshes(meshes: Array, disposeSource?: boolean, allow32BitsIndices?: boolean, meshSubclass?: Mesh): Mesh; } } + declare module BABYLON { interface IGetSetVerticesData { isVerticesDataPresent(kind: string): boolean; @@ -4149,7 +4036,11 @@ declare module BABYLON { positions: number[]; normals: number[]; uvs: number[]; - uv2s: number[]; + uvs2: number[]; + uvs3: number[]; + uvs4: number[]; + uvs5: number[]; + uvs6: number[]; colors: number[]; matricesIndices: number[]; matricesWeights: number[]; @@ -4167,13 +4058,34 @@ declare module BABYLON { static ExtractFromGeometry(geometry: Geometry, copyWhenShared?: boolean): VertexData; private static _ExtractFrom(meshOrGeometry, copyWhenShared?); static CreateRibbon(pathArray: Vector3[][], closeArray: boolean, closePath: boolean, offset: number, sideOrientation?: number): VertexData; + static CreateBox(options: { + width?: number; + height?: number; + depth?: number; + faceUV?: Vector4[]; + faceColors?: Color4[]; + sideOrientation?: number; + }): VertexData; static CreateBox(size: number, sideOrientation?: number): VertexData; - static CreateSphere(segments: number, diameter: number, sideOrientation?: number): VertexData; + static CreateSphere(options: { + segments?: number; + diameterX?: number; + diameterY?: number; + diameterZ?: number; + sideOrientation?: number; + }): VertexData; + static CreateSphere(segments: number, diameter?: number, sideOrientation?: number): VertexData; static CreateCylinder(height: number, diameterTop: number, diameterBottom: number, tessellation: number, subdivisions?: number, sideOrientation?: number): VertexData; static CreateTorus(diameter: any, thickness: any, tessellation: any, sideOrientation?: number): VertexData; static CreateLines(points: Vector3[]): VertexData; static CreateDashedLines(points: Vector3[], dashSize: number, gapSize: number, dashNb: number): VertexData; - static CreateGround(width: number, height: number, subdivisions: number): VertexData; + static CreateGround(options: { + width?: number; + height?: number; + subdivisions?: number; + sideOrientation?: number; + }): VertexData; + static CreateGround(width: number, height: number, subdivisions?: number): VertexData; static CreateTiledGround(xmin: number, zmin: number, xmax: number, zmax: number, subdivisions?: { w: number; h: number; @@ -4182,6 +4094,11 @@ declare module BABYLON { h: number; }): VertexData; static CreateGroundFromHeightMap(width: number, height: number, subdivisions: number, minHeight: number, maxHeight: number, buffer: Uint8Array, bufferWidth: number, bufferHeight: number): VertexData; + static CreatePlane(options: { + width?: number; + height?: number; + sideOrientation?: number; + }): VertexData; static CreatePlane(size: number, sideOrientation?: number): VertexData; static CreateDisc(radius: number, tessellation: number, sideOrientation?: number): VertexData; static CreateTorusKnot(radius: number, tube: number, radialSegments: number, tubularSegments: number, p: number, q: number, sideOrientation?: number): VertexData; @@ -4194,6 +4111,7 @@ declare module BABYLON { private static _ComputeSides(sideOrientation, positions, indices, normals, uvs); } } + declare module BABYLON.Internals { class MeshLODLevel { distance: number; @@ -4201,6 +4119,7 @@ declare module BABYLON.Internals { constructor(distance: number, mesh: Mesh); } } + declare module BABYLON { /** * A simplifier interface for future simplification implementations. @@ -4323,6 +4242,7 @@ declare module BABYLON { private calculateError(vertex1, vertex2, pointResult?, normalResult?, uvResult?, colorResult?); } } + declare module BABYLON { class Polygon { static Rectangle(xmin: number, ymin: number, xmax: number, ymax: number): Vector2[]; @@ -4344,6 +4264,7 @@ declare module BABYLON { private addSide(positions, normals, uvs, indices, bounds, points, depth, flip); } } + declare module BABYLON { class SubMesh { materialIndex: number; @@ -4372,7 +4293,7 @@ declare module BABYLON { _checkCollision(collider: Collider): boolean; updateBoundingInfo(world: Matrix): void; isInFrustum(frustumPlanes: Plane[]): boolean; - render(): void; + render(enableAlphaMode: boolean): void; getLinesIndexBuffer(indices: number[], engine: any): WebGLBuffer; canIntersects(ray: Ray): boolean; intersects(ray: Ray, positions: Vector3[], indices: number[], fastCheck?: boolean): IntersectionInfo; @@ -4381,6 +4302,7 @@ declare module BABYLON { static CreateFromIndices(materialIndex: number, startIndex: number, indexCount: number, mesh: AbstractMesh, renderingMesh?: Mesh): SubMesh; } } + declare module BABYLON { class VertexBuffer { private _mesh; @@ -4403,6 +4325,10 @@ declare module BABYLON { private static _NormalKind; private static _UVKind; private static _UV2Kind; + private static _UV3Kind; + private static _UV4Kind; + private static _UV5Kind; + private static _UV6Kind; private static _ColorKind; private static _MatricesIndicesKind; private static _MatricesWeightsKind; @@ -4410,213 +4336,188 @@ declare module BABYLON { static NormalKind: string; static UVKind: string; static UV2Kind: string; + static UV3Kind: string; + static UV4Kind: string; + static UV5Kind: string; + static UV6Kind: string; static ColorKind: string; static MatricesIndicesKind: string; static MatricesWeightsKind: string; } } + declare module BABYLON { - class Particle { - position: Vector3; - direction: Vector3; - color: Color4; - colorStep: Color4; - lifeTime: number; - age: number; - size: number; - angle: number; - angularSpeed: number; - copyTo(other: Particle): void; + class BoundingBoxRenderer { + frontColor: Color3; + backColor: Color3; + showBackLines: boolean; + renderList: SmartArray; + private _scene; + private _colorShader; + private _vb; + private _ib; + constructor(scene: Scene); + private _prepareRessources(); + reset(): void; + render(): void; + dispose(): void; } } + declare module BABYLON { - class ParticleSystem implements IDisposable { - name: string; - static BLENDMODE_ONEONE: number; - static BLENDMODE_STANDARD: number; - id: string; - renderingGroupId: number; - emitter: any; - emitRate: number; - manualEmitCount: number; - updateSpeed: number; - targetStopDuration: number; - disposeOnStop: boolean; - minEmitPower: number; - maxEmitPower: number; - minLifeTime: number; - maxLifeTime: number; - minSize: number; - maxSize: number; - minAngularSpeed: number; - maxAngularSpeed: number; - particleTexture: Texture; - onDispose: () => void; - updateFunction: (particles: Particle[]) => void; - blendMode: number; - forceDepthWrite: boolean; - gravity: Vector3; - direction1: Vector3; - direction2: Vector3; - minEmitBox: Vector3; - maxEmitBox: Vector3; - color1: Color4; - color2: Color4; - colorDead: Color4; - textureMask: Color4; - startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3) => void; - startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3) => void; - private particles; - private _capacity; + class DepthRenderer { private _scene; - private _vertexDeclaration; - private _vertexStrideSize; - private _stockParticles; - private _newPartsExcess; - private _vertexBuffer; - private _indexBuffer; - private _vertices; + private _depthMap; private _effect; - private _customEffect; + private _viewMatrix; + private _projectionMatrix; + private _transformMatrix; + private _worldViewProjection; private _cachedDefines; - private _scaledColorStep; - private _colorDiff; - private _scaledDirection; - private _scaledGravity; - private _currentRenderId; - private _alive; - private _started; - private _stopped; - private _actualFrame; - private _scaledUpdateSpeed; - constructor(name: string, capacity: number, scene: Scene, customEffect?: Effect); - recycleParticle(particle: Particle): void; - getCapacity(): number; - isAlive(): boolean; - isStarted(): boolean; - start(): void; - stop(): void; - _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void; - private _update(newParticles); - private _getEffect(); - animate(): void; - render(): number; + constructor(scene: Scene, type?: number); + isReady(subMesh: SubMesh, useInstances: boolean): boolean; + getDepthMap(): RenderTargetTexture; dispose(): void; - clone(name: string, newEmitter: any): ParticleSystem; } } + declare module BABYLON { - interface IPhysicsEnginePlugin { - initialize(iterations?: number): any; - setGravity(gravity: Vector3): void; - runOneStep(delta: number): void; - registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; - registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; - unregisterMesh(mesh: AbstractMesh): any; - applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; - createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; + class EdgesRenderer { + private _source; + private _linesPositions; + private _linesNormals; + private _linesIndices; + private _epsilon; + private _indicesCount; + private _lineShader; + private _vb0; + private _vb1; + private _ib; + private _buffers; + private _checkVerticesInsteadOfIndices; + constructor(source: AbstractMesh, epsilon?: number, checkVerticesInsteadOfIndices?: boolean); + private _prepareRessources(); dispose(): void; - isSupported(): boolean; - updateBodyPosition(mesh: AbstractMesh): void; - } - interface PhysicsBodyCreationOptions { - mass: number; - friction: number; - restitution: number; - } - interface PhysicsCompoundBodyPart { - mesh: Mesh; - impostor: number; + private _processEdgeForAdjacencies(pa, pb, p0, p1, p2); + private _processEdgeForAdjacenciesWithVertices(pa, pb, p0, p1, p2); + private _checkEdge(faceIndex, edge, faceNormals, p0, p1); + _generateEdgesLines(): void; + render(): void; } - class PhysicsEngine { - gravity: Vector3; - private _currentPlugin; - constructor(plugin?: IPhysicsEnginePlugin); - _initialize(gravity?: Vector3): void; - _runOneStep(delta: number): void; - _setGravity(gravity: Vector3): void; - _registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; - _registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; - _unregisterMesh(mesh: AbstractMesh): void; - _applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; - _createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; - _updateBodyPosition(mesh: AbstractMesh): void; - dispose(): void; - isSupported(): boolean; - static NoImpostor: number; - static SphereImpostor: number; - static BoxImpostor: number; - static PlaneImpostor: number; - static MeshImpostor: number; - static CapsuleImpostor: number; - static ConeImpostor: number; - static CylinderImpostor: number; - static ConvexHullImpostor: number; - static Epsilon: number; +} + +declare module BABYLON { + class OutlineRenderer { + private _scene; + private _effect; + private _cachedDefines; + constructor(scene: Scene); + render(subMesh: SubMesh, batch: _InstancesBatch, useOverlay?: boolean): void; + isReady(subMesh: SubMesh, useInstances: boolean): boolean; } } + declare module BABYLON { - class CannonJSPlugin implements IPhysicsEnginePlugin { - checkWithEpsilon: (value: number) => number; - private _world; - private _registeredMeshes; - private _physicsMaterials; - initialize(iterations?: number): void; - private _checkWithEpsilon(value); - runOneStep(delta: number): void; - setGravity(gravity: Vector3): void; - registerMesh(mesh: AbstractMesh, impostor: number, options?: PhysicsBodyCreationOptions): any; - private _createSphere(radius, mesh, options?); - private _createBox(x, y, z, mesh, options?); - private _createPlane(mesh, options?); - private _createConvexPolyhedron(rawVerts, rawFaces, mesh, options?); - private _addMaterial(friction, restitution); - private _createRigidBodyFromShape(shape, mesh, mass, friction, restitution); - registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; - private _unbindBody(body); - unregisterMesh(mesh: AbstractMesh): void; - applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; - updateBodyPosition: (mesh: AbstractMesh) => void; - createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3): boolean; + class RenderingGroup { + index: number; + private _scene; + private _opaqueSubMeshes; + private _transparentSubMeshes; + private _alphaTestSubMeshes; + private _activeVertices; + constructor(index: number, scene: Scene); + render(customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray) => void): boolean; + prepare(): void; + dispatch(subMesh: SubMesh): void; + } +} + +declare module BABYLON { + class RenderingManager { + static MAX_RENDERINGGROUPS: number; + private _scene; + private _renderingGroups; + private _depthBufferAlreadyCleaned; + constructor(scene: Scene); + private _renderParticles(index, activeMeshes); + private _renderSprites(index); + private _clearDepthBuffer(); + render(customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray) => void, activeMeshes: AbstractMesh[], renderParticles: boolean, renderSprites: boolean): void; + reset(): void; + dispatch(subMesh: SubMesh): void; + } +} + +declare module BABYLON { + class Sprite { + name: string; + position: Vector3; + color: Color4; + width: number; + height: number; + angle: number; + cellIndex: number; + invertU: number; + invertV: number; + disposeWhenFinishedAnimating: boolean; + animations: Animation[]; + private _animationStarted; + private _loopAnimation; + private _fromIndex; + private _toIndex; + private _delay; + private _direction; + private _frameCount; + private _manager; + private _time; + size: number; + constructor(name: string, manager: SpriteManager); + playAnimation(from: number, to: number, loop: boolean, delay: number): void; + stopAnimation(): void; + _animate(deltaTime: number): void; dispose(): void; - isSupported(): boolean; } } + declare module BABYLON { - class OimoJSPlugin implements IPhysicsEnginePlugin { - private _world; - private _registeredMeshes; - private _checkWithEpsilon(value); - initialize(iterations?: number): void; - setGravity(gravity: Vector3): void; - registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; - registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; - private _createBodyAsCompound(part, options, initialMesh); - unregisterMesh(mesh: AbstractMesh): void; - private _unbindBody(body); - /** - * Update the body position according to the mesh position - * @param mesh - */ - updateBodyPosition: (mesh: AbstractMesh) => void; - applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; - createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; + class SpriteManager { + name: string; + cellSize: number; + sprites: Sprite[]; + renderingGroupId: number; + layerMask: number; + onDispose: () => void; + fogEnabled: boolean; + private _capacity; + private _spriteTexture; + private _epsilon; + private _scene; + private _vertexDeclaration; + private _vertexStrideSize; + private _vertexBuffer; + private _indexBuffer; + private _vertices; + private _effectBase; + private _effectFog; + constructor(name: string, imgUrl: string, capacity: number, cellSize: number, scene: Scene, epsilon?: number, samplingMode?: number); + private _appendSpriteVertex(index, sprite, offsetX, offsetY, rowSize); + render(): void; dispose(): void; - isSupported(): boolean; - private _getLastShape(body); - runOneStep(time: number): void; } } + declare module BABYLON { class AnaglyphPostProcess extends PostProcess { constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class BlackAndWhitePostProcess extends PostProcess { constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class BlurPostProcess extends PostProcess { direction: Vector2; @@ -4624,12 +4525,14 @@ declare module BABYLON { constructor(name: string, direction: Vector2, blurWidth: number, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class ColorCorrectionPostProcess extends PostProcess { private _colorTableTexture; constructor(name: string, colorTableUrl: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class ConvolutionPostProcess extends PostProcess { kernel: number[]; @@ -4642,17 +4545,20 @@ declare module BABYLON { static GaussianKernel: number[]; } } + declare module BABYLON { class DisplayPassPostProcess extends PostProcess { constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class FilterPostProcess extends PostProcess { kernelMatrix: Matrix; constructor(name: string, kernelMatrix: Matrix, ratio: number, camera?: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class FxaaPostProcess extends PostProcess { texelWidth: number; @@ -4660,6 +4566,125 @@ declare module BABYLON { constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + +declare module BABYLON { + class HDRRenderingPipeline extends PostProcessRenderPipeline implements IDisposable { + /** + * Public members + */ + /** + * Gaussian blur coefficient + * @type {number} + */ + gaussCoeff: number; + /** + * Gaussian blur mean + * @type {number} + */ + gaussMean: number; + /** + * Gaussian blur standard deviation + * @type {number} + */ + gaussStandDev: number; + /** + * Exposure, controls the overall intensity of the pipeline + * @type {number} + */ + exposure: number; + /** + * Minimum luminance that the post-process can output. Luminance is >= 0 + * @type {number} + */ + minimumLuminance: number; + /** + * Maximum luminance that the post-process can output. Must be suprerior to minimumLuminance + * @type {number} + */ + maximumLuminance: number; + /** + * Increase rate for luminance: eye adaptation speed to dark + * @type {number} + */ + luminanceIncreaserate: number; + /** + * Decrease rate for luminance: eye adaptation speed to bright + * @type {number} + */ + luminanceDecreaseRate: number; + /** + * Minimum luminance needed to compute HDR + * @type {number} + */ + brightThreshold: number; + /** + * Private members + */ + private _guassianBlurHPostProcess; + private _guassianBlurVPostProcess; + private _brightPassPostProcess; + private _textureAdderPostProcess; + private _downSampleX4PostProcess; + private _originalPostProcess; + private _hdrPostProcess; + private _hdrCurrentLuminance; + private _hdrOutputLuminance; + static LUM_STEPS: number; + private _downSamplePostProcesses; + private _scene; + private _needUpdate; + /** + * @constructor + * @param {string} name - The rendering pipeline name + * @param {BABYLON.Scene} scene - The scene linked to this pipeline + * @param {any} ratio - The size of the postprocesses (0.5 means that your postprocess will have a width = canvas.width 0.5 and a height = canvas.height 0.5) + * @param {BABYLON.PostProcess} originalPostProcess - the custom original color post-process. Must be "reusable". Can be null. + * @param {BABYLON.Camera[]} cameras - The array of cameras that the rendering pipeline will be attached to + */ + constructor(name: string, scene: Scene, ratio: number, originalPostProcess?: PostProcess, cameras?: Camera[]); + /** + * Tells the pipeline to update its post-processes + */ + update(): void; + /** + * Returns the current calculated luminance + */ + getCurrentLuminance(): number; + /** + * Returns the currently drawn luminance + */ + getOutputLuminance(): number; + /** + * Releases the rendering pipeline and its internal effects. Detaches pipeline from cameras + */ + dispose(): void; + /** + * Creates the HDR post-process and computes the luminance adaptation + */ + private _createHDRPostProcess(scene, ratio); + /** + * Texture Adder post-process + */ + private _createTextureAdderPostProcess(scene, ratio); + /** + * Down sample X4 post-process + */ + private _createDownSampleX4PostProcess(scene, ratio); + /** + * Bright pass post-process + */ + private _createBrightPassPostProcess(scene, ratio); + /** + * Luminance generator. Creates the luminance post-process and down sample post-processes + */ + private _createLuminanceGeneratorPostProcess(scene); + /** + * Gaussian blur post-processes. Horizontal and Vertical + */ + private _createGaussianBlurPostProcess(scene, ratio); + } +} + declare module BABYLON { class LensRenderingPipeline extends PostProcessRenderPipeline { /** @@ -4750,16 +4775,19 @@ declare module BABYLON { private _createGrainTexture(); } } + declare module BABYLON { class PassPostProcess extends PostProcess { constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean); } } + declare module BABYLON { class PostProcess { name: string; onApply: (effect: Effect) => void; onBeforeRender: (effect: Effect) => void; + onAfterRender: (effect: Effect) => void; onSizeChanged: () => void; onActivate: (camera: Camera) => void; width: number; @@ -4771,16 +4799,18 @@ declare module BABYLON { private _engine; private _renderRatio; private _reusable; + private _textureType; _textures: SmartArray; _currentRenderTextureInd: number; private _effect; - constructor(name: string, fragmentUrl: string, parameters: string[], samplers: string[], ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean, defines?: string); + constructor(name: string, fragmentUrl: string, parameters: string[], samplers: string[], ratio: number | any, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean, defines?: string, textureType?: number); isReusable(): boolean; activate(camera: Camera, sourceTexture?: WebGLTexture): void; apply(): Effect; dispose(camera?: Camera): void; } } + declare module BABYLON { class PostProcessManager { private _scene; @@ -4796,6 +4826,7 @@ declare module BABYLON { dispose(): void; } } + declare module BABYLON { class RefractionPostProcess extends PostProcess { color: Color3; @@ -4806,6 +4837,7 @@ declare module BABYLON { dispose(camera: Camera): void; } } + declare module BABYLON { class SSAORenderingPipeline extends PostProcessRenderPipeline { /** @@ -4893,18 +4925,28 @@ declare module BABYLON { private _createRandomTexture(); } } + declare module BABYLON { - class StereogramInterlacePostProcess extends PostProcess { + class StereoscopicInterlacePostProcess extends PostProcess { private _stepSize; - constructor(name: string, camB: Camera, postProcessA: PostProcess, isStereogramHoriz: boolean, samplingMode?: number); + constructor(name: string, camB: Camera, postProcessA: PostProcess, isStereoscopicHoriz: boolean, samplingMode?: number); } } + declare module BABYLON { - class StereoscopicInterlacePostProcess extends PostProcess { - private _stepSize; - constructor(name: string, camB: Camera, postProcessA: PostProcess, isStereoscopicHoriz: boolean, samplingMode?: number); + enum TonemappingOperator { + Hable = 0, + Reinhard = 1, + HejiDawson = 2, + Photographic = 3, + } + class TonemapPostProcess extends PostProcess { + private _operator; + private _exposureAdjustment; + constructor(name: string, operator: TonemappingOperator, exposureAdjustment: number, camera: Camera, samplingMode?: number, engine?: Engine, textureFormat?: number); } } + declare module BABYLON { class VolumetricLightScatteringPostProcess extends PostProcess { private _volumetricLightScatteringPass; @@ -4937,9 +4979,25 @@ declare module BABYLON { * Array containing the excluded meshes not rendered in the internal pass */ excludedMeshes: AbstractMesh[]; + /** + * Controls the overall intensity of the post-process + * @type {number} + */ exposure: number; + /** + * Dissipates each sample's contribution in range [0, 1] + * @type {number} + */ decay: number; + /** + * Controls the overall intensity of each sample + * @type {number} + */ weight: number; + /** + * Controls the density of each sample + * @type {number} + */ density: number; /** * @constructor @@ -4951,8 +5009,9 @@ declare module BABYLON { * @param {number} samplingMode - The post-process filtering mode * @param {BABYLON.Engine} engine - The babylon engine * @param {boolean} reusable - If the post-process is reusable + * @param {BABYLON.Scene} scene - The constructor needs a scene reference to initialize internal components. If "camera" is null (RenderPipelineà, "scene" must be provided */ - constructor(name: string, ratio: any, camera: Camera, mesh?: Mesh, samples?: number, samplingMode?: number, engine?: Engine, reusable?: boolean); + constructor(name: string, ratio: any, camera: Camera, mesh?: Mesh, samples?: number, samplingMode?: number, engine?: Engine, reusable?: boolean, scene?: Scene); isReady(subMesh: SubMesh, useInstances: boolean): boolean; /** * Sets the new light position for light scattering effect @@ -4985,850 +5044,1275 @@ declare module BABYLON { static CreateDefaultMesh(name: string, scene: Scene): Mesh; } } + +declare module BABYLON { + class VRDistortionCorrectionPostProcess extends PostProcess { + aspectRatio: number; + private _isRightEye; + private _distortionFactors; + private _postProcessScaleFactor; + private _lensCenterOffset; + private _scaleIn; + private _scaleFactor; + private _lensCenter; + constructor(name: string, camera: Camera, isRightEye: boolean, vrMetrics: VRCameraMetrics); + } +} + +declare module BABYLON.Internals { + class AndOrNotEvaluator { + static Eval(query: string, evaluateCallback: (val: any) => boolean): boolean; + private static _HandleParenthesisContent(parenthesisContent, evaluateCallback); + private static _SimplifyNegation(booleanString); + } +} + +declare module BABYLON { + interface IAssetTask { + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + run(scene: Scene, onSuccess: () => void, onError: () => void): any; + } + class MeshAssetTask implements IAssetTask { + name: string; + meshesNames: any; + rootUrl: string; + sceneFilename: string; + loadedMeshes: Array; + loadedParticleSystems: Array; + loadedSkeletons: Array; + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + constructor(name: string, meshesNames: any, rootUrl: string, sceneFilename: string); + run(scene: Scene, onSuccess: () => void, onError: () => void): void; + } + class TextFileAssetTask implements IAssetTask { + name: string; + url: string; + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + text: string; + constructor(name: string, url: string); + run(scene: Scene, onSuccess: () => void, onError: () => void): void; + } + class BinaryFileAssetTask implements IAssetTask { + name: string; + url: string; + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + data: ArrayBuffer; + constructor(name: string, url: string); + run(scene: Scene, onSuccess: () => void, onError: () => void): void; + } + class ImageAssetTask implements IAssetTask { + name: string; + url: string; + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + image: HTMLImageElement; + constructor(name: string, url: string); + run(scene: Scene, onSuccess: () => void, onError: () => void): void; + } + class TextureAssetTask implements IAssetTask { + name: string; + url: string; + noMipmap: boolean; + invertY: boolean; + samplingMode: number; + onSuccess: (task: IAssetTask) => void; + onError: (task: IAssetTask) => void; + isCompleted: boolean; + texture: Texture; + constructor(name: string, url: string, noMipmap?: boolean, invertY?: boolean, samplingMode?: number); + run(scene: Scene, onSuccess: () => void, onError: () => void): void; + } + class AssetsManager { + private _tasks; + private _scene; + private _waitingTasksCount; + onFinish: (tasks: IAssetTask[]) => void; + onTaskSuccess: (task: IAssetTask) => void; + onTaskError: (task: IAssetTask) => void; + useDefaultLoadingScreen: boolean; + constructor(scene: Scene); + addMeshTask(taskName: string, meshesNames: any, rootUrl: string, sceneFilename: string): IAssetTask; + addTextFileTask(taskName: string, url: string): IAssetTask; + addBinaryFileTask(taskName: string, url: string): IAssetTask; + addImageTask(taskName: string, url: string): IAssetTask; + addTextureTask(taskName: string, url: string, noMipmap?: boolean, invertY?: boolean, samplingMode?: number): IAssetTask; + private _decreaseWaitingTasksCount(); + private _runTask(task); + reset(): AssetsManager; + load(): AssetsManager; + } +} + declare module BABYLON { - class VRDistortionCorrectionPostProcess extends PostProcess { - aspectRatio: number; - private _isRightEye; - private _distortionFactors; - private _postProcessScaleFactor; - private _lensCenterOffset; - private _scaleIn; - private _scaleFactor; - private _lensCenter; - constructor(name: string, camera: Camera, isRightEye: boolean, vrMetrics: VRCameraMetrics); + class Database { + private callbackManifestChecked; + private currentSceneUrl; + private db; + private enableSceneOffline; + private enableTexturesOffline; + private manifestVersionFound; + private mustUpdateRessources; + private hasReachedQuota; + private isSupported; + private idbFactory; + static IsUASupportingBlobStorage: boolean; + static IDBStorageEnabled: boolean; + constructor(urlToScene: string, callbackManifestChecked: (checked: boolean) => any); + static parseURL: (url: string) => string; + static ReturnFullUrlLocation: (url: string) => string; + checkManifestFile(): void; + openAsync(successCallback: any, errorCallback: any): void; + loadImageFromDB(url: string, image: HTMLImageElement): void; + private _loadImageFromDBAsync(url, image, notInDBCallback); + private _saveImageIntoDBAsync(url, image); + private _checkVersionFromDB(url, versionLoaded); + private _loadVersionFromDBAsync(url, callback, updateInDBCallback); + private _saveVersionIntoDBAsync(url, callback); + private loadFileFromDB(url, sceneLoaded, progressCallBack, errorCallback, useArrayBuffer?); + private _loadFileFromDBAsync(url, callback, notInDBCallback, useArrayBuffer?); + private _saveFileIntoDBAsync(url, callback, progressCallback, useArrayBuffer?); } } + declare module BABYLON { - class PostProcessRenderEffect { + class FilesInput { private _engine; - private _postProcesses; - private _getPostProcess; - private _singleInstance; - private _cameras; - private _indicesForCamera; - private _renderPasses; - private _renderEffectAsPasses; - _name: string; - applyParameters: (postProcess: PostProcess) => void; - constructor(engine: Engine, name: string, getPostProcess: () => PostProcess, singleInstance?: boolean); - _update(): void; - addPass(renderPass: PostProcessRenderPass): void; - removePass(renderPass: PostProcessRenderPass): void; - addRenderEffectAsPass(renderEffect: PostProcessRenderEffect): void; - getPass(passName: string): void; - emptyPasses(): void; - _attachCameras(cameras: Camera): any; - _attachCameras(cameras: Camera[]): any; - _detachCameras(cameras: Camera): any; - _detachCameras(cameras: Camera[]): any; - _enable(cameras: Camera): any; - _enable(cameras: Camera[]): any; - _disable(cameras: Camera): any; - _disable(cameras: Camera[]): any; - getPostProcess(camera?: Camera): PostProcess; - private _linkParameters(); - private _linkTextures(effect); + private _currentScene; + private _canvas; + private _sceneLoadedCallback; + private _progressCallback; + private _additionnalRenderLoopLogicCallback; + private _textureLoadingCallback; + private _startingProcessingFilesCallback; + private _elementToMonitor; + static FilesTextures: any[]; + static FilesToLoad: any[]; + private _sceneFileToLoad; + private _filesToLoad; + constructor(p_engine: Engine, p_scene: Scene, p_canvas: HTMLCanvasElement, p_sceneLoadedCallback: any, p_progressCallback: any, p_additionnalRenderLoopLogicCallback: any, p_textureLoadingCallback: any, p_startingProcessingFilesCallback: any); + monitorElementForDragNDrop(p_elementToMonitor: HTMLElement): void; + private renderFunction(); + private drag(e); + private drop(eventDrop); + loadFiles(event: any): void; + reload(): void; } } + declare module BABYLON { - class PostProcessRenderPass { - private _enabled; - private _renderList; - private _renderTexture; - private _scene; - private _refCount; - _name: string; - constructor(scene: Scene, name: string, size: number, renderList: Mesh[], beforeRender: () => void, afterRender: () => void); - _incRefCount(): number; - _decRefCount(): number; - _update(): void; - setRenderList(renderList: Mesh[]): void; - getRenderTexture(): RenderTargetTexture; + class Gamepads { + private babylonGamepads; + private oneGamepadConnected; + private isMonitoring; + private gamepadEventSupported; + private gamepadSupportAvailable; + private _callbackGamepadConnected; + private buttonADataURL; + private static gamepadDOMInfo; + constructor(ongamedpadconnected: (gamepad: Gamepad) => void); + private _insertGamepadDOMInstructions(); + private _insertGamepadDOMNotSupported(); + dispose(): void; + private _onGamepadConnected(evt); + private _addNewGamepad(gamepad); + private _onGamepadDisconnected(evt); + private _startMonitoringGamepads(); + private _stopMonitoringGamepads(); + private _checkGamepadsStatus(); + private _updateGamepadObjects(); } -} -declare module BABYLON { - class PostProcessRenderPipeline { - private _engine; - private _renderEffects; - private _renderEffectsForIsolatedPass; - private _cameras; - _name: string; - private static PASS_EFFECT_NAME; - private static PASS_SAMPLER_NAME; - constructor(engine: Engine, name: string); - addEffect(renderEffect: PostProcessRenderEffect): void; - _enableEffect(renderEffectName: string, cameras: Camera): any; - _enableEffect(renderEffectName: string, cameras: Camera[]): any; - _disableEffect(renderEffectName: string, cameras: Camera): any; - _disableEffect(renderEffectName: string, cameras: Camera[]): any; - _attachCameras(cameras: Camera, unique: boolean): any; - _attachCameras(cameras: Camera[], unique: boolean): any; - _detachCameras(cameras: Camera): any; - _detachCameras(cameras: Camera[]): any; - _enableDisplayOnlyPass(passName: any, cameras: Camera): any; - _enableDisplayOnlyPass(passName: any, cameras: Camera[]): any; - _disableDisplayOnlyPass(cameras: Camera): any; - _disableDisplayOnlyPass(cameras: Camera[]): any; - _update(): void; + class StickValues { + x: any; + y: any; + constructor(x: any, y: any); } -} -declare module BABYLON { - class PostProcessRenderPipelineManager { - private _renderPipelines; - constructor(); - addPipeline(renderPipeline: PostProcessRenderPipeline): void; - attachCamerasToRenderPipeline(renderPipelineName: string, cameras: Camera, unique?: boolean): any; - attachCamerasToRenderPipeline(renderPipelineName: string, cameras: Camera[], unique?: boolean): any; - detachCamerasFromRenderPipeline(renderPipelineName: string, cameras: Camera): any; - detachCamerasFromRenderPipeline(renderPipelineName: string, cameras: Camera[]): any; - enableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera): any; - enableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera[]): any; - disableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera): any; - disableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera[]): any; - enableDisplayOnlyPassInPipeline(renderPipelineName: string, passName: string, cameras: Camera): any; - enableDisplayOnlyPassInPipeline(renderPipelineName: string, passName: string, cameras: Camera[]): any; - disableDisplayOnlyPassInPipeline(renderPipelineName: string, cameras: Camera): any; - disableDisplayOnlyPassInPipeline(renderPipelineName: string, cameras: Camera[]): any; + class Gamepad { + id: string; + index: number; + browserGamepad: any; + private _leftStick; + private _rightStick; + private _onleftstickchanged; + private _onrightstickchanged; + constructor(id: string, index: number, browserGamepad: any); + onleftstickchanged(callback: (values: StickValues) => void): void; + onrightstickchanged(callback: (values: StickValues) => void): void; + leftStick: StickValues; + rightStick: StickValues; + update(): void; + } + class GenericPad extends Gamepad { + id: string; + index: number; + gamepad: any; + private _buttons; + private _onbuttondown; + private _onbuttonup; + onbuttondown(callback: (buttonPressed: number) => void): void; + onbuttonup(callback: (buttonReleased: number) => void): void; + constructor(id: string, index: number, gamepad: any); + private _setButtonValue(newValue, currentValue, buttonIndex); + update(): void; + } + enum Xbox360Button { + A = 0, + B = 1, + X = 2, + Y = 3, + Start = 4, + Back = 5, + LB = 6, + RB = 7, + LeftStick = 8, + RightStick = 9, + } + enum Xbox360Dpad { + Up = 0, + Down = 1, + Left = 2, + Right = 3, + } + class Xbox360Pad extends Gamepad { + private _leftTrigger; + private _rightTrigger; + private _onlefttriggerchanged; + private _onrighttriggerchanged; + private _onbuttondown; + private _onbuttonup; + private _ondpaddown; + private _ondpadup; + private _buttonA; + private _buttonB; + private _buttonX; + private _buttonY; + private _buttonBack; + private _buttonStart; + private _buttonLB; + private _buttonRB; + private _buttonLeftStick; + private _buttonRightStick; + private _dPadUp; + private _dPadDown; + private _dPadLeft; + private _dPadRight; + onlefttriggerchanged(callback: (value: number) => void): void; + onrighttriggerchanged(callback: (value: number) => void): void; + leftTrigger: number; + rightTrigger: number; + onbuttondown(callback: (buttonPressed: Xbox360Button) => void): void; + onbuttonup(callback: (buttonReleased: Xbox360Button) => void): void; + ondpaddown(callback: (dPadPressed: Xbox360Dpad) => void): void; + ondpadup(callback: (dPadReleased: Xbox360Dpad) => void): void; + private _setButtonValue(newValue, currentValue, buttonType); + private _setDPadValue(newValue, currentValue, buttonType); + buttonA: number; + buttonB: number; + buttonX: number; + buttonY: number; + buttonStart: number; + buttonBack: number; + buttonLB: number; + buttonRB: number; + buttonLeftStick: number; + buttonRightStick: number; + dPadUp: number; + dPadDown: number; + dPadLeft: number; + dPadRight: number; update(): void; } } +interface Navigator { + getGamepads(func?: any): any; + webkitGetGamepads(func?: any): any; + msGetGamepads(func?: any): any; + webkitGamepads(func?: any): any; +} + declare module BABYLON { - class BoundingBoxRenderer { - frontColor: Color3; - backColor: Color3; - showBackLines: boolean; - renderList: SmartArray; - private _scene; - private _colorShader; - private _vb; - private _ib; - constructor(scene: Scene); - private _prepareRessources(); - reset(): void; - render(): void; - dispose(): void; + class SceneOptimization { + priority: number; + apply: (scene: Scene) => boolean; + constructor(priority?: number); + } + class TextureOptimization extends SceneOptimization { + priority: number; + maximumSize: number; + constructor(priority?: number, maximumSize?: number); + apply: (scene: Scene) => boolean; + } + class HardwareScalingOptimization extends SceneOptimization { + priority: number; + maximumScale: number; + private _currentScale; + constructor(priority?: number, maximumScale?: number); + apply: (scene: Scene) => boolean; + } + class ShadowsOptimization extends SceneOptimization { + apply: (scene: Scene) => boolean; + } + class PostProcessesOptimization extends SceneOptimization { + apply: (scene: Scene) => boolean; + } + class LensFlaresOptimization extends SceneOptimization { + apply: (scene: Scene) => boolean; + } + class ParticlesOptimization extends SceneOptimization { + apply: (scene: Scene) => boolean; + } + class RenderTargetsOptimization extends SceneOptimization { + apply: (scene: Scene) => boolean; + } + class MergeMeshesOptimization extends SceneOptimization { + static _UpdateSelectionTree: boolean; + static UpdateSelectionTree: boolean; + private _canBeMerged; + apply: (scene: Scene, updateSelectionTree?: boolean) => boolean; + } + class SceneOptimizerOptions { + targetFrameRate: number; + trackerDuration: number; + optimizations: SceneOptimization[]; + constructor(targetFrameRate?: number, trackerDuration?: number); + static LowDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; + static ModerateDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; + static HighDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; + } + class SceneOptimizer { + static _CheckCurrentState(scene: Scene, options: SceneOptimizerOptions, currentPriorityLevel: number, onSuccess?: () => void, onFailure?: () => void): void; + static OptimizeAsync(scene: Scene, options?: SceneOptimizerOptions, onSuccess?: () => void, onFailure?: () => void): void; } } + declare module BABYLON { - class DepthRenderer { - private _scene; - private _depthMap; - private _effect; - private _viewMatrix; - private _projectionMatrix; - private _transformMatrix; - private _worldViewProjection; - private _cachedDefines; - constructor(scene: Scene, type?: number); - isReady(subMesh: SubMesh, useInstances: boolean): boolean; - getDepthMap(): RenderTargetTexture; - dispose(): void; + class SceneSerializer { + static Serialize(scene: Scene): any; + static SerializeMesh(toSerialize: any, withParents?: boolean, withChildren?: boolean): any; } } + declare module BABYLON { - class OutlineRenderer { - private _scene; - private _effect; - private _cachedDefines; - constructor(scene: Scene); - render(subMesh: SubMesh, batch: _InstancesBatch, useOverlay?: boolean): void; - isReady(subMesh: SubMesh, useInstances: boolean): boolean; + class SmartArray { + data: Array; + length: number; + private _id; + private _duplicateId; + constructor(capacity: number); + push(value: any): void; + pushNoDuplicate(value: any): void; + sort(compareFn: any): void; + reset(): void; + concat(array: any): void; + concatWithNoDuplicate(array: any): void; + indexOf(value: any): number; + private static _GlobalId; } } + declare module BABYLON { - class RenderingGroup { - index: number; - private _scene; - private _opaqueSubMeshes; - private _transparentSubMeshes; - private _alphaTestSubMeshes; - private _activeVertices; - constructor(index: number, scene: Scene); - render(customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray) => void): boolean; - prepare(): void; - dispatch(subMesh: SubMesh): void; + class SmartCollection { + count: number; + items: any; + private _keys; + private _initialCapacity; + constructor(capacity?: number); + add(key: any, item: any): number; + remove(key: any): number; + removeItemOfIndex(index: number): number; + indexOf(key: any): number; + item(key: any): any; + getAllKeys(): any[]; + getKeyByIndex(index: number): any; + getItemByIndex(index: number): any; + empty(): void; + forEach(block: (item: any) => void): void; } } + declare module BABYLON { - class RenderingManager { - static MAX_RENDERINGGROUPS: number; - private _scene; - private _renderingGroups; - private _depthBufferAlreadyCleaned; - constructor(scene: Scene); - private _renderParticles(index, activeMeshes); - private _renderSprites(index); - private _clearDepthBuffer(); - render(customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray) => void, activeMeshes: AbstractMesh[], renderParticles: boolean, renderSprites: boolean): void; - reset(): void; - dispatch(subMesh: SubMesh): void; + class Tags { + static EnableFor(obj: any): void; + static DisableFor(obj: any): void; + static HasTags(obj: any): boolean; + static GetTags(obj: any): any; + static AddTagsTo(obj: any, tagsString: string): void; + static _AddTagTo(obj: any, tag: string): void; + static RemoveTagsFrom(obj: any, tagsString: string): void; + static _RemoveTagFrom(obj: any, tag: string): void; + static MatchesQuery(obj: any, tagsQuery: string): boolean; } } -declare module BABYLON { - class Sprite { - name: string; - position: Vector3; - color: Color4; + +declare module BABYLON.Internals { + interface DDSInfo { width: number; height: number; - angle: number; - cellIndex: number; - invertU: number; - invertV: number; - disposeWhenFinishedAnimating: boolean; - animations: Animation[]; - private _animationStarted; - private _loopAnimation; - private _fromIndex; - private _toIndex; - private _delay; - private _direction; - private _frameCount; - private _manager; - private _time; - size: number; - constructor(name: string, manager: SpriteManager); - playAnimation(from: number, to: number, loop: boolean, delay: number): void; - stopAnimation(): void; - _animate(deltaTime: number): void; - dispose(): void; + mipmapCount: number; + isFourCC: boolean; + isRGB: boolean; + isLuminance: boolean; + isCube: boolean; } -} -declare module BABYLON { - class SpriteManager { - name: string; - cellSize: number; - sprites: Sprite[]; - renderingGroupId: number; - onDispose: () => void; - fogEnabled: boolean; - private _capacity; - private _spriteTexture; - private _epsilon; - private _scene; - private _vertexDeclaration; - private _vertexStrideSize; - private _vertexBuffer; - private _indexBuffer; - private _vertices; - private _effectBase; - private _effectFog; - constructor(name: string, imgUrl: string, capacity: number, cellSize: number, scene: Scene, epsilon?: number, samplingMode?: number); - private _appendSpriteVertex(index, sprite, offsetX, offsetY, rowSize); - render(): void; - dispose(): void; + class DDSTools { + static GetDDSInfo(arrayBuffer: any): DDSInfo; + private static GetRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); + private static GetRGBArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); + private static GetLuminanceArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); + static UploadDDSLevels(gl: WebGLRenderingContext, ext: any, arrayBuffer: any, info: DDSInfo, loadMipmaps: boolean, faces: number): void; } } + declare module BABYLON.Internals { - class AndOrNotEvaluator { - static Eval(query: string, evaluateCallback: (val: any) => boolean): boolean; - private static _HandleParenthesisContent(parenthesisContent, evaluateCallback); - private static _SimplifyNegation(booleanString); + class TGATools { + private static _TYPE_NO_DATA; + private static _TYPE_INDEXED; + private static _TYPE_RGB; + private static _TYPE_GREY; + private static _TYPE_RLE_INDEXED; + private static _TYPE_RLE_RGB; + private static _TYPE_RLE_GREY; + private static _ORIGIN_MASK; + private static _ORIGIN_SHIFT; + private static _ORIGIN_BL; + private static _ORIGIN_BR; + private static _ORIGIN_UL; + private static _ORIGIN_UR; + static GetTGAHeader(data: Uint8Array): any; + static UploadContent(gl: WebGLRenderingContext, data: Uint8Array): void; + static _getImageData8bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + static _getImageData16bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + static _getImageData24bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + static _getImageData32bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + static _getImageDataGrey8bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + static _getImageDataGrey16bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; } } + declare module BABYLON { - interface IAssetTask { - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - run(scene: Scene, onSuccess: () => void, onError: () => void): any; - } - class MeshAssetTask implements IAssetTask { - name: string; - meshesNames: any; - rootUrl: string; - sceneFilename: string; - loadedMeshes: Array; - loadedParticleSystems: Array; - loadedSkeletons: Array; - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - constructor(name: string, meshesNames: any, rootUrl: string, sceneFilename: string); - run(scene: Scene, onSuccess: () => void, onError: () => void): void; + interface IAnimatable { + animations: Array; } - class TextFileAssetTask implements IAssetTask { - name: string; - url: string; - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - text: string; - constructor(name: string, url: string); - run(scene: Scene, onSuccess: () => void, onError: () => void): void; + interface ISize { + width: number; + height: number; } - class BinaryFileAssetTask implements IAssetTask { - name: string; - url: string; - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - data: ArrayBuffer; - constructor(name: string, url: string); - run(scene: Scene, onSuccess: () => void, onError: () => void): void; + class Tools { + static BaseUrl: string; + static ToHex(i: number): string; + static SetImmediate(action: () => void): void; + static IsExponantOfTwo(value: number): boolean; + static GetExponantOfTwo(value: number, max: number): number; + static GetFilename(path: string): string; + static GetDOMTextContent(element: HTMLElement): string; + static ToDegrees(angle: number): number; + static ToRadians(angle: number): number; + static ExtractMinAndMaxIndexed(positions: number[], indices: number[], indexStart: number, indexCount: number): { + minimum: Vector3; + maximum: Vector3; + }; + static ExtractMinAndMax(positions: number[], start: number, count: number): { + minimum: Vector3; + maximum: Vector3; + }; + static MakeArray(obj: any, allowsNullUndefined?: boolean): Array; + static GetPointerPrefix(): string; + static QueueNewFrame(func: any): void; + static RequestFullscreen(element: any): void; + static ExitFullscreen(): void; + static CleanUrl(url: string): string; + static LoadImage(url: string, onload: any, onerror: any, database: any): HTMLImageElement; + static LoadFile(url: string, callback: (data: any) => void, progressCallBack?: () => void, database?: any, useArrayBuffer?: boolean, onError?: () => void): void; + static ReadFileAsDataURL(fileToLoad: any, callback: any, progressCallback: any): void; + static ReadFile(fileToLoad: any, callback: any, progressCallBack: any, useArrayBuffer?: boolean): void; + static Clamp(value: number, min?: number, max?: number): number; + static Sign(value: number): number; + static Format(value: number, decimals?: number): string; + static CheckExtends(v: Vector3, min: Vector3, max: Vector3): void; + static WithinEpsilon(a: number, b: number, epsilon?: number): boolean; + static DeepCopy(source: any, destination: any, doNotCopyList?: string[], mustCopyList?: string[]): void; + static IsEmpty(obj: any): boolean; + static RegisterTopRootEvents(events: { + name: string; + handler: EventListener; + }[]): void; + static UnregisterTopRootEvents(events: { + name: string; + handler: EventListener; + }[]): void; + static DumpFramebuffer(width: number, height: number, engine: Engine, successCallback?: (data: String) => void): void; + static CreateScreenshot(engine: Engine, camera: Camera, size: any, successCallback?: (data: String) => void): void; + static ValidateXHRData(xhr: XMLHttpRequest, dataType?: number): boolean; + private static _NoneLogLevel; + private static _MessageLogLevel; + private static _WarningLogLevel; + private static _ErrorLogLevel; + private static _LogCache; + static errorsCount: number; + static OnNewCacheEntry: (entry: string) => void; + static NoneLogLevel: number; + static MessageLogLevel: number; + static WarningLogLevel: number; + static ErrorLogLevel: number; + static AllLogLevel: number; + private static _AddLogEntry(entry); + private static _FormatMessage(message); + static Log: (message: string) => void; + private static _LogDisabled(message); + private static _LogEnabled(message); + static Warn: (message: string) => void; + private static _WarnDisabled(message); + private static _WarnEnabled(message); + static Error: (message: string) => void; + private static _ErrorDisabled(message); + private static _ErrorEnabled(message); + static LogCache: string; + static ClearLogCache(): void; + static LogLevels: number; + private static _PerformanceNoneLogLevel; + private static _PerformanceUserMarkLogLevel; + private static _PerformanceConsoleLogLevel; + private static _performance; + static PerformanceNoneLogLevel: number; + static PerformanceUserMarkLogLevel: number; + static PerformanceConsoleLogLevel: number; + static PerformanceLogLevel: number; + static _StartPerformanceCounterDisabled(counterName: string, condition?: boolean): void; + static _EndPerformanceCounterDisabled(counterName: string, condition?: boolean): void; + static _StartUserMark(counterName: string, condition?: boolean): void; + static _EndUserMark(counterName: string, condition?: boolean): void; + static _StartPerformanceConsole(counterName: string, condition?: boolean): void; + static _EndPerformanceConsole(counterName: string, condition?: boolean): void; + static StartPerformanceCounter: (counterName: string, condition?: boolean) => void; + static EndPerformanceCounter: (counterName: string, condition?: boolean) => void; + static Now: number; + static GetFps(): number; } - class ImageAssetTask implements IAssetTask { - name: string; - url: string; - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - image: HTMLImageElement; - constructor(name: string, url: string); - run(scene: Scene, onSuccess: () => void, onError: () => void): void; + /** + * An implementation of a loop for asynchronous functions. + */ + class AsyncLoop { + iterations: number; + private _fn; + private _successCallback; + index: number; + private _done; + /** + * Constroctor. + * @param iterations the number of iterations. + * @param _fn the function to run each iteration + * @param _successCallback the callback that will be called upon succesful execution + * @param offset starting offset. + */ + constructor(iterations: number, _fn: (asyncLoop: AsyncLoop) => void, _successCallback: () => void, offset?: number); + /** + * Execute the next iteration. Must be called after the last iteration was finished. + */ + executeNext(): void; + /** + * Break the loop and run the success callback. + */ + breakLoop(): void; + /** + * Helper function + */ + static Run(iterations: number, _fn: (asyncLoop: AsyncLoop) => void, _successCallback: () => void, offset?: number): AsyncLoop; + /** + * A for-loop that will run a given number of iterations synchronous and the rest async. + * @param iterations total number of iterations + * @param syncedIterations number of synchronous iterations in each async iteration. + * @param fn the function to call each iteration. + * @param callback a success call back that will be called when iterating stops. + * @param breakFunction a break condition (optional) + * @param timeout timeout settings for the setTimeout function. default - 0. + * @constructor + */ + static SyncAsyncForLoop(iterations: number, syncedIterations: number, fn: (iteration: number) => void, callback: () => void, breakFunction?: () => boolean, timeout?: number): void; } - class TextureAssetTask implements IAssetTask { - name: string; - url: string; - noMipmap: boolean; - invertY: boolean; - samplingMode: number; - onSuccess: (task: IAssetTask) => void; - onError: (task: IAssetTask) => void; - isCompleted: boolean; - texture: Texture; - constructor(name: string, url: string, noMipmap?: boolean, invertY?: boolean, samplingMode?: number); - run(scene: Scene, onSuccess: () => void, onError: () => void): void; +} + +declare module BABYLON { + enum JoystickAxis { + X = 0, + Y = 1, + Z = 2, } - class AssetsManager { - private _tasks; - private _scene; - private _waitingTasksCount; - onFinish: (tasks: IAssetTask[]) => void; - onTaskSuccess: (task: IAssetTask) => void; - onTaskError: (task: IAssetTask) => void; - useDefaultLoadingScreen: boolean; - constructor(scene: Scene); - addMeshTask(taskName: string, meshesNames: any, rootUrl: string, sceneFilename: string): IAssetTask; - addTextFileTask(taskName: string, url: string): IAssetTask; - addBinaryFileTask(taskName: string, url: string): IAssetTask; - addImageTask(taskName: string, url: string): IAssetTask; - addTextureTask(taskName: string, url: string, noMipmap?: boolean, invertY?: boolean, samplingMode?: number): IAssetTask; - private _decreaseWaitingTasksCount(); - private _runTask(task); - reset(): AssetsManager; - load(): AssetsManager; + class VirtualJoystick { + reverseLeftRight: boolean; + reverseUpDown: boolean; + deltaPosition: Vector3; + pressed: boolean; + private static _globalJoystickIndex; + private static vjCanvas; + private static vjCanvasContext; + private static vjCanvasWidth; + private static vjCanvasHeight; + private static halfWidth; + private static halfHeight; + private _action; + private _axisTargetedByLeftAndRight; + private _axisTargetedByUpAndDown; + private _joystickSensibility; + private _inversedSensibility; + private _rotationSpeed; + private _inverseRotationSpeed; + private _rotateOnAxisRelativeToMesh; + private _joystickPointerID; + private _joystickColor; + private _joystickPointerPos; + private _joystickPreviousPointerPos; + private _joystickPointerStartPos; + private _deltaJoystickVector; + private _leftJoystick; + private _joystickIndex; + private _touches; + private _onPointerDownHandlerRef; + private _onPointerMoveHandlerRef; + private _onPointerUpHandlerRef; + private _onPointerOutHandlerRef; + private _onResize; + constructor(leftJoystick?: boolean); + setJoystickSensibility(newJoystickSensibility: number): void; + private _onPointerDown(e); + private _onPointerMove(e); + private _onPointerUp(e); + /** + * Change the color of the virtual joystick + * @param newColor a string that must be a CSS color value (like "red") or the hexa value (like "#FF0000") + */ + setJoystickColor(newColor: string): void; + setActionOnTouch(action: () => any): void; + setAxisForLeftRight(axis: JoystickAxis): void; + setAxisForUpDown(axis: JoystickAxis): void; + private _clearCanvas(); + private _drawVirtualJoystick(); + releaseCanvas(): void; } } + declare module BABYLON { - class Database { - private callbackManifestChecked; - private currentSceneUrl; - private db; - private enableSceneOffline; - private enableTexturesOffline; - private manifestVersionFound; - private mustUpdateRessources; - private hasReachedQuota; - private isSupported; - private idbFactory; - static IsUASupportingBlobStorage: boolean; - static IDBStorageEnabled: boolean; - constructor(urlToScene: string, callbackManifestChecked: (checked: boolean) => any); - static parseURL: (url: string) => string; - static ReturnFullUrlLocation: (url: string) => string; - checkManifestFile(): void; - openAsync(successCallback: any, errorCallback: any): void; - loadImageFromDB(url: string, image: HTMLImageElement): void; - private _loadImageFromDBAsync(url, image, notInDBCallback); - private _saveImageIntoDBAsync(url, image); - private _checkVersionFromDB(url, versionLoaded); - private _loadVersionFromDBAsync(url, callback, updateInDBCallback); - private _saveVersionIntoDBAsync(url, callback); - private loadFileFromDB(url, sceneLoaded, progressCallBack, errorCallback, useArrayBuffer?); - private _loadFileFromDBAsync(url, callback, notInDBCallback, useArrayBuffer?); - private _saveFileIntoDBAsync(url, callback, progressCallback, useArrayBuffer?); + class VRDeviceOrientationFreeCamera extends FreeCamera { + _alpha: number; + _beta: number; + _gamma: number; + private _offsetOrientation; + private _deviceOrientationHandler; + constructor(name: string, position: Vector3, scene: Scene, compensateDistorsion?: boolean); + _onOrientationEvent(evt: DeviceOrientationEvent): void; + attachControl(element: HTMLElement, noPreventDefault?: boolean): void; + detachControl(element: HTMLElement): void; } } + +declare var HMDVRDevice: any; +declare var PositionSensorVRDevice: any; declare module BABYLON { - class FilesInput { - private _engine; - private _currentScene; - private _canvas; - private _sceneLoadedCallback; - private _progressCallback; - private _additionnalRenderLoopLogicCallback; - private _textureLoadingCallback; - private _startingProcessingFilesCallback; - private _elementToMonitor; - static FilesTextures: any[]; - static FilesToLoad: any[]; - private _sceneFileToLoad; - private _filesToLoad; - constructor(p_engine: Engine, p_scene: Scene, p_canvas: HTMLCanvasElement, p_sceneLoadedCallback: any, p_progressCallback: any, p_additionnalRenderLoopLogicCallback: any, p_textureLoadingCallback: any, p_startingProcessingFilesCallback: any); - monitorElementForDragNDrop(p_elementToMonitor: HTMLElement): void; - private renderFunction(); - private drag(e); - private drop(eventDrop); - loadFiles(event: any): void; - reload(): void; + class WebVRFreeCamera extends FreeCamera { + _hmdDevice: any; + _sensorDevice: any; + _cacheState: any; + _cacheQuaternion: Quaternion; + _cacheRotation: Vector3; + _vrEnabled: boolean; + constructor(name: string, position: Vector3, scene: Scene, compensateDistorsion?: boolean); + private _getWebVRDevices(devices); + _checkInputs(): void; + attachControl(element: HTMLElement, noPreventDefault?: boolean): void; + detachControl(element: HTMLElement): void; } } + declare module BABYLON { - class Gamepads { - private babylonGamepads; - private oneGamepadConnected; - private isMonitoring; - private gamepadEventSupported; - private gamepadSupportAvailable; - private _callbackGamepadConnected; - private buttonADataURL; - private static gamepadDOMInfo; - constructor(ongamedpadconnected: (gamepad: Gamepad) => void); - private _insertGamepadDOMInstructions(); - private _insertGamepadDOMNotSupported(); - dispose(): void; - private _onGamepadConnected(evt); - private _addNewGamepad(gamepad); - private _onGamepadDisconnected(evt); - private _startMonitoringGamepads(); - private _stopMonitoringGamepads(); - private _checkGamepadsStatus(); - private _updateGamepadObjects(); - } - class StickValues { - x: any; - y: any; - constructor(x: any, y: any); - } - class Gamepad { - id: string; - index: number; - browserGamepad: any; - private _leftStick; - private _rightStick; - private _onleftstickchanged; - private _onrightstickchanged; - constructor(id: string, index: number, browserGamepad: any); - onleftstickchanged(callback: (values: StickValues) => void): void; - onrightstickchanged(callback: (values: StickValues) => void): void; - leftStick: StickValues; - rightStick: StickValues; - update(): void; - } - class GenericPad extends Gamepad { - id: string; - index: number; - gamepad: any; - private _buttons; - private _onbuttondown; - private _onbuttonup; - onbuttondown(callback: (buttonPressed: number) => void): void; - onbuttonup(callback: (buttonReleased: number) => void): void; - constructor(id: string, index: number, gamepad: any); - private _setButtonValue(newValue, currentValue, buttonIndex); - update(): void; + interface IOctreeContainer { + blocks: Array>; } - enum Xbox360Button { - A = 0, - B = 1, - X = 2, - Y = 3, - Start = 4, - Back = 5, - LB = 6, - RB = 7, - LeftStick = 8, - RightStick = 9, + class Octree { + maxDepth: number; + blocks: Array>; + dynamicContent: T[]; + private _maxBlockCapacity; + private _selectionContent; + private _creationFunc; + constructor(creationFunc: (entry: T, block: OctreeBlock) => void, maxBlockCapacity?: number, maxDepth?: number); + update(worldMin: Vector3, worldMax: Vector3, entries: T[]): void; + addMesh(entry: T): void; + select(frustumPlanes: Plane[], allowDuplicate?: boolean): SmartArray; + intersects(sphereCenter: Vector3, sphereRadius: number, allowDuplicate?: boolean): SmartArray; + intersectsRay(ray: Ray): SmartArray; + static _CreateBlocks(worldMin: Vector3, worldMax: Vector3, entries: T[], maxBlockCapacity: number, currentDepth: number, maxDepth: number, target: IOctreeContainer, creationFunc: (entry: T, block: OctreeBlock) => void): void; + static CreationFuncForMeshes: (entry: AbstractMesh, block: OctreeBlock) => void; + static CreationFuncForSubMeshes: (entry: SubMesh, block: OctreeBlock) => void; } - enum Xbox360Dpad { - Up = 0, - Down = 1, - Left = 2, - Right = 3, +} + +declare module BABYLON { + class OctreeBlock { + entries: T[]; + blocks: Array>; + private _depth; + private _maxDepth; + private _capacity; + private _minPoint; + private _maxPoint; + private _boundingVectors; + private _creationFunc; + constructor(minPoint: Vector3, maxPoint: Vector3, capacity: number, depth: number, maxDepth: number, creationFunc: (entry: T, block: OctreeBlock) => void); + capacity: number; + minPoint: Vector3; + maxPoint: Vector3; + addEntry(entry: T): void; + addEntries(entries: T[]): void; + select(frustumPlanes: Plane[], selection: SmartArray, allowDuplicate?: boolean): void; + intersects(sphereCenter: Vector3, sphereRadius: number, selection: SmartArray, allowDuplicate?: boolean): void; + intersectsRay(ray: Ray, selection: SmartArray): void; + createInnerBlocks(): void; } - class Xbox360Pad extends Gamepad { - private _leftTrigger; - private _rightTrigger; - private _onlefttriggerchanged; - private _onrighttriggerchanged; - private _onbuttondown; - private _onbuttonup; - private _ondpaddown; - private _ondpadup; - private _buttonA; - private _buttonB; - private _buttonX; - private _buttonY; - private _buttonBack; - private _buttonStart; - private _buttonLB; - private _buttonRB; - private _buttonLeftStick; - private _buttonRightStick; - private _dPadUp; - private _dPadDown; - private _dPadLeft; - private _dPadRight; - onlefttriggerchanged(callback: (value: number) => void): void; - onrighttriggerchanged(callback: (value: number) => void): void; - leftTrigger: number; - rightTrigger: number; - onbuttondown(callback: (buttonPressed: Xbox360Button) => void): void; - onbuttonup(callback: (buttonReleased: Xbox360Button) => void): void; - ondpaddown(callback: (dPadPressed: Xbox360Dpad) => void): void; - ondpadup(callback: (dPadReleased: Xbox360Dpad) => void): void; - private _setButtonValue(newValue, currentValue, buttonType); - private _setDPadValue(newValue, currentValue, buttonType); - buttonA: number; - buttonB: number; - buttonX: number; - buttonY: number; - buttonStart: number; - buttonBack: number; - buttonLB: number; - buttonRB: number; - buttonLeftStick: number; - buttonRightStick: number; - dPadUp: number; - dPadDown: number; - dPadLeft: number; - dPadRight: number; - update(): void; +} + +declare module BABYLON { + class ShadowGenerator { + private static _FILTER_NONE; + private static _FILTER_VARIANCESHADOWMAP; + private static _FILTER_POISSONSAMPLING; + private static _FILTER_BLURVARIANCESHADOWMAP; + static FILTER_NONE: number; + static FILTER_VARIANCESHADOWMAP: number; + static FILTER_POISSONSAMPLING: number; + static FILTER_BLURVARIANCESHADOWMAP: number; + private _filter; + blurScale: number; + private _blurBoxOffset; + private _bias; + private _lightDirection; + bias: number; + blurBoxOffset: number; + filter: number; + useVarianceShadowMap: boolean; + usePoissonSampling: boolean; + useBlurVarianceShadowMap: boolean; + private _light; + private _scene; + private _shadowMap; + private _shadowMap2; + private _darkness; + private _transparencyShadow; + private _effect; + private _viewMatrix; + private _projectionMatrix; + private _transformMatrix; + private _worldViewProjection; + private _cachedPosition; + private _cachedDirection; + private _cachedDefines; + private _currentRenderID; + private _downSamplePostprocess; + private _boxBlurPostprocess; + private _mapSize; + constructor(mapSize: number, light: IShadowLight); + isReady(subMesh: SubMesh, useInstances: boolean): boolean; + getShadowMap(): RenderTargetTexture; + getShadowMapForRendering(): RenderTargetTexture; + getLight(): IShadowLight; + getTransformMatrix(): Matrix; + getDarkness(): number; + setDarkness(darkness: number): void; + setTransparencyShadow(hasShadow: boolean): void; + private _packHalf(depth); + dispose(): void; } } -interface Navigator { - getGamepads(func?: any): any; - webkitGetGamepads(func?: any): any; - msGetGamepads(func?: any): any; - webkitGamepads(func?: any): any; + +declare module BABYLON.Internals { } + declare module BABYLON { - class SceneOptimization { - priority: number; - apply: (scene: Scene) => boolean; - constructor(priority?: number); - } - class TextureOptimization extends SceneOptimization { - priority: number; - maximumSize: number; - constructor(priority?: number, maximumSize?: number); - apply: (scene: Scene) => boolean; - } - class HardwareScalingOptimization extends SceneOptimization { - priority: number; - maximumScale: number; - private _currentScale; - constructor(priority?: number, maximumScale?: number); - apply: (scene: Scene) => boolean; - } - class ShadowsOptimization extends SceneOptimization { - apply: (scene: Scene) => boolean; + class BaseTexture { + name: string; + delayLoadState: number; + hasAlpha: boolean; + getAlphaFromRGB: boolean; + level: number; + isCube: boolean; + isRenderTarget: boolean; + animations: Animation[]; + onDispose: () => void; + coordinatesIndex: number; + coordinatesMode: number; + wrapU: number; + wrapV: number; + anisotropicFilteringLevel: number; + _cachedAnisotropicFilteringLevel: number; + private _scene; + _texture: WebGLTexture; + constructor(scene: Scene); + getScene(): Scene; + getTextureMatrix(): Matrix; + getReflectionTextureMatrix(): Matrix; + getInternalTexture(): WebGLTexture; + isReady(): boolean; + getSize(): ISize; + getBaseSize(): ISize; + scale(ratio: number): void; + canRescale: boolean; + _removeFromCache(url: string, noMipmap: boolean): void; + _getFromCache(url: string, noMipmap: boolean, sampling?: number): WebGLTexture; + delayLoad(): void; + releaseInternalTexture(): void; + clone(): BaseTexture; + dispose(): void; } - class PostProcessesOptimization extends SceneOptimization { - apply: (scene: Scene) => boolean; +} + +declare module BABYLON { + class CubeTexture extends BaseTexture { + url: string; + coordinatesMode: number; + private _noMipmap; + private _extensions; + private _textureMatrix; + constructor(rootUrl: string, scene: Scene, extensions?: string[], noMipmap?: boolean); + clone(): CubeTexture; + delayLoad(): void; + getReflectionTextureMatrix(): Matrix; } - class LensFlaresOptimization extends SceneOptimization { - apply: (scene: Scene) => boolean; +} + +declare module BABYLON { + class DynamicTexture extends Texture { + private _generateMipMaps; + private _canvas; + private _context; + constructor(name: string, options: any, scene: Scene, generateMipMaps: boolean, samplingMode?: number); + canRescale: boolean; + scale(ratio: number): void; + getContext(): CanvasRenderingContext2D; + clear(): void; + update(invertY?: boolean): void; + drawText(text: string, x: number, y: number, font: string, color: string, clearColor: string, invertY?: boolean, update?: boolean): void; + clone(): DynamicTexture; } - class ParticlesOptimization extends SceneOptimization { - apply: (scene: Scene) => boolean; +} + +declare module BABYLON { + class MirrorTexture extends RenderTargetTexture { + mirrorPlane: Plane; + private _transformMatrix; + private _mirrorMatrix; + private _savedViewMatrix; + constructor(name: string, size: number, scene: Scene, generateMipMaps?: boolean); + clone(): MirrorTexture; } - class RenderTargetsOptimization extends SceneOptimization { - apply: (scene: Scene) => boolean; +} + +declare module BABYLON { + class RawTexture extends Texture { + format: number; + constructor(data: ArrayBufferView, width: number, height: number, format: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number); + update(data: ArrayBufferView): void; + static CreateLuminanceTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; + static CreateLuminanceAlphaTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; + static CreateAlphaTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; + static CreateRGBTexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; + static CreateRGBATexture(data: ArrayBufferView, width: number, height: number, scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number): RawTexture; } - class MergeMeshesOptimization extends SceneOptimization { - private _canBeMerged; - apply: (scene: Scene) => boolean; +} + +declare module BABYLON { + class RenderTargetTexture extends Texture { + renderList: AbstractMesh[]; + renderParticles: boolean; + renderSprites: boolean; + coordinatesMode: number; + onBeforeRender: () => void; + onAfterRender: () => void; + onAfterUnbind: () => void; + onClear: (engine: Engine) => void; + activeCamera: Camera; + customRenderFunction: (opaqueSubMeshes: SmartArray, transparentSubMeshes: SmartArray, alphaTestSubMeshes: SmartArray, beforeTransparents?: () => void) => void; + private _size; + _generateMipMaps: boolean; + private _renderingManager; + _waitingRenderList: string[]; + private _doNotChangeAspectRatio; + private _currentRefreshId; + private _refreshRate; + constructor(name: string, size: any, scene: Scene, generateMipMaps?: boolean, doNotChangeAspectRatio?: boolean, type?: number); + resetRefreshCounter(): void; + refreshRate: number; + _shouldRender(): boolean; + isReady(): boolean; + getRenderSize(): number; + canRescale: boolean; + scale(ratio: number): void; + resize(size: any, generateMipMaps?: boolean): void; + render(useCameraPostProcess?: boolean, dumpForDebug?: boolean): void; + clone(): RenderTargetTexture; } - class SceneOptimizerOptions { - targetFrameRate: number; - trackerDuration: number; - optimizations: SceneOptimization[]; - constructor(targetFrameRate?: number, trackerDuration?: number); - static LowDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; - static ModerateDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; - static HighDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions; +} + +declare module BABYLON { + class Texture extends BaseTexture { + static NEAREST_SAMPLINGMODE: number; + static BILINEAR_SAMPLINGMODE: number; + static TRILINEAR_SAMPLINGMODE: number; + static EXPLICIT_MODE: number; + static SPHERICAL_MODE: number; + static PLANAR_MODE: number; + static CUBIC_MODE: number; + static PROJECTION_MODE: number; + static SKYBOX_MODE: number; + static CLAMP_ADDRESSMODE: number; + static WRAP_ADDRESSMODE: number; + static MIRROR_ADDRESSMODE: number; + url: string; + uOffset: number; + vOffset: number; + uScale: number; + vScale: number; + uAng: number; + vAng: number; + wAng: number; + private _noMipmap; + _invertY: boolean; + private _rowGenerationMatrix; + private _cachedTextureMatrix; + private _projectionModeMatrix; + private _t0; + private _t1; + private _t2; + private _cachedUOffset; + private _cachedVOffset; + private _cachedUScale; + private _cachedVScale; + private _cachedUAng; + private _cachedVAng; + private _cachedWAng; + private _cachedCoordinatesMode; + _samplingMode: number; + private _buffer; + private _deleteBuffer; + constructor(url: string, scene: Scene, noMipmap?: boolean, invertY?: boolean, samplingMode?: number, onLoad?: () => void, onError?: () => void, buffer?: any, deleteBuffer?: boolean); + delayLoad(): void; + updateSamplingMode(samplingMode: number): void; + private _prepareRowForTextureGeneration(x, y, z, t); + getTextureMatrix(): Matrix; + getReflectionTextureMatrix(): Matrix; + clone(): Texture; + static CreateFromBase64String(data: string, name: string, scene: Scene, noMipmap?: boolean, invertY?: boolean, samplingMode?: number, onLoad?: () => void, onError?: () => void): Texture; } - class SceneOptimizer { - static _CheckCurrentState(scene: Scene, options: SceneOptimizerOptions, currentPriorityLevel: number, onSuccess?: () => void, onFailure?: () => void): void; - static OptimizeAsync(scene: Scene, options?: SceneOptimizerOptions, onSuccess?: () => void, onFailure?: () => void): void; +} + +declare module BABYLON { + class VideoTexture extends Texture { + video: HTMLVideoElement; + private _autoLaunch; + private _lastUpdate; + constructor(name: string, urls: string[], scene: Scene, generateMipMaps?: boolean, invertY?: boolean, samplingMode?: number); + update(): boolean; } } + declare module BABYLON { - class SceneSerializer { - static Serialize(scene: Scene): any; + class CannonJSPlugin implements IPhysicsEnginePlugin { + checkWithEpsilon: (value: number) => number; + private _world; + private _registeredMeshes; + private _physicsMaterials; + initialize(iterations?: number): void; + private _checkWithEpsilon(value); + runOneStep(delta: number): void; + setGravity(gravity: Vector3): void; + registerMesh(mesh: AbstractMesh, impostor: number, options?: PhysicsBodyCreationOptions): any; + private _createSphere(radius, mesh, options?); + private _createBox(x, y, z, mesh, options?); + private _createPlane(mesh, options?); + private _createConvexPolyhedron(rawVerts, rawFaces, mesh, options?); + private _addMaterial(friction, restitution); + private _createRigidBodyFromShape(shape, mesh, mass, friction, restitution); + registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; + private _unbindBody(body); + unregisterMesh(mesh: AbstractMesh): void; + applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; + updateBodyPosition: (mesh: AbstractMesh) => void; + createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3): boolean; + dispose(): void; + isSupported(): boolean; } } + declare module BABYLON { - class SmartArray { - data: Array; - length: number; - private _id; - private _duplicateId; - constructor(capacity: number); - push(value: any): void; - pushNoDuplicate(value: any): void; - sort(compareFn: any): void; - reset(): void; - concat(array: any): void; - concatWithNoDuplicate(array: any): void; - indexOf(value: any): number; - private static _GlobalId; + class OimoJSPlugin implements IPhysicsEnginePlugin { + private _world; + private _registeredMeshes; + private _checkWithEpsilon(value); + initialize(iterations?: number): void; + setGravity(gravity: Vector3): void; + registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any; + registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any; + private _createBodyAsCompound(part, options, initialMesh); + unregisterMesh(mesh: AbstractMesh): void; + private _unbindBody(body); + /** + * Update the body position according to the mesh position + * @param mesh + */ + updateBodyPosition: (mesh: AbstractMesh) => void; + applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void; + createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean; + dispose(): void; + isSupported(): boolean; + private _getLastShape(body); + runOneStep(time: number): void; } } + declare module BABYLON { - class SmartCollection { - count: number; - items: any; - private _keys; - private _initialCapacity; - constructor(capacity?: number); - add(key: any, item: any): number; - remove(key: any): number; - removeItemOfIndex(index: number): number; - indexOf(key: any): number; - item(key: any): any; - getAllKeys(): any[]; - getKeyByIndex(index: number): any; - getItemByIndex(index: number): any; - empty(): void; - forEach(block: (item: any) => void): void; + class PostProcessRenderEffect { + private _engine; + private _postProcesses; + private _getPostProcess; + private _singleInstance; + private _cameras; + private _indicesForCamera; + private _renderPasses; + private _renderEffectAsPasses; + _name: string; + applyParameters: (postProcess: PostProcess) => void; + constructor(engine: Engine, name: string, getPostProcess: () => PostProcess, singleInstance?: boolean); + _update(): void; + addPass(renderPass: PostProcessRenderPass): void; + removePass(renderPass: PostProcessRenderPass): void; + addRenderEffectAsPass(renderEffect: PostProcessRenderEffect): void; + getPass(passName: string): void; + emptyPasses(): void; + _attachCameras(cameras: Camera): any; + _attachCameras(cameras: Camera[]): any; + _detachCameras(cameras: Camera): any; + _detachCameras(cameras: Camera[]): any; + _enable(cameras: Camera): any; + _enable(cameras: Camera[]): any; + _disable(cameras: Camera): any; + _disable(cameras: Camera[]): any; + getPostProcess(camera?: Camera): PostProcess; + private _linkParameters(); + private _linkTextures(effect); } } + declare module BABYLON { - class Tags { - static EnableFor(obj: any): void; - static DisableFor(obj: any): void; - static HasTags(obj: any): boolean; - static GetTags(obj: any): any; - static AddTagsTo(obj: any, tagsString: string): void; - static _AddTagTo(obj: any, tag: string): void; - static RemoveTagsFrom(obj: any, tagsString: string): void; - static _RemoveTagFrom(obj: any, tag: string): void; - static MatchesQuery(obj: any, tagsQuery: string): boolean; + class PostProcessRenderPass { + private _enabled; + private _renderList; + private _renderTexture; + private _scene; + private _refCount; + _name: string; + constructor(scene: Scene, name: string, size: number, renderList: Mesh[], beforeRender: () => void, afterRender: () => void); + _incRefCount(): number; + _decRefCount(): number; + _update(): void; + setRenderList(renderList: Mesh[]): void; + getRenderTexture(): RenderTargetTexture; } } + declare module BABYLON { - interface IAnimatable { - animations: Array; - } - interface ISize { - width: number; - height: number; - } - class Tools { - static BaseUrl: string; - static SetImmediate(action: () => void): void; - static IsExponantOfTwo(value: number): boolean; - static GetExponantOfTwo(value: number, max: number): number; - static GetFilename(path: string): string; - static GetDOMTextContent(element: HTMLElement): string; - static ToDegrees(angle: number): number; - static ToRadians(angle: number): number; - static ExtractMinAndMaxIndexed(positions: number[], indices: number[], indexStart: number, indexCount: number): { - minimum: Vector3; - maximum: Vector3; - }; - static ExtractMinAndMax(positions: number[], start: number, count: number): { - minimum: Vector3; - maximum: Vector3; - }; - static MakeArray(obj: any, allowsNullUndefined?: boolean): Array; - static GetPointerPrefix(): string; - static QueueNewFrame(func: any): void; - static RequestFullscreen(element: any): void; - static ExitFullscreen(): void; - static CleanUrl(url: string): string; - static LoadImage(url: string, onload: any, onerror: any, database: any): HTMLImageElement; - static LoadFile(url: string, callback: (data: any) => void, progressCallBack?: () => void, database?: any, useArrayBuffer?: boolean, onError?: () => void): void; - static ReadFileAsDataURL(fileToLoad: any, callback: any, progressCallback: any): void; - static ReadFile(fileToLoad: any, callback: any, progressCallBack: any, useArrayBuffer?: boolean): void; - static Clamp(value: number, min?: number, max?: number): number; - static Sign(value: number): number; - static Format(value: number, decimals?: number): string; - static CheckExtends(v: Vector3, min: Vector3, max: Vector3): void; - static WithinEpsilon(a: number, b: number, epsilon?: number): boolean; - static DeepCopy(source: any, destination: any, doNotCopyList?: string[], mustCopyList?: string[]): void; - static IsEmpty(obj: any): boolean; - static RegisterTopRootEvents(events: { - name: string; - handler: EventListener; - }[]): void; - static UnregisterTopRootEvents(events: { - name: string; - handler: EventListener; - }[]): void; - static DumpFramebuffer(width: number, height: number, engine: Engine): void; - static CreateScreenshot(engine: Engine, camera: Camera, size: any): void; - static ValidateXHRData(xhr: XMLHttpRequest, dataType?: number): boolean; - private static _NoneLogLevel; - private static _MessageLogLevel; - private static _WarningLogLevel; - private static _ErrorLogLevel; - private static _LogCache; - static OnNewCacheEntry: (entry: string) => void; - static NoneLogLevel: number; - static MessageLogLevel: number; - static WarningLogLevel: number; - static ErrorLogLevel: number; - static AllLogLevel: number; - private static _AddLogEntry(entry); - private static _FormatMessage(message); - static Log: (message: string) => void; - private static _LogDisabled(message); - private static _LogEnabled(message); - static Warn: (message: string) => void; - private static _WarnDisabled(message); - private static _WarnEnabled(message); - static Error: (message: string) => void; - private static _ErrorDisabled(message); - private static _ErrorEnabled(message); - static LogCache: string; - static LogLevels: number; - private static _PerformanceNoneLogLevel; - private static _PerformanceUserMarkLogLevel; - private static _PerformanceConsoleLogLevel; - private static _performance; - static PerformanceNoneLogLevel: number; - static PerformanceUserMarkLogLevel: number; - static PerformanceConsoleLogLevel: number; - static PerformanceLogLevel: number; - static _StartPerformanceCounterDisabled(counterName: string, condition?: boolean): void; - static _EndPerformanceCounterDisabled(counterName: string, condition?: boolean): void; - static _StartUserMark(counterName: string, condition?: boolean): void; - static _EndUserMark(counterName: string, condition?: boolean): void; - static _StartPerformanceConsole(counterName: string, condition?: boolean): void; - static _EndPerformanceConsole(counterName: string, condition?: boolean): void; - static StartPerformanceCounter: (counterName: string, condition?: boolean) => void; - static EndPerformanceCounter: (counterName: string, condition?: boolean) => void; - static Now: number; - static GetFps(): number; - } - /** - * An implementation of a loop for asynchronous functions. - */ - class AsyncLoop { - iterations: number; - private _fn; - private _successCallback; - index: number; - private _done; - /** - * Constroctor. - * @param iterations the number of iterations. - * @param _fn the function to run each iteration - * @param _successCallback the callback that will be called upon succesful execution - * @param offset starting offset. - */ - constructor(iterations: number, _fn: (asyncLoop: AsyncLoop) => void, _successCallback: () => void, offset?: number); - /** - * Execute the next iteration. Must be called after the last iteration was finished. - */ - executeNext(): void; - /** - * Break the loop and run the success callback. - */ - breakLoop(): void; - /** - * Helper function - */ - static Run(iterations: number, _fn: (asyncLoop: AsyncLoop) => void, _successCallback: () => void, offset?: number): AsyncLoop; - /** - * A for-loop that will run a given number of iterations synchronous and the rest async. - * @param iterations total number of iterations - * @param syncedIterations number of synchronous iterations in each async iteration. - * @param fn the function to call each iteration. - * @param callback a success call back that will be called when iterating stops. - * @param breakFunction a break condition (optional) - * @param timeout timeout settings for the setTimeout function. default - 0. - * @constructor - */ - static SyncAsyncForLoop(iterations: number, syncedIterations: number, fn: (iteration: number) => void, callback: () => void, breakFunction?: () => boolean, timeout?: number): void; + class PostProcessRenderPipeline { + private _engine; + private _renderEffects; + private _renderEffectsForIsolatedPass; + private _cameras; + _name: string; + private static PASS_EFFECT_NAME; + private static PASS_SAMPLER_NAME; + constructor(engine: Engine, name: string); + addEffect(renderEffect: PostProcessRenderEffect): void; + _enableEffect(renderEffectName: string, cameras: Camera): any; + _enableEffect(renderEffectName: string, cameras: Camera[]): any; + _disableEffect(renderEffectName: string, cameras: Camera): any; + _disableEffect(renderEffectName: string, cameras: Camera[]): any; + _attachCameras(cameras: Camera, unique: boolean): any; + _attachCameras(cameras: Camera[], unique: boolean): any; + _detachCameras(cameras: Camera): any; + _detachCameras(cameras: Camera[]): any; + _enableDisplayOnlyPass(passName: any, cameras: Camera): any; + _enableDisplayOnlyPass(passName: any, cameras: Camera[]): any; + _disableDisplayOnlyPass(cameras: Camera): any; + _disableDisplayOnlyPass(cameras: Camera[]): any; + _update(): void; } } -declare module BABYLON.Internals { - interface DDSInfo { - width: number; - height: number; - mipmapCount: number; - isFourCC: boolean; - isRGB: boolean; - isLuminance: boolean; - isCube: boolean; + +declare module BABYLON { + class PostProcessRenderPipelineManager { + private _renderPipelines; + constructor(); + addPipeline(renderPipeline: PostProcessRenderPipeline): void; + attachCamerasToRenderPipeline(renderPipelineName: string, cameras: Camera, unique?: boolean): any; + attachCamerasToRenderPipeline(renderPipelineName: string, cameras: Camera[], unique?: boolean): any; + detachCamerasFromRenderPipeline(renderPipelineName: string, cameras: Camera): any; + detachCamerasFromRenderPipeline(renderPipelineName: string, cameras: Camera[]): any; + enableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera): any; + enableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera[]): any; + disableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera): any; + disableEffectInPipeline(renderPipelineName: string, renderEffectName: string, cameras: Camera[]): any; + enableDisplayOnlyPassInPipeline(renderPipelineName: string, passName: string, cameras: Camera): any; + enableDisplayOnlyPassInPipeline(renderPipelineName: string, passName: string, cameras: Camera[]): any; + disableDisplayOnlyPassInPipeline(renderPipelineName: string, cameras: Camera): any; + disableDisplayOnlyPassInPipeline(renderPipelineName: string, cameras: Camera[]): any; + update(): void; } - class DDSTools { - static GetDDSInfo(arrayBuffer: any): DDSInfo; - private static GetRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); - private static GetRGBArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); - private static GetLuminanceArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer); - static UploadDDSLevels(gl: WebGLRenderingContext, ext: any, arrayBuffer: any, info: DDSInfo, loadMipmaps: boolean, faces: number): void; +} + +declare module BABYLON { + class CustomProceduralTexture extends ProceduralTexture { + private _animate; + private _time; + private _config; + private _texturePath; + constructor(name: string, texturePath: any, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + private loadJson(jsonUrl); + isReady(): boolean; + render(useCameraPostProcess?: boolean): void; + updateTextures(): void; + updateShaderUniforms(): void; + animate: boolean; } } -declare module BABYLON.Internals { - class TGATools { - private static _TYPE_NO_DATA; - private static _TYPE_INDEXED; - private static _TYPE_RGB; - private static _TYPE_GREY; - private static _TYPE_RLE_INDEXED; - private static _TYPE_RLE_RGB; - private static _TYPE_RLE_GREY; - private static _ORIGIN_MASK; - private static _ORIGIN_SHIFT; - private static _ORIGIN_BL; - private static _ORIGIN_BR; - private static _ORIGIN_UL; - private static _ORIGIN_UR; - static GetTGAHeader(data: Uint8Array): any; - static UploadContent(gl: WebGLRenderingContext, data: Uint8Array): void; - static _getImageData8bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; - static _getImageData16bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; - static _getImageData24bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; - static _getImageData32bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; - static _getImageDataGrey8bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; - static _getImageDataGrey16bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array; + +declare module BABYLON { + class ProceduralTexture extends Texture { + private _size; + _generateMipMaps: boolean; + isEnabled: boolean; + private _doNotChangeAspectRatio; + private _currentRefreshId; + private _refreshRate; + private _vertexBuffer; + private _indexBuffer; + private _effect; + private _vertexDeclaration; + private _vertexStrideSize; + private _uniforms; + private _samplers; + private _fragment; + _textures: Texture[]; + private _floats; + private _floatsArrays; + private _colors3; + private _colors4; + private _vectors2; + private _vectors3; + private _matrices; + private _fallbackTexture; + private _fallbackTextureUsed; + constructor(name: string, size: any, fragment: any, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + reset(): void; + isReady(): boolean; + resetRefreshCounter(): void; + setFragment(fragment: any): void; + refreshRate: number; + _shouldRender(): boolean; + getRenderSize(): number; + resize(size: any, generateMipMaps: any): void; + private _checkUniform(uniformName); + setTexture(name: string, texture: Texture): ProceduralTexture; + setFloat(name: string, value: number): ProceduralTexture; + setFloats(name: string, value: number[]): ProceduralTexture; + setColor3(name: string, value: Color3): ProceduralTexture; + setColor4(name: string, value: Color4): ProceduralTexture; + setVector2(name: string, value: Vector2): ProceduralTexture; + setVector3(name: string, value: Vector3): ProceduralTexture; + setMatrix(name: string, value: Matrix): ProceduralTexture; + render(useCameraPostProcess?: boolean): void; + clone(): ProceduralTexture; + dispose(): void; } } + declare module BABYLON { - enum JoystickAxis { - X = 0, - Y = 1, - Z = 2, + class WoodProceduralTexture extends ProceduralTexture { + private _ampScale; + private _woodColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + ampScale: number; + woodColor: Color3; } - class VirtualJoystick { - reverseLeftRight: boolean; - reverseUpDown: boolean; - deltaPosition: Vector3; - pressed: boolean; - private static _globalJoystickIndex; - private static vjCanvas; - private static vjCanvasContext; - private static vjCanvasWidth; - private static vjCanvasHeight; - private static halfWidth; - private static halfHeight; - private _action; - private _axisTargetedByLeftAndRight; - private _axisTargetedByUpAndDown; - private _joystickSensibility; - private _inversedSensibility; - private _rotationSpeed; - private _inverseRotationSpeed; - private _rotateOnAxisRelativeToMesh; - private _joystickPointerID; - private _joystickColor; - private _joystickPointerPos; - private _joystickPointerStartPos; - private _deltaJoystickVector; - private _leftJoystick; - private _joystickIndex; - private _touches; - constructor(leftJoystick?: boolean); - setJoystickSensibility(newJoystickSensibility: number): void; - private _onPointerDown(e); - private _onPointerMove(e); - private _onPointerUp(e); - /** - * Change the color of the virtual joystick - * @param newColor a string that must be a CSS color value (like "red") or the hexa value (like "#FF0000") - */ - setJoystickColor(newColor: string): void; - setActionOnTouch(action: () => any): void; - setAxisForLeftRight(axis: JoystickAxis): void; - setAxisForUpDown(axis: JoystickAxis): void; - private _clearCanvas(); - private _drawVirtualJoystick(); - releaseCanvas(): void; + class FireProceduralTexture extends ProceduralTexture { + private _time; + private _speed; + private _autoGenerateTime; + private _fireColors; + private _alphaThreshold; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + render(useCameraPostProcess?: boolean): void; + static PurpleFireColors: Color3[]; + static GreenFireColors: Color3[]; + static RedFireColors: Color3[]; + static BlueFireColors: Color3[]; + fireColors: Color3[]; + time: number; + speed: Vector2; + alphaThreshold: number; + } + class CloudProceduralTexture extends ProceduralTexture { + private _skyColor; + private _cloudColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + skyColor: Color4; + cloudColor: Color4; + } + class GrassProceduralTexture extends ProceduralTexture { + private _grassColors; + private _herb1; + private _herb2; + private _herb3; + private _groundColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + grassColors: Color3[]; + groundColor: Color3; + } + class RoadProceduralTexture extends ProceduralTexture { + private _roadColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + roadColor: Color3; + } + class BrickProceduralTexture extends ProceduralTexture { + private _numberOfBricksHeight; + private _numberOfBricksWidth; + private _jointColor; + private _brickColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + numberOfBricksHeight: number; + numberOfBricksWidth: number; + jointColor: Color3; + brickColor: Color3; + } + class MarbleProceduralTexture extends ProceduralTexture { + private _numberOfTilesHeight; + private _numberOfTilesWidth; + private _amplitude; + private _marbleColor; + private _jointColor; + constructor(name: string, size: number, scene: Scene, fallbackTexture?: Texture, generateMipMaps?: boolean); + updateShaderUniforms(): void; + numberOfTilesHeight: number; + numberOfTilesWidth: number; + jointColor: Color3; + marbleColor: Color3; } } diff --git a/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.client.ts b/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.client.ts index ea39bc9b..9ce2e67e 100644 --- a/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.client.ts +++ b/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.client.ts @@ -763,7 +763,13 @@ module VORLON { * as the user switches between clients on the dashboard. */ public refresh(): void { - this._sendScenesData(); + if (this.engine) { + this._sendScenesData(); + } else { + this.engine = this._getBabylonEngine(); + this.scenes = this.engine.scenes; + this._sendScenesData(); + } } /** @@ -775,13 +781,13 @@ module VORLON { } else { } - document.addEventListener("DOMContentLoaded", () => { + //document.addEventListener("DOMContentLoaded", () => { this.engine = this._getBabylonEngine(); if (this.engine) { this.scenes = this.engine.scenes; this.refresh(); } - }); + //}); } /** diff --git a/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.dashboard.ts b/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.dashboard.ts index 8fa6f884..aa3537cf 100644 --- a/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.dashboard.ts +++ b/Plugins/Vorlon/plugins/babylonInspector/vorlon.babylonInspector.dashboard.ts @@ -1115,7 +1115,7 @@ module VORLON { constructor() { // name , html for dash css for dash super("babylonInspector", "control.html", "control.css"); - this._ready = true; + this._ready = false; this.id = 'BABYLONINSPECTOR'; this._dataTreeGenerator = new DataTreeGenerator(this); this.treeRoot = null; diff --git a/Plugins/Vorlon/plugins/device/control.html b/Plugins/Vorlon/plugins/device/control.html new file mode 100644 index 00000000..d6868019 --- /dev/null +++ b/Plugins/Vorlon/plugins/device/control.html @@ -0,0 +1,80 @@ +
+

Device Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Viewport
Aspect Ratio
Width
Width (in EMs)
Meta Viewport Tag
Screen Size
Screen Width
Screen Available Width
Window Inner Width
Body Client Width
Resolution
Dots Per Inch (DPI)
Dots Per Pixel (DPPX)
Dots Per Centimeter (DPCM)
Miscellaneous
Root Font Size
Pixel Ratio
User Agent
+
diff --git a/Plugins/Vorlon/plugins/device/control.less b/Plugins/Vorlon/plugins/device/control.less new file mode 100644 index 00000000..2a79fb35 --- /dev/null +++ b/Plugins/Vorlon/plugins/device/control.less @@ -0,0 +1,27 @@ +#device-information { + padding: 20px; + + & > table { + width: 100%; + margin: 20px 0 30px; + + th, td { + padding: 5px 10px; + text-align: left; + } + + th { + background: #f7f7f7; + border-bottom: 1px solid #ddd; + font-weight: 400; + } + + td { + border-bottom: 1px solid #eee; + + &:first-child { + width: 30%; + } + } + } +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/device/res.min.js b/Plugins/Vorlon/plugins/device/res.min.js new file mode 100644 index 00000000..f7aa22ea --- /dev/null +++ b/Plugins/Vorlon/plugins/device/res.min.js @@ -0,0 +1,6 @@ +/*! + * res 0.3.0+201410120233 + * https://github.com/ryanve/res + * MIT License, 2014 Ryan Van Etten + */ +!function(a,b,c){"undefined"!=typeof module&&module.exports?module.exports=c(require):a[b]=c(function(b){return a[b]})}(this,"res",function(a){function b(b){return a("actual")("resolution",b.valueOf(),e[b])}function c(){return"undefined"==typeof window?0:+window.devicePixelRatio||Math.sqrt(screen.deviceXDPI*screen.deviceYDPI)/e.dpi||0}function d(a){var d=e[a];b[a]=1==d?c:function(){return c()*d}}var e={dppx:1,dpi:96,dpcm:96/2.54};return d("dppx"),d("dpcm"),d("dpi"),b}); \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/device/verge.min.js b/Plugins/Vorlon/plugins/device/verge.min.js new file mode 100644 index 00000000..acec14b0 --- /dev/null +++ b/Plugins/Vorlon/plugins/device/verge.min.js @@ -0,0 +1,6 @@ +/*! + * verge 1.9.1+201402130803 + * https://github.com/ryanve/verge + * MIT License 2013 Ryan Van Etten + */ +!function(a,b,c){"undefined"!=typeof module&&module.exports?module.exports=c():a[b]=c()}(this,"verge",function(){function a(){return{width:k(),height:l()}}function b(a,b){var c={};return b=+b||0,c.width=(c.right=a.right+b)-(c.left=a.left-b),c.height=(c.bottom=a.bottom+b)-(c.top=a.top-b),c}function c(a,c){return a=a&&!a.nodeType?a[0]:a,a&&1===a.nodeType?b(a.getBoundingClientRect(),c):!1}function d(b){b=null==b?a():1===b.nodeType?c(b):b;var d=b.height,e=b.width;return d="function"==typeof d?d.call(b):d,e="function"==typeof e?e.call(b):e,e/d}var e={},f="undefined"!=typeof window&&window,g="undefined"!=typeof document&&document,h=g&&g.documentElement,i=f.matchMedia||f.msMatchMedia,j=i?function(a){return!!i.call(f,a).matches}:function(){return!1},k=e.viewportW=function(){var a=h.clientWidth,b=f.innerWidth;return b>a?b:a},l=e.viewportH=function(){var a=h.clientHeight,b=f.innerHeight;return b>a?b:a};return e.mq=j,e.matchMedia=i?function(){return i.apply(f,arguments)}:function(){return{}},e.viewport=a,e.scrollX=function(){return f.pageXOffset||h.scrollLeft},e.scrollY=function(){return f.pageYOffset||h.scrollTop},e.rectangle=c,e.aspect=d,e.inX=function(a,b){var d=c(a,b);return!!d&&d.right>=0&&d.left<=k()},e.inY=function(a,b){var d=c(a,b);return!!d&&d.bottom>=0&&d.top<=l()},e.inViewport=function(a,b){var d=c(a,b);return!!d&&d.bottom>=0&&d.right>=0&&d.top<=l()&&d.left<=k()},e}); \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/device/vorlon.device.client.ts b/Plugins/Vorlon/plugins/device/vorlon.device.client.ts new file mode 100644 index 00000000..ec537e26 --- /dev/null +++ b/Plugins/Vorlon/plugins/device/vorlon.device.client.ts @@ -0,0 +1,185 @@ +module VORLON { + declare var $: any; + declare var res: any; + declare var verge: any; + + export class DeviceClient extends ClientPlugin { + constructor() { + super("device"); + this._ready = true; + } + + //Return unique id for your plugin + public getID(): string { + return "DEVICE"; + } + + public refresh(): void { + // override this method with cleanup work that needs to happen + // as the user switches between clients on the dashboard + + if (typeof verge === 'undefined' || typeof res === 'undefined') { + return; + } + + //sometimes refresh is called before document was loaded + if (!document.body){ + setTimeout(() => { + this.refresh(); + }, 200); + return; + } + + // user agent string + var userAgent = this.getUserAgent(); + // console.info('User agent:', userAgent); + + // meta viewport tag + var metaViewport = this.getMetaViewport(); + // console.info('Meta viewport:', metaViewport); + + // screen widths + var screenWidths = this.getScreenWidths(); + // console.info('Screen widths', screenWidths); + + // screen resolution + var resolution = this.getResolution(); + // console.info('Resolution', resolution); + + // root font size + var rootFontSize:any = this.getRootFontSize(); + // console.info('Root font size:', rootFontSize); + + // viewport + var viewport = this.getViewport(); + // console.info('Viewport', viewport); + + // pixel ratio + var pixelRatio = this.getPixelRatio(); + // console.info('Pixel ratio:', pixelRatio); + + var data = { + userAgent: userAgent, + metaViewport: metaViewport, + screenWidths: screenWidths, + resolution: resolution, + rootFontSize: rootFontSize, + viewport: viewport, + pixelRatio: pixelRatio + } + + var message = { + type: 'full', // 'full' specifies that this message contains all device information + data: data + } + + this.sendToDashboard(message); + } + + public refreshResize(): void { + if (typeof verge === 'undefined') { + return; + } + + var data = { + screenWidths: this.getScreenWidths(), + viewport: this.getViewport(), + } + + var message = { + type: 'resize', // 'resize' specifies that this message only contains data that changed due to a window resize + data: data + }; + + this.sendToDashboard(message); + + // console.log('Device information refreshed for resize.'); + } + + public getUserAgent(): string { + return navigator.userAgent; + } + + public getMetaViewport(): string { + var metaViewportTag: any = document.querySelector('meta[name="viewport"]'); + var metaViewport; + if ((metaViewport !== null || metaViewport === []) && typeof metaViewportTag != 'undefined' && metaViewportTag != null) { + metaViewport = metaViewportTag.outerHTML; + } else { + console.log('No meta viewport tag found.'); + metaViewport = 'No meta viewport tag found.'; + } + + return metaViewport; + } + + public getScreenWidths(): any { + return { + screenWidth: screen.width, + screenAvailWidth: screen.availWidth, + windowInnerWidth: window.innerWidth, + bodyClientWidth: document.body.clientWidth, + } + } + + public getResolution(): any { + return { + dpi: res.dpi(), + dppx: res.dppx(), + dpcm: res.dpcm() + } + } + + public getRootFontSize(): number { + // returns the root font size in pixels + var htmlRoot = document.getElementsByTagName('html')[0]; + return parseInt(window.getComputedStyle(htmlRoot, null).getPropertyValue('font-size')); + } + + public getViewport(): any { + var rootFontSize: number = this.getRootFontSize(); + return { + aspectRatio: verge.aspect(screen), + width: verge.viewportW(), + widthEm: (verge.viewportW() / rootFontSize).toFixed(0), + } + } + + public getPixelRatio(): any { + // pixel ratio refers to ratio between physical pixels and logical pixels + // see http://stackoverflow.com/a/8785677 for more information + var pixelRatio: any = window.devicePixelRatio || window.screen.availWidth / document.documentElement.clientWidth; + pixelRatio = pixelRatio.toFixed(2); + return pixelRatio; + } + + // This code will run on the client ////////////////////// + + // Start the clientside code + public startClientSide(): void { + // load the "res" and "verge" libraries + + this._loadNewScriptAsync("res.min.js",() => { + if (res) { + this.refresh(); + } + }); + this._loadNewScriptAsync("verge.min.js",() => { + if (verge) { + this.refresh(); + } + }); + + // update certain information when the page is resized + window.addEventListener('resize', this.refreshResize.bind(this)); + } + + // Handle messages from the dashboard, on the client + public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { + // the dashboard shouldn't be sending anything + } + } + + // Register the plugin with vorlon core + Core.RegisterClientPlugin(new DeviceClient()); +} diff --git a/Plugins/Vorlon/plugins/device/vorlon.device.dashboard.ts b/Plugins/Vorlon/plugins/device/vorlon.device.dashboard.ts new file mode 100644 index 00000000..c2ad459b --- /dev/null +++ b/Plugins/Vorlon/plugins/device/vorlon.device.dashboard.ts @@ -0,0 +1,91 @@ + module VORLON { + export class DeviceDashboard extends DashboardPlugin { + + //Do any setup you need, call super to configure + //the plugin with html and css for the dashboard + constructor() { + // name , html for dash css for dash + super("device", "control.html", "control.css"); + this._ready = true; + } + + //Return unique id for your plugin + public getID(): string { + return "DEVICE"; + } + + // This code will run on the dashboard ////////////////////// + + // Start dashboard code + // uses _insertHtmlContentAsync to insert the control.html content + // into the dashboard + private _table: HTMLTableElement; + + public startDashboardSide(div: HTMLDivElement = null): void { + this._insertHtmlContentAsync(div, (filledDiv) => { + this._table = filledDiv.querySelector('table'); + }) + } + + // called to update the HTML with a complete set of data + public update(data: any): void { + // resolution + var resolution = data.resolution; + this.setTableValue('dpi', this.round2decimals(resolution.dpi).toString()); + this.setTableValue('dppx', this.round2decimals(resolution.dppx).toString()); + this.setTableValue('dpcm', this.round2decimals(resolution.dpcm).toString()); + + // miscellaneous + this.setTableValue('root-font-size', data.rootFontSize + 'px'); + this.setTableValue('pixel-ratio', this.round2decimals(data.pixelRatio).toString()); + this.setTableValue('user-agent', data.userAgent); + + + this.updateResize(data); + } + + // called to update the HTML with a set of data stemming from a window resize + public updateResize(data: any): void { + // viewport + var viewport = data.viewport; + this.setTableValue('aspect-ratio', this.round2decimals(viewport.aspectRatio).toString()); + this.setTableValue('width', viewport.width + 'px'); + this.setTableValue('width-em', viewport.widthEm + 'em'); + this.setTableValue('meta-viewport-tag', data.metaViewport); + + // screen width + var screenWidths = data.screenWidths + this.setTableValue('screen-width', screenWidths.screenWidth + 'px'); + this.setTableValue('screen-available-width', screenWidths.screenAvailWidth + 'px'); + this.setTableValue('window-inner-width', screenWidths.windowInnerWidth + 'px'); + this.setTableValue('body-client-width', screenWidths.bodyClientWidth + 'px'); + } + + public setTableValue(cssClass: string, value: string): void { + if (this._table) + this._table.querySelector('.' + cssClass).textContent = value; + } + + private round2decimals(value: any):number{ + return (Math.round(value * 100) / 100); + } + + // When we get a message from the client, just show it + public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + var data = receivedObject.data; + var udpateType = receivedObject.type; + + switch (udpateType) { + case 'full': + this.update(data); + break; + case 'resize': + this.updateResize(data); + break; + } + } + } + + //Register the plugin with vorlon core + Core.RegisterDashboardPlugin(new DeviceDashboard()); +} diff --git a/Plugins/Vorlon/plugins/domExplorer/control.html b/Plugins/Vorlon/plugins/domExplorer/control.html index 2b0474bd..e356a4fb 100644 --- a/Plugins/Vorlon/plugins/domExplorer/control.html +++ b/Plugins/Vorlon/plugins/domExplorer/control.html @@ -10,6 +10,8 @@
Refresh + +
diff --git a/Plugins/Vorlon/plugins/domExplorer/control.less b/Plugins/Vorlon/plugins/domExplorer/control.less index 4e35cadd..7b0a705d 100644 --- a/Plugins/Vorlon/plugins/domExplorer/control.less +++ b/Plugins/Vorlon/plugins/domExplorer/control.less @@ -1,10 +1,12 @@ +@import "../vorlontheme.less"; + .plugin-dom { @bracketColor: #BBB; - @nodeNameColor: #6b2d81; + @nodeNameColor: @lightpurple; @nodeTextColor: #111; @attributeNameColor: #ed2424; @attributeValueColor: #007ca7; - @quotesColor: #6b2d81; + @quotesColor: @lightpurple; [button-block] { display: block; @@ -315,7 +317,11 @@ } } } - +.notexpansible{ + .treeNodeButton { + display: none; + } +} .treeNodeButton { display: inline-block; color: #444; diff --git a/Plugins/Vorlon/plugins/domExplorer/readme.md b/Plugins/Vorlon/plugins/domExplorer/readme.md new file mode 100644 index 00000000..c1d09cf4 --- /dev/null +++ b/Plugins/Vorlon/plugins/domExplorer/readme.md @@ -0,0 +1,4 @@ +## DOM Explorer +Inspect the DOM tree and its corresponding styles. + +[More information](http://blogs.msdn.com/b/eternalcoding/archive/2015/08/06/vorlon-js-focus-on-dom-explorer.aspx) \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.client.ts b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.client.ts index 4e451d02..2806ab1f 100644 --- a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.client.ts +++ b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.client.ts @@ -8,10 +8,11 @@ private _globalloadactive = false; private _overlay: HTMLElement; private _observerMutationObserver; - + private _overlayInspect: HTMLElement; constructor() { super("domExplorer"); this._id = "DOM"; + //this.debug = true; this._ready = false; } @@ -78,6 +79,9 @@ } private _packageNode(node: any): PackagedNode { + if (!node) + return; + var packagedNode = { id: node.id, type: node.nodeType, @@ -85,15 +89,16 @@ classes: node.className, content: null, hasChildNodes: false, - attributes: node.attributes ? Array.prototype.map.call(node.attributes,(attr) => { + attributes: node.attributes ? Array.prototype.map.call(node.attributes, (attr) => { return [attr.name, attr.value]; }) : [], - styles: DOMExplorerClient.GetAppliedStyles(node), + //styles: DOMExplorerClient.GetAppliedStyles(node), children: [], isEmpty: false, rootHTML: null, internalId: VORLON.Tools.CreateGUID() }; + if (node.innerHTML === "") { packagedNode.isEmpty = true; } @@ -108,7 +113,7 @@ } else { if (!node.__vorlon) { - node.__vorlon = {}; + node.__vorlon = {}; } node.__vorlon.internalId = packagedNode.internalId; } @@ -125,7 +130,8 @@ var node = root.childNodes[index]; var packagedNode = this._packageNode(node); var b = false; - if (node.childNodes && node.childNodes.length > 1) { + + if (node.childNodes && node.childNodes.length > 1 || (node && node.nodeName && (node.nodeName.toLowerCase() === "script" || node.nodeName.toLowerCase() === "style"))) { packagedNode.hasChildNodes = true; } else if (withChildsNodes || node.childNodes.length == 1) { @@ -136,7 +142,10 @@ this._packageDOM(node, packagedNode, withChildsNodes, highlightElementID); } - if ((node).__vorlon.ignore) { return; } + if ((node).__vorlon && (node).__vorlon.ignore) { + return; + } + packagedObject.children.push(packagedNode); if (highlightElementID === packagedNode.internalId) { highlightElementID = ""; @@ -162,6 +171,10 @@ } private _getElementByInternalId(internalId: string, node: HTMLElement): any { + if (!node) { + return null; + } + if (node.__vorlon && node.__vorlon.internalId === internalId) { return node; } @@ -251,10 +264,11 @@ var p = element.getBoundingClientRect(); var w = element.offsetWidth; var h = element.offsetHeight; - return { x: p.top, y: p.left, width: w, height: h }; + //console.log("check offset for highlight " + p.top + "," + p.left); + return { x: p.top - element.scrollTop, y: p.left - element.scrollLeft, width: w, height: h }; } - public setClientSelectedElement(elementId: string) { + public setClientHighlightedElement(elementId: string) { var element = this._getElementByInternalId(elementId, document.documentElement); if (!element) { @@ -263,10 +277,10 @@ if (!this._overlay) { this._overlay = document.createElement("div"); this._overlay.id = "vorlonOverlay"; - this._overlay.style.position = "absolute"; + this._overlay.style.position = "fixed"; this._overlay.style.backgroundColor = "rgba(255,255,0,0.4)"; this._overlay.style.pointerEvents = "none"; - ( this._overlay).__vorlon = { ignore: true }; + (this._overlay).__vorlon = { ignore: true }; document.body.appendChild(this._overlay); } this._overlay.style.display = "block"; @@ -277,7 +291,7 @@ this._overlay.style.height = position.height + "px"; } - public unselectClientElement(internalId?: string) { + public unhighlightClientElement(internalId?: string) { if (this._overlay) this._overlay.style.display = "none"; } @@ -287,11 +301,67 @@ } refresh(): void { + //sometimes refresh is called before document was loaded + if (!document.body) { + setTimeout(() => { + this.refresh(); + }, 200); + return; + } + var packagedObject = this._packageNode(document.documentElement); this._packageDOM(document.documentElement, packagedObject, this._globalloadactive, null); this.sendCommandToDashboard('init', packagedObject); } + inspect(): void { + if (document.elementFromPoint) { + if (this._overlayInspect) + return; + this.trace("INSPECT"); + this._overlayInspect = document.createElement("DIV"); + this._overlayInspect.__vorlon = { ignore: true }; + this._overlayInspect.style.position = "fixed"; + this._overlayInspect.style.left = "0"; + this._overlayInspect.style.right = "0"; + this._overlayInspect.style.top = "0"; + this._overlayInspect.style.bottom = "0"; + this._overlayInspect.style.zIndex = "5000000000000000"; + this._overlayInspect.style.touchAction = "manipulation"; + this._overlayInspect.style.backgroundColor = "rgba(255,0,0,0.3)"; + document.body.appendChild(this._overlayInspect); + var event = "mousedown"; + if (this._overlayInspect.onpointerdown !== undefined) { + event = "pointerdown"; + } + this._overlayInspect.addEventListener(event, (arg) => { + var evt = arg; + this.trace("tracking element at " + evt.clientX + "/" + evt.clientY); + this._overlayInspect.parentElement.removeChild(this._overlayInspect); + var el = document.elementFromPoint(evt.clientX, evt.clientY); + if (el) { + this.trace("element found"); + this.openElementInDashboard(el); + } else { + this.trace("element not found"); + } + this._overlayInspect = null; + }); + } else { + //TODO : send message back to dashboard and disable button + this.trace("VORLON, inspection not supported"); + } + } + + openElementInDashboard(element: Element) { + if (element) { + var parentId = this.getFirstParentWithInternalId(element); + if (parentId) { + this.refreshbyId(parentId, this._packageNode(element).internalId); + } + } + } + setStyle(internaID: string, property: string, newValue: string): void { var element = this._getElementByInternalId(internaID, document.documentElement); element.style[property] = newValue; @@ -325,21 +395,27 @@ searchDOMBySelector(selector: string, position: number = 0) { var length = 0; - if (selector) { - var elements = document.querySelectorAll(selector); - length = elements.length; - if (elements.length) { - if (!elements[position]) - position = 0; - var parentId = this.getFirstParentWithInternalId(elements[position]); - if (parentId) { - this.refreshbyId(parentId, this._packageNode(elements[position]).internalId); - } - if (position < elements.length + 1) { - position++; + try { + if (selector) { + var elements = document.querySelectorAll(selector); + length = elements.length; + if (elements.length) { + if (!elements[position]) + position = 0; + var parentId = this.getFirstParentWithInternalId(elements[position]); + if (parentId) { + this.refreshbyId(parentId, this._packageNode(elements[position]).internalId); + } + if (position < elements.length + 1) { + position++; + } } } } + catch (e) { + + } + this.sendCommandToDashboard('searchDOMByResults', { length: length, selector: selector, position: position }); } @@ -353,7 +429,7 @@ if (attributeName) element.setAttribute(attributeName, attributeValue); if (attributeName && attributeName.indexOf('on') === 0) { - element[attributeName] = function () { + element[attributeName] = function() { try { eval(attributeValue); } catch (e) { console.error(e); } }; @@ -374,14 +450,17 @@ var element = this._getElementByInternalId(internaID, document.documentElement); element.innerHTML = value; } + + public getNodeStyle(internalID: string) { + var element = this._getElementByInternalId(internalID, document.documentElement); + if (element) { + var styles = DOMExplorerClient.GetAppliedStyles(element); + this.sendCommandToDashboard('nodeStyle', { internalID: internalID, styles: styles }); + } + } } DOMExplorerClient.prototype.ClientCommands = { - select(data: any) { - var plugin = this; - plugin.unselectClientElement(); - plugin.setClientSelectedElement(data.order); - }, getMutationObeserverAvailability() { var plugin = this; plugin.getMutationObeserverAvailability(); @@ -417,9 +496,27 @@ plugin.setElementValue(data.order, data.value); }, + select(data: any) { + var plugin = this; + plugin.unhighlightClientElement(); + plugin.setClientHighlightedElement(data.order); + plugin.getNodeStyle(data.order); + }, + unselect(data: any) { var plugin = this; - plugin.unselectClientElement(data.order); + plugin.unhighlightClientElement(data.order); + }, + + highlight(data: any) { + var plugin = this; + plugin.unhighlightClientElement(); + plugin.setClientHighlightedElement(data.order); + }, + + unhighlight(data: any) { + var plugin = this; + plugin.unhighlightClientElement(data.order); }, refreshNode(data: any) { @@ -427,11 +524,22 @@ plugin.refreshbyId(data.order); }, + getNodeStyles(data: any) { + var plugin = this; + console.log("get node style"); + //plugin.refreshbyId(data.order); + }, + refresh() { var plugin = this; plugin.refresh(); }, + inspect() { + var plugin = this; + plugin.inspect(); + }, + getInnerHTML(data: any) { var plugin = this; plugin.getInnerHTML(data.order); diff --git a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.dashboard.ts b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.dashboard.ts index f04dc864..f3799601 100644 --- a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.dashboard.ts +++ b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.dashboard.ts @@ -9,8 +9,10 @@ private _computedsection: HTMLElement; private _dashboardDiv: HTMLDivElement; public refreshButton: Element; + public inspectButton: Element; public clikedNodeID = null; public _selectedNode: DomExplorerNode; + public _highlightedNode: string; public _rootNode: DomExplorerNode; private _autorefresh: boolean = false; public _innerHTMLView: HTMLTextAreaElement; @@ -53,8 +55,12 @@ var domSettings = new DomSettings(this); this.searchDOM(); this.refreshButton = this._containerDiv.querySelector('x-action[event="refresh"]'); + this.inspectButton = this._containerDiv.querySelector('x-action[event="inspect"]'); this._stylesEditor = new DomExplorerPropertyEditor(this); - this._containerDiv.addEventListener('refresh',() => { + this._containerDiv.addEventListener('inspectFromClient',() => { + this.sendCommandToClient('inspect'); + }); + this._containerDiv.addEventListener('refresh', () => { this.sendCommandToClient('refresh'); }); this._containerDiv.addEventListener('gethtml',() => { @@ -104,16 +110,17 @@ if (node.className.match('treeNodeHeader') || node.parentElement.className.match('treeNodeClosingText')) { var hovered = this.treeDiv.querySelector('[data-hovered-tag]'); if (hovered) hovered.removeAttribute('data-hovered-tag'); - var id = $(node).data('internalid'); - if (id) { - this.hoverNode(id, true); - } - else { - var id = $(node.parentElement).data('internalid'); - if (id) { - this.hoverNode(id, true); - } - } + this.hoverNode(null, true); + // var id = $(node).data('internalid'); + // if (id) { + // this.hoverNode(id, true); + // } + // else { + // var id = $(node.parentElement).data('internalid'); + // if (id) { + // this.hoverNode(id, true); + // } + // } } }, true); @@ -283,7 +290,8 @@ } if (this._rootNode) this._rootNode.dispose(); - this.treeDiv.parentElement.classList.add('active'); + if (this.treeDiv.parentElement) + this.treeDiv.parentElement.classList.add('active'); this._rootNode = new DomExplorerNode(this, null, this.treeDiv, root); } @@ -309,18 +317,23 @@ } hoverNode(internalId: string, unselect: boolean = false) { + this._highlightedNode = internalId; if (!internalId) { - this.sendCommandToClient('unselect', { + this.sendCommandToClient('unhighlight', { order: null }); } else if (unselect) { - this.sendCommandToClient('unselect', { - order: internalId - }); + setTimeout(()=>{ + if (!this._highlightedNode){ + this.sendCommandToClient('unhighlight', { + order: internalId + }); + } + }, 5); } else { - this.sendCommandToClient('select', { + this.sendCommandToClient('highlight', { order: internalId }); } @@ -348,11 +361,18 @@ order: this._selectedNode.node.internalId }); this._stylesEditor.generateStyles(selected.node, selected.node.internalId); + //this._stylesEditor.generateStyles(selected.node, selected.node.internalId); this._innerHTMLView.value = ""; } else { this._selectedNode = null; } } + + setNodeStyle(internalId: string, styles){ + if (this._selectedNode && this._selectedNode.node.internalId == internalId){ + this._stylesEditor.generateStyles(this._selectedNode.node, this._selectedNode.node.internalId, styles); + } + } } DOMExplorerDashboard.prototype.DashboardCommands = { @@ -387,6 +407,11 @@ refreshNode(node: PackagedNode) { var plugin = this; plugin.updateDashboard(node); + }, + nodeStyle(data: any){ + console.log("dashboard receive node style", data); + var plugin = this; + plugin.setNodeStyle(data.internalID, data.styles); } } @@ -552,7 +577,7 @@ } renderDOMNodeContent() { - var root = FluentDOM.for(this.element); + var root = FluentDOM.forElement(this.element); root.append('BUTTON', 'treeNodeButton',(nodeButton) => { nodeButton.element.id = "plusbtn" + this.node.internalId; if (this.node.hasChildNodes && (!this.node.children || this.node.children.length === 0)) { @@ -653,9 +678,10 @@ menu("#treeNodeClosingText" + this.node.internalId); }); }); - } + } else { - ( this.header.querySelector('.closetag')).innerHTML = "/>"; + root.element.classList.add('notexpansible'); + ( this.header.querySelector('.closetag')).innerHTML = "/>"; } } // Main node @@ -860,7 +886,7 @@ return parentNode.appendChild(button); } - public generateStyles(node: PackagedNode, internalId: string): void { + public generateStyles(node: PackagedNode, internalId: string, styles?:any): void { this.node = node; this.internalId = internalId; this.styles = []; @@ -868,17 +894,19 @@ this.plugin.styleView.removeChild(this.plugin.styleView.lastChild); } - // Current styles - for (var index = 0; index < node.styles.length; index++) { - var style = node.styles[index]; - var splits = style.split(":"); - this.styles.push(new DomExplorerPropertyEditorItem(this, splits[0], splits[1], this.internalId)); + if (styles){ + // Current styles + for (var index = 0; index < styles.length; index++) { + var style = styles[index]; + var splits = style.split(":"); + this.styles.push(new DomExplorerPropertyEditorItem(this, splits[0], splits[1], this.internalId)); + } + // Append add style button + this._generateButton(this.plugin.styleView, "+", "styleButton", null).addEventListener('click',(e) => { + new DomExplorerPropertyEditorItem(this, "property", "value", this.internalId, true); + this.plugin.styleView.appendChild(e.target); + }); } - // Append add style button - this._generateButton(this.plugin.styleView, "+", "styleButton", null).addEventListener('click',(e) => { - new DomExplorerPropertyEditorItem(this, "property", "value", this.internalId, true); - this.plugin.styleView.appendChild(e.target); - }); } } diff --git a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.interfaces.ts b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.interfaces.ts index 6a664065..e4cd46c5 100644 --- a/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.interfaces.ts +++ b/Plugins/Vorlon/plugins/domExplorer/vorlon.domExplorer.interfaces.ts @@ -14,7 +14,7 @@ module VORLON { content: string; isEmpty: boolean; attributes: Array; - styles: any; + styles?: any; internalId: string; hasChildNodes: boolean; rootHTML: any; diff --git a/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.client.ts b/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.client.ts index 8b3ec060..7e9c857b 100644 --- a/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.client.ts +++ b/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.client.ts @@ -31,8 +31,9 @@ } private inspect(obj: any, context: any, deepness: number): ObjectDescriptor { - if (!obj) + if (!obj || typeof obj != "object") { return null; + } var objProperties = Object.getOwnPropertyNames(obj); var proto = Object.getPrototypeOf(obj); @@ -102,15 +103,17 @@ private getMessages(messages: IArguments): Array { var resmessages = []; - for (var i = 0, l = messages.length; i < l; i++) { - var msg = messages[i]; - if (typeof msg === 'string' || typeof msg === 'number') { - resmessages.push(msg); - } else { - if (msg == window || msg == document) { - resmessages.push('VORLON : object cannot be inspected, too big...'); + if (messages && messages.length > 0){ + for (var i = 0, l = messages.length; i < l; i++) { + var msg = messages[i]; + if (typeof msg === 'string' || typeof msg === 'number') { + resmessages.push(msg); } else { - resmessages.push(this.inspect(msg, msg, 0)); + if (msg == window || msg == document) { + resmessages.push('VORLON : object cannot be inspected, too big...'); + } else { + resmessages.push(this.inspect(msg, msg, 0)); + } } } } @@ -146,6 +149,10 @@ } private batchSend(items: any[]) { + if (this._pendingEntriesTimeout) { + clearTimeout(this._pendingEntriesTimeout); + this._pendingEntriesTimeout = null; + } var batch = []; for (var i = 0, l = items.length; i < l; i++) { if (batch.length < this._maxBatchSize) { @@ -159,61 +166,62 @@ } public startClientSide(): void { + this._cache = []; + this._pendingEntries = []; + // Overrides clear, log, error and warn this._hooks.clear = Tools.Hook(window.console, "clear",(): void => { this.clearClientConsole(); }); - - this._hooks.dir = Tools.Hook(window.console, "dir",(message: string): void => { - var messages = arguments; + + this._hooks.dir = Tools.Hook(window.console, "dir",(message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "dir" }; this.addEntry(data); }); - this._hooks.log = Tools.Hook(window.console, "log",(message: string): void => { - var messages = arguments; + this._hooks.log = Tools.Hook(window.console, "log", (message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "log" }; this.addEntry(data); }); - this._hooks.debug = Tools.Hook(window.console, "debug",(message: string): void => { + this._hooks.debug = Tools.Hook(window.console, "debug", (message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "debug" }; this.addEntry(data); }); - this._hooks.info = Tools.Hook(window.console, "info",(message: string): void => { + this._hooks.info = Tools.Hook(window.console, "info",(message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "info" }; this.addEntry(data); }); - this._hooks.warn = Tools.Hook(window.console, "warn",(message: string): void => { + this._hooks.warn = Tools.Hook(window.console, "warn",(message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "warn" }; this.addEntry(data); }); - this._hooks.error = Tools.Hook(window.console, "error",(message: string): void => { + this._hooks.error = Tools.Hook(window.console, "error",(message: any): void => { var data = { - messages: this.getMessages(arguments[0]), + messages: this.getMessages(message), type: "error" }; @@ -223,11 +231,11 @@ // Override Error constructor var previousError = Error; - Error = ((message: string) => { + Error = ((message: any) => { var error = new previousError(message); var data = { - messages: [message], + messages: this.getMessages(message), type: "exception" }; @@ -236,12 +244,11 @@ return error; }); - window.addEventListener('error', () => { - var err = arguments[0]; + window.addEventListener('error', (err) => { - if (err.error) { + if (err && (err).error) { //this.addEntry({ messages: [err.error.message], type: "exception" }); - this.addEntry({ messages: [err.error.stack], type: "exception" }); + this.addEntry({ messages: [(err).error.stack], type: "exception" }); } }); } @@ -259,11 +266,10 @@ } } - public refresh(): void { - this.sendCommandToDashboard("clear"); - + public refresh(): void { //delay sending cache to dashboard to let other plugins load... setTimeout(() => { + this.sendCommandToDashboard("clear"); this.batchSend(this._cache); }, 300); } diff --git a/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.dashboard.ts b/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.dashboard.ts index 90a87518..7a7a1393 100644 --- a/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.dashboard.ts +++ b/Plugins/Vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.dashboard.ts @@ -121,6 +121,7 @@ } public clearDashboard() { + this._logEntries.splice(0, this._logEntries.length); this._containerDiv.innerHTML = ''; } @@ -217,7 +218,7 @@ } public renderContent() { - if (this.contentRendered) + if (!this.obj || this.contentRendered) return; if (this.obj.proto) { @@ -318,13 +319,14 @@ this.addMessage(entry.messages[i]); } } + private addMessage(msg: any) { if (typeof msg === 'string' || typeof msg === 'number') { var elt = document.createElement('DIV'); elt.className = 'log-message text-message'; this.element.appendChild(elt); - elt.textContent = msg; + elt.textContent = msg + ''; } else { var obj = new InteractiveConsoleObject(this.element, msg, true); this.objects.push(obj); diff --git a/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.client.ts b/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.client.ts index 2da34d33..7342d9d8 100644 --- a/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.client.ts +++ b/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.client.ts @@ -8,104 +8,125 @@ constructor() { super("modernizrReport"); this._ready = false; + this._id = "MODERNIZR"; + //this.debug = true; } - public getID(): string { - return "MODERNIZR"; + public startClientSide(): void { + this.loadModernizrFeatures(); + } + + public loadModernizrFeatures(){ + this.trace("loading modernizr script"); + this._loadNewScriptAsync("modernizr.js", () => { + this.trace("modernizr script loaded"); + this.checkSupportedFeatures(); + }, true); } - public startClientSide(): void { - this._loadNewScriptAsync("modernizr.js",() => { - if (Modernizr) { - this.supportedFeatures.push({ featureName: "Application cache", isSupported: Modernizr.applicationcache, type: "html" }); - this.supportedFeatures.push({ featureName: "Audio tag", isSupported: Modernizr.audio, type: "html" }); - this.supportedFeatures.push({ featureName: "background-size", isSupported: Modernizr.backgroundsize, type: "css" }); - this.supportedFeatures.push({ featureName: "border-image", isSupported: Modernizr.borderimage, type: "css" }); - this.supportedFeatures.push({ featureName: "border-radius", isSupported: Modernizr.borderradius, type: "css" }); - this.supportedFeatures.push({ featureName: "box-shadow", isSupported: Modernizr.boxshadow, type: "css" }); - this.supportedFeatures.push({ featureName: "canvas", isSupported: Modernizr.canvas, type: "html" }); - this.supportedFeatures.push({ featureName: "canvas text", isSupported: Modernizr.canvastext, type: "html" }); - this.supportedFeatures.push({ featureName: "CSS Animations", isSupported: Modernizr.cssanimations, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Columns", isSupported: Modernizr.csscolumns, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Gradients", isSupported: Modernizr.cssgradients, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Reflections", isSupported: Modernizr.cssreflections, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Transforms", isSupported: Modernizr.csstransforms, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Transforms 3d", isSupported: Modernizr.csstransforms3d, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Transitions", isSupported: Modernizr.csstransitions, type: "css" }); - this.supportedFeatures.push({ featureName: "Drag'n'drop", isSupported: Modernizr.draganddrop, type: "html" }); - this.supportedFeatures.push({ featureName: "Flexbox", isSupported: Modernizr.flexbox, type: "css" }); - this.supportedFeatures.push({ featureName: "@font-face", isSupported: Modernizr.fontface, type: "css" }); - this.supportedFeatures.push({ featureName: "CSS Generated Content (:before/:after)", isSupported: Modernizr.generatedcontent, type: "css" }); - this.supportedFeatures.push({ featureName: "Geolocation API", isSupported: Modernizr.geolocation, type: "misc" }); - this.supportedFeatures.push({ featureName: "hashchange Event", isSupported: Modernizr.hashchange, type: "html" }); - this.supportedFeatures.push({ featureName: "History Management", isSupported: Modernizr.history, type: "html" }); - this.supportedFeatures.push({ featureName: "Color Values hsla()", isSupported: Modernizr.hsla, type: "css" }); - this.supportedFeatures.push({ featureName: "IndexedDB", isSupported: Modernizr.indexeddb, type: "html" }); - this.supportedFeatures.push({ featureName: "Inline SVG in HTML5", isSupported: Modernizr.inlinesvg, type: "misc" }); - this.supportedFeatures.push({ featureName: "Input Attribute autocomplete", isSupported: Modernizr.input.autocomplete, type: "html" }); - /* TO DO: Inputs... */ - this.supportedFeatures.push({ featureName: "localStorage", isSupported: Modernizr.localstorage, type: "html" }); - this.supportedFeatures.push({ featureName: "Multiple backgrounds", isSupported: Modernizr.multiplebgs, type: "css" }); - this.supportedFeatures.push({ featureName: "opacity", isSupported: Modernizr.opacity, type: "css" }); - this.supportedFeatures.push({ featureName: "Cross-window Messaging", isSupported: Modernizr.postmessage, type: "html" }); - this.supportedFeatures.push({ featureName: "Color Values rgba()", isSupported: Modernizr.rgba, type: "css" }); - this.supportedFeatures.push({ featureName: "sessionStorage", isSupported: Modernizr.sessionstorage, type: "html" }); - this.supportedFeatures.push({ featureName: "SVG SMIL animation", isSupported: Modernizr.smil, type: "misc" }); - this.supportedFeatures.push({ featureName: "SVG", isSupported: Modernizr.svg, type: "misc" }); - this.supportedFeatures.push({ featureName: "SVG Clipping Paths", isSupported: Modernizr.svgclippaths, type: "misc" }); - this.supportedFeatures.push({ featureName: "text-shadow", isSupported: Modernizr.textshadow, type: "css" }); - this.supportedFeatures.push({ featureName: "Touch Events", isSupported: Modernizr.touch, type: "misc" }); - this.supportedFeatures.push({ featureName: "Video", isSupported: Modernizr.video, type: "html" }); - this.supportedFeatures.push({ featureName: "WebGL", isSupported: Modernizr.webgl, type: "misc" }); - this.supportedFeatures.push({ featureName: "Web Sockets", isSupported: ("WebSocket" in window), type: "html" }); - this.supportedFeatures.push({ featureName: "Web SQL Database", isSupported: Modernizr.websqldatabase, type: "html" }); - this.supportedFeatures.push({ featureName: "Web Workers", isSupported: Modernizr.webworkers, type: "html" }); - this.supportedFeatures.push({ featureName: "A [download] attribute", isSupported: Modernizr.adownload, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Mozilla Audio Data API", isSupported: Modernizr.audiodata, type: "noncore" }); - this.supportedFeatures.push({ featureName: "HTML5 Web Audio API", isSupported: Modernizr.webaudio, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Battery Status API", isSupported: Modernizr.battery, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Low Battery Level", isSupported: Modernizr.lowbattery, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Blob Constructor", isSupported: Modernizr.blobconstructor, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Canvas toDataURL image/jpeg", isSupported: Modernizr.todataurljpeg, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Canvas toDataURL image/png", isSupported: Modernizr.todataurlpng, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Canvas toDataURL image/webp", isSupported: Modernizr.todataurlwebp, type: "noncore" }); - this.supportedFeatures.push({ featureName: "HTML5 Content Editable Attribute", isSupported: Modernizr.contenteditable, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Content Security Policy", isSupported: Modernizr.contentsecuritypolicy, type: "noncore" }); - this.supportedFeatures.push({ featureName: "HTML5 Context Menu", isSupported: Modernizr.contextmenu, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Cookie", isSupported: Modernizr.cookies, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Cross-Origin Resource Sharing", isSupported: Modernizr.cors, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS background-position Shorthand", isSupported: Modernizr.bgpositionshorthand, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS background-position-x/y", isSupported: Modernizr.bgpositionxy, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS background-repeat: space", isSupported: Modernizr.bgrepeatspace, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS background-repeat: round", isSupported: Modernizr.bgrepeatround, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS background-size: cover", isSupported: Modernizr.bgsizecover, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS Box Sizing", isSupported: Modernizr.boxsizing, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS Calc", isSupported: Modernizr.csscalc, type: "noncore" }); - this.supportedFeatures.push({ featureName: "CSS Cubic Bezier Range", isSupported: Modernizr.cubicbezierrange, type: "noncore" }); - this.supportedFeatures.push({ featureName: "Gamepad", isSupported: Modernizr.gamepads, type: "noncore" }); - - //this.supportedFeatures.push({ featureName: "", isSupported: Modernizr.display-runin, type: "noncore" }); - //this.supportedFeatures.push({ featureName: "", isSupported: Modernizr.display-table, type: "noncore" }); + public checkSupportedFeatures() { + if (Modernizr) { + this.trace("checkin client features with Modernizr"); + this.supportedFeatures = []; - var message: any = {}; - message.features = this.supportedFeatures; + this.supportedFeatures.push({ featureName: "Application cache", isSupported: Modernizr.applicationcache, type: "html" }); + this.supportedFeatures.push({ featureName: "Audio tag", isSupported: Modernizr.audio, type: "html" }); + this.supportedFeatures.push({ featureName: "background-size", isSupported: Modernizr.backgroundsize, type: "css" }); + this.supportedFeatures.push({ featureName: "border-image", isSupported: Modernizr.borderimage, type: "css" }); + this.supportedFeatures.push({ featureName: "border-radius", isSupported: Modernizr.borderradius, type: "css" }); + this.supportedFeatures.push({ featureName: "box-shadow", isSupported: Modernizr.boxshadow, type: "css" }); + this.supportedFeatures.push({ featureName: "canvas", isSupported: Modernizr.canvas, type: "html" }); + this.supportedFeatures.push({ featureName: "canvas text", isSupported: Modernizr.canvastext, type: "html" }); + this.supportedFeatures.push({ featureName: "CSS Animations", isSupported: Modernizr.cssanimations, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Columns", isSupported: Modernizr.csscolumns, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Gradients", isSupported: Modernizr.cssgradients, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Reflections", isSupported: Modernizr.cssreflections, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Transforms", isSupported: Modernizr.csstransforms, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Transforms 3d", isSupported: Modernizr.csstransforms3d, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Transitions", isSupported: Modernizr.csstransitions, type: "css" }); + this.supportedFeatures.push({ featureName: "Drag'n'drop", isSupported: Modernizr.draganddrop, type: "html" }); + this.supportedFeatures.push({ featureName: "Flexbox", isSupported: Modernizr.flexbox, type: "css" }); + this.supportedFeatures.push({ featureName: "@font-face", isSupported: Modernizr.fontface, type: "css" }); + this.supportedFeatures.push({ featureName: "CSS Generated Content (:before/:after)", isSupported: Modernizr.generatedcontent, type: "css" }); + this.supportedFeatures.push({ featureName: "Geolocation API", isSupported: Modernizr.geolocation, type: "misc" }); + this.supportedFeatures.push({ featureName: "hashchange Event", isSupported: Modernizr.hashchange, type: "html" }); + this.supportedFeatures.push({ featureName: "History Management", isSupported: Modernizr.history, type: "html" }); + this.supportedFeatures.push({ featureName: "Color Values hsla()", isSupported: Modernizr.hsla, type: "css" }); + this.supportedFeatures.push({ featureName: "IndexedDB", isSupported: Modernizr.indexeddb, type: "html" }); + this.supportedFeatures.push({ featureName: "Inline SVG in HTML5", isSupported: Modernizr.inlinesvg, type: "misc" }); + this.supportedFeatures.push({ featureName: "Input Attribute autocomplete", isSupported: Modernizr.input.autocomplete, type: "html" }); + /* TO DO: Inputs... */ + this.supportedFeatures.push({ featureName: "localStorage", isSupported: Modernizr.localstorage, type: "html" }); + this.supportedFeatures.push({ featureName: "Multiple backgrounds", isSupported: Modernizr.multiplebgs, type: "css" }); + this.supportedFeatures.push({ featureName: "opacity", isSupported: Modernizr.opacity, type: "css" }); + this.supportedFeatures.push({ featureName: "Cross-window Messaging", isSupported: Modernizr.postmessage, type: "html" }); + this.supportedFeatures.push({ featureName: "Color Values rgba()", isSupported: Modernizr.rgba, type: "css" }); + this.supportedFeatures.push({ featureName: "sessionStorage", isSupported: Modernizr.sessionstorage, type: "html" }); + this.supportedFeatures.push({ featureName: "SVG SMIL animation", isSupported: Modernizr.smil, type: "misc" }); + this.supportedFeatures.push({ featureName: "SVG", isSupported: Modernizr.svg, type: "misc" }); + this.supportedFeatures.push({ featureName: "SVG Clipping Paths", isSupported: Modernizr.svgclippaths, type: "misc" }); + this.supportedFeatures.push({ featureName: "text-shadow", isSupported: Modernizr.textshadow, type: "css" }); + this.supportedFeatures.push({ featureName: "Touch Events", isSupported: Modernizr.touch, type: "misc" }); + this.supportedFeatures.push({ featureName: "Video", isSupported: Modernizr.video, type: "html" }); + this.supportedFeatures.push({ featureName: "WebGL", isSupported: Modernizr.webgl, type: "misc" }); + this.supportedFeatures.push({ featureName: "Web Sockets", isSupported: ("WebSocket" in window), type: "html" }); + this.supportedFeatures.push({ featureName: "Web SQL Database", isSupported: Modernizr.websqldatabase, type: "html" }); + this.supportedFeatures.push({ featureName: "Web Workers", isSupported: Modernizr.webworkers, type: "html" }); + this.supportedFeatures.push({ featureName: "A [download] attribute", isSupported: Modernizr.adownload, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Mozilla Audio Data API", isSupported: Modernizr.audiodata, type: "noncore" }); + this.supportedFeatures.push({ featureName: "HTML5 Web Audio API", isSupported: Modernizr.webaudio, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Battery Status API", isSupported: Modernizr.battery, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Low Battery Level", isSupported: Modernizr.lowbattery, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Blob Constructor", isSupported: Modernizr.blobconstructor, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Canvas toDataURL image/jpeg", isSupported: Modernizr.todataurljpeg, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Canvas toDataURL image/png", isSupported: Modernizr.todataurlpng, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Canvas toDataURL image/webp", isSupported: Modernizr.todataurlwebp, type: "noncore" }); + this.supportedFeatures.push({ featureName: "HTML5 Content Editable Attribute", isSupported: Modernizr.contenteditable, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Content Security Policy", isSupported: Modernizr.contentsecuritypolicy, type: "noncore" }); + this.supportedFeatures.push({ featureName: "HTML5 Context Menu", isSupported: Modernizr.contextmenu, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Cookie", isSupported: Modernizr.cookies, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Cross-Origin Resource Sharing", isSupported: Modernizr.cors, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS background-position Shorthand", isSupported: Modernizr.bgpositionshorthand, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS background-position-x/y", isSupported: Modernizr.bgpositionxy, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS background-repeat: space", isSupported: Modernizr.bgrepeatspace, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS background-repeat: round", isSupported: Modernizr.bgrepeatround, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS background-size: cover", isSupported: Modernizr.bgsizecover, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS Box Sizing", isSupported: Modernizr.boxsizing, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS Calc", isSupported: Modernizr.csscalc, type: "noncore" }); + this.supportedFeatures.push({ featureName: "CSS Cubic Bezier Range", isSupported: Modernizr.cubicbezierrange, type: "noncore" }); + this.supportedFeatures.push({ featureName: "Gamepad", isSupported: Modernizr.gamepads, type: "noncore" }); + + //this.supportedFeatures.push({ featureName: "", isSupported: Modernizr.display-runin, type: "noncore" }); + //this.supportedFeatures.push({ featureName: "", isSupported: Modernizr.display-table, type: "noncore" }); - this.sendToDashboard(message); - } - }); + this.sendFeaturesToDashboard(); + } } - public refresh(): void { + public sendFeaturesToDashboard() { var message: any = {}; - message.features = this.supportedFeatures; - this.sendToDashboard(message); + message.features = this.supportedFeatures || []; + this.trace("sending " + message.features.length + " features"); + this.sendCommandToDashboard("clientfeatures", message); } - public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { - + public refresh(): void { + this.trace("refreshing Modernizr"); + if (this.supportedFeatures && this.supportedFeatures.length){ + this.sendFeaturesToDashboard(); + }else{ + this.loadModernizrFeatures(); + } } } + ModernizrReportClient.prototype.ClientCommands = { + refresh: function(data: any) { + var plugin = this; + plugin.refresh(); + } + }; + // Register Core.RegisterClientPlugin(new ModernizrReportClient()); } \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.dashboard.ts b/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.dashboard.ts index 6b69fe99..5d8ba961 100644 --- a/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.dashboard.ts +++ b/Plugins/Vorlon/plugins/modernizrReport/vorlon.modernizrReport.dashboard.ts @@ -6,10 +6,8 @@ constructor() { super("modernizrReport", "control.html", "control.css"); this._ready = false; - } - - public getID(): string { - return "MODERNIZR"; + this._id = "MODERNIZR"; + //this.debug = true; } private _filterList: any = {}; @@ -18,7 +16,7 @@ private _miscFeaturesListTable: HTMLTableElement; private _nonCoreFeaturesListTable: HTMLTableElement; - public startDashboardSide(div: HTMLDivElement = null): void { + public startDashboardSide(div: HTMLDivElement = null): void { this._insertHtmlContentAsync(div,(filledDiv) => { this._cssFeaturesListTable = Tools.QuerySelectorById(div, "cssFeaturesList"); @@ -39,9 +37,10 @@ }); } - public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + public displayClientFeatures(receivedObject: any): void { if (!receivedObject || !receivedObject.features) return; + var targettedTable; var supportedFeatures: FeatureSupported[] = receivedObject.features; if (supportedFeatures && supportedFeatures.length) @@ -81,6 +80,13 @@ } } + ModernizrReportDashboard.prototype.DashboardCommands = { + clientfeatures: function(data: any) { + var plugin = this; + plugin.displayClientFeatures(data); + } + }; + // Register Core.RegisterDashboardPlugin(new ModernizrReportDashboard()); } \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.client.ts b/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.client.ts index 4e1c437e..b30f3039 100644 --- a/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.client.ts +++ b/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.client.ts @@ -4,6 +4,7 @@ constructor() { super("networkMonitor"); + //this.debug = true; this._ready = true; } @@ -12,6 +13,7 @@ } public sendClientData(): void { + this.trace("network monitor sending data ") var entries = window.performance.getEntries(); //console.log(entries); @@ -38,14 +40,7 @@ //console.log(this.performanceItems); var message: any = {}; message.entries = this.performanceItems; - Core.Messenger.sendRealtimeMessage(this.getID(), message, RuntimeSide.Client, "message"); - } - - public startClientSide(): void { - var that = this; - window.onload = (event) => { - that.sendClientData(); - }; + this.sendCommandToDashboard("performanceItems", message); } public refresh(): void { @@ -53,6 +48,12 @@ } } + NetworkMonitorClient.prototype.ClientCommands = { + refresh: function (data: any) { + var plugin = this; + plugin.sendClientData(); + } + }; //Register the plugin with vorlon core Core.RegisterClientPlugin(new NetworkMonitorClient()); } \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.dashboard.ts b/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.dashboard.ts index 49c79915..42e7ecec 100644 --- a/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.dashboard.ts +++ b/Plugins/Vorlon/plugins/networkMonitor/vorlon.networkMonitor.dashboard.ts @@ -2,21 +2,28 @@ export class NetworkMonitorDashboard extends DashboardPlugin { constructor() { super("networkMonitor", "control.html", "control.css"); - this._ready = true; - } - - public getID(): string { - return "NETWORK"; + this._ready = false; + this._id = "NETWORK"; + //this.debug = true; } private _containerDiv: HTMLElement; + public startDashboardSide(div: HTMLDivElement = null): void { this._insertHtmlContentAsync(div, (filledDiv) => { this._containerDiv = Tools.QuerySelectorById(div, "networkLogList"); + this._ready = true; }) } - public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + public processEntries(receivedObject: any): void { + if (!this._containerDiv){ + console.error("NetworkMonitor dashboard receive client message but is not ready"); + return; + } + + this._containerDiv.innerHTML = ""; + var barColors = { background: "rgb(211,211,211)", blocked: "rgb(204, 204, 204)", @@ -30,6 +37,10 @@ response: "rgb(52, 150, 255)" } var maxTime = 0; + + if (!receivedObject || !receivedObject.entries) + return; + for (var n = 0; n < receivedObject.entries.length; n++) { maxTime = Math.max(maxTime, receivedObject.entries[n].startTime + receivedObject.entries[n].duration); } @@ -104,6 +115,13 @@ return bar } } + + NetworkMonitorDashboard.prototype.DashboardCommands = { + performanceItems: function(data: any) { + var plugin = this; + plugin.processEntries(data); + } + }; //Register the plugin with vorlon core Core.RegisterDashboardPlugin(new NetworkMonitorDashboard()); diff --git a/Plugins/Vorlon/plugins/ngInspector/readme.md b/Plugins/Vorlon/plugins/ngInspector/readme.md new file mode 100644 index 00000000..19f9917d --- /dev/null +++ b/Plugins/Vorlon/plugins/ngInspector/readme.md @@ -0,0 +1,4 @@ +## ngInspector +Inspect your Angular.js scopes. + +[More information](http://sebastienollivier.fr/blog/javascript/nginspector-for-vorlon) \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/ngInspector/vorlon.ngInspector.client.ts b/Plugins/Vorlon/plugins/ngInspector/vorlon.ngInspector.client.ts index 21872525..605bd9f6 100644 --- a/Plugins/Vorlon/plugins/ngInspector/vorlon.ngInspector.client.ts +++ b/Plugins/Vorlon/plugins/ngInspector/vorlon.ngInspector.client.ts @@ -5,7 +5,7 @@ private _rootScopes: Scope[] = []; private _currentShownScopeId: number = null; - + constructor() { super("ngInspector"); this._ready = false; @@ -25,6 +25,15 @@ }); } + public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { + if (typeof angular == 'undefined') + return; + + if (receivedObject.type === MessageType.ReloadWithDebugInfo) { + angular.reloadWithDebugInfo(); + } + } + private _timeoutId: NodeJS.Timer; private _markForRefresh() { if (this._timeoutId) { @@ -40,13 +49,13 @@ this._rootScopes = []; this._findRootScopes(document.body); - this.sendToDashboard({ scopes: this._rootScopes }); + this.sendToDashboard({ scopes: this._rootScopes }); } private _findRootScopes(element: Node) { - if(typeof angular == 'undefined') + if (typeof angular == 'undefined') return; - + var rootScope = angular.element(element).scope(); if (!!rootScope) { var cleanedRootScope = this._cleanScope(rootScope); @@ -65,21 +74,24 @@ } private _findChildrenScopes(element: Node, parentScope: Scope) { - if(typeof angular == 'undefined') + if (typeof angular == 'undefined') return; - + for (var i = 0; i < element.childNodes.length; i++) { var childNode = element.childNodes[i]; var childScope = angular.element(childNode).scope(); - if (!!childScope && childScope.$id !== parentScope.$id) { + if (!!childScope + && childScope.$id !== parentScope.$id + && parentScope.$children.indexOf(childScope) === -1) { var cleanedChildScope = this._cleanScope(childScope); - if (childNode.attributes["ng-repeat"] || - childNode.attributes["data-ng-repeat"] || - childNode.attributes["x-ng-repeat"] || - childNode.attributes["ng_repeat"] || - childNode.attributes["ng:repeat"]) { + if (childNode.attributes && + (childNode.attributes["ng-repeat"] || + childNode.attributes["data-ng-repeat"] || + childNode.attributes["x-ng-repeat"] || + childNode.attributes["ng_repeat"] || + childNode.attributes["ng:repeat"])) { cleanedChildScope.$type = ScopeType.NgRepeat; cleanedChildScope.$name = "ng-repeat"; } @@ -88,7 +100,21 @@ // Workaround for IE, name property of constructor return undefined :/ // Get the name from the constructor function as string - var name: string = constructor.toString().match(/function (\w+)\(/)[1]; + var match = constructor.toString().match(/function (\w+)\(/); + var name: string = ""; + if (!!match && match.length > 1) { + name = match[1]; + } + else { + var ngControllerAttribute = childNode.attributes["ng-controller"] || + childNode.attributes["data-ng-controller"] || + childNode.attributes["x-ng-controller"] || + childNode.attributes["ng_controller"] || + childNode.attributes["ng:controller"]; + if (ngControllerAttribute) { + name = ngControllerAttribute.name; + } + } cleanedChildScope.$type = ScopeType.Controller; cleanedChildScope.$name = name; @@ -135,15 +161,6 @@ return scopePackaged; } - - public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { - if(typeof angular == 'undefined') - return; - - if (receivedObject.type === MessageType.ReloadWithDebugInfo) { - angular.reloadWithDebugInfo(); - } - } } // Register diff --git a/Plugins/Vorlon/plugins/objectExplorer/control.less b/Plugins/Vorlon/plugins/objectExplorer/control.less index 36ca81b5..5ce4ee70 100644 --- a/Plugins/Vorlon/plugins/objectExplorer/control.less +++ b/Plugins/Vorlon/plugins/objectExplorer/control.less @@ -1,3 +1,5 @@ +@import "../vorlontheme.less"; + .plugin-objexplorer { .obj-explorer-container { @@ -20,32 +22,32 @@ height: @searchBoxHeight; border-bottom: 1px solid #EEE; display: flex; - - .panel{ + + .panel { position: relative; - - input{ + + input { width: 100%; padding-left: 30px; } - + i { - left:10px; - top:0; - line-height: 27px; - color:#CCC; - position:absolute; + left: 10px; + top: 0; + line-height: 27px; + color: #CCC; + position: absolute; } } - - .searchPanel{ + + .searchPanel { flex: 1; } - - .filterPanel{ + + .filterPanel { width: 20%; - min-width: 120px; - margin-left : 4px; + min-width: 120px; + margin-left: 4px; } } @@ -57,6 +59,7 @@ bottom: 0; padding: 5px 20px 20px 30px; overflow: auto; + line-height: 1.5em; } } @@ -64,84 +67,69 @@ height: 100%; } - .tree-view { - padding: 0; - } - - .treeNodeButton { - color: #777; - margin-left: -18px; - margin-right: 4px; - width: 14px; - height: 14px; - line-height: 12px; - border: 1px solid #999; - text-align: center; - cursor: pointer; - display: inline-block; - } - - .treeNodeHeader { - text-decoration: none; - color: #ccc; - } - - .treeNodeSelected { - background: rgba(0,183,199,0.1); - } - - .treeNodeTools { - color: #444; - float: right; - text-decoration: none; + .objdescriptor { + margin-bottom: 5px; } - .treeNodeText { - text-decoration: none; - border-left: 1px solid #eee; - padding-left: 26px; - margin-left: -12px; - } + .expandable { + > .expand { + cursor: pointer; - .firstTreeNodeText { - text-decoration: none; - padding-left: 0px; - } + > .expand-btn { + color: #BBB; + margin-left: -18px; + margin-right: 4px; + width: 14px; + height: 14px; + line-height: 12px; + border: 1px solid #DDD; + text-align: center; + cursor: pointer; + display: inline-block; + } + } - .treeNodeClosingText { - border-left: 1px solid #eee; - padding-left: 13px; - } + > .expand-content { + display: none; + padding-left: 30px; + margin-left: -12px; + border-left: 1px solid #DDD; + } - .nodeName { - color: #1d0c3d; + &.expanded { + > .expand-content { + display: block; + } + } } - .nodeTag { - color: #1d0c3d; + .tree-view { + padding: 0; } - - .nodeAttribute { - color: #ed2424; + + .objdescriptor>*{ + margin-bottom: 2px; } - .nodeValue { - color: #007ca7; - margin-left: 6px; - } + .property{ + + .prop-name{ + } - .nodeType { - color: #BBB; - font-style:italic; - margin-left: 6px; + .prop-value{ + color: @lightblue; + margin-left: 10px; + } } - .attachNode { - text-align: center; - cursor: pointer; - display: inline; - color: #CCC; - margin-left: 5px; - font-size: 0.8em; + .actionicon { + margin-left: 0.5em; + display: inline-block; + width: 16px; + height: 16px; + line-height: 16px; + color: lighten(@lightpurple, 50%); + text-align:center; + vertical-align : middle; } } diff --git a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.client.ts b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.client.ts index e6ed2ff2..e69d3b9b 100644 --- a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.client.ts +++ b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.client.ts @@ -4,13 +4,14 @@ private _previousSelectedNode; private _currentPropertyPath: string; private _timeoutId; + private _objPrototype = Object.getPrototypeOf({}); constructor() { super("objectExplorer"); this._id = "OBJEXPLORER"; this._ready = false; } - + private STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; private ARGUMENT_NAMES = /([^\s,]+)/g; private rootProperty = 'window'; @@ -29,121 +30,206 @@ return result; } - private _getProperty(propertyPath: string): ObjPropertyDescriptor { + private inspect(path: Array, obj: any, context: any): ObjExplorerObjDescriptor { + if (obj === undefined) + return null; + + var res = { + proto: null, + fullpath: path.join('.'), + name: path[path.length - 1], + functions: [], + properties: [], + contentFetched: true, + type: typeof obj + }; + + this.trace("inspecting " + res.fullpath + " as " + res.type); + if (res.type === "function") { + return res; + } else if (obj === null) { + res.type = "null"; + res.value = null; + return res; + } + else if (res.type !== "object") { + res.value = obj.toString(); + return res; + } + + var objProperties = Object.getOwnPropertyNames(obj); + var proto = Object.getPrototypeOf(obj); + + if (proto && proto != this._objPrototype) + res.proto = this.inspect(path, proto, context); + + for (var i = 0, l = objProperties.length; i < l; i++) { + var p = objProperties[i]; + + var propertyType = ""; + if (p === '__vorlon') + continue; + var propPath = JSON.parse(JSON.stringify(path)); + propPath.push(p); + + try { + var objValue = context[p]; + propertyType = typeof objValue; + if (propertyType === 'function') { + res.functions.push({ + name: p, + fullpath: propPath.join('.'), + args: this.getFunctionArgumentNames(objValue) + }); + } else if (propertyType === 'undefined') { + res.properties.push({ + name: p, + type: propertyType, + fullpath: propPath.join('.'), + value: undefined + }); + } else if (propertyType === 'null') { + res.properties.push({ + name: p, + type: propertyType, + fullpath: propPath.join('.'), + value: null + }); + } else if (propertyType === 'object') { + + var desc = { + name: p, + type: propertyType, + fullpath: propPath.join('.'), + contentFetched: false + }; + if (objValue === null) { + desc.type = "null"; + desc.contentFetched = true; + } + res.properties.push(desc); + } else { + res.properties.push({ + name: p, + fullpath: propPath.join('.'), + type: propertyType, + value: objValue.toString() + }); + } + } catch (exception) { + this.trace('error reading property ' + p + ' of type ' + propertyType); + this.trace(exception); + res.properties.push({ + name: p, + type: propertyType, + fullpath: propPath.join('.'), + val: "oups, Vorlon has an error reading this " + propertyType + " property..." + }); + } + } + + res.functions = res.functions.sort(function (a, b) { + var lowerAName = a.name.toLowerCase(); + var lowerBName = b.name.toLowerCase(); + + if (lowerAName > lowerBName) + return 1; + if (lowerAName < lowerBName) + return -1; + return 0; + }); + + res.properties = res.properties.sort(function (a, b) { + var lowerAName = a.name.toLowerCase(); + var lowerBName = b.name.toLowerCase(); + + if (lowerAName > lowerBName) + return 1; + if (lowerAName < lowerBName) + return -1; + return 0; + }); + + return res; + } + + private _getProperty(propertyPath: string): ObjExplorerObjDescriptor { var selectedObj = window; var tokens = [this.rootProperty]; + this.trace("getting obj at " + propertyPath); + if (propertyPath && propertyPath !== this.rootProperty) { tokens = propertyPath.split('.'); - if (tokens && tokens.length) { + if (tokens && tokens.length) { for (var i = 0, l = tokens.length; i < l; i++) { selectedObj = selectedObj[tokens[i]]; - if (!selectedObj) + if (selectedObj === undefined) { + this.trace(tokens[i] + " not found"); break; + } } } } - if (!selectedObj) { + if (selectedObj === undefined) { console.log('not found'); - return { type: 'notfound', name: 'not found', fullpath: propertyPath, contentFetched: true, content: [] }; + return { type: 'notfound', name: 'not found', val: null, functions: [], properties: [], contentFetched: false, fullpath: null }; } - var res = this.getObjDescriptor(selectedObj, tokens, true); + var res = this.inspect(tokens, selectedObj, selectedObj); return res; } - private getObjDescriptor(object: any, pathTokens: Array, scanChild: boolean = false): ObjPropertyDescriptor { - pathTokens = pathTokens || []; - var name = pathTokens[pathTokens.length - 1]; - var type = typeof object; - if (object === null) { - type = 'null'; - } - if (object === undefined) { - type = 'undefined'; - } - - var fullpath = this.rootProperty; - if (!name) { - name = this.rootProperty; - fullpath = this.rootProperty; - } else { - if (fullpath.indexOf(this.rootProperty + ".") !== 0 && pathTokens[0] !== this.rootProperty) { - fullpath = this.rootProperty + '.' + pathTokens.join('.'); - } else { - fullpath = pathTokens.join('.'); - } - } - - //console.log('check ' + name + ' ' + type); - var res = { type: type, name: name, fullpath: fullpath, contentFetched: false, content: [], value: null }; - - if (type === 'string' || type === 'number' || type === 'boolean') { - res.value = object.toString(); - } else if (type === 'function') { - res.value = this.getFunctionArgumentNames(object).join(','); - } - - if (object && scanChild) { - for (var e in object) { - var itemTokens = pathTokens.concat([e]); - res.content.push(this.getObjDescriptor(object[e], itemTokens, false)); - } - - res.contentFetched = true; - } - return res; - } - - private _packageAndSendObjectProperty(type: string, path?: string) { + private _packageAndSendObjectProperty(path?: string) { path = path || this._currentPropertyPath; var packagedObject = this._getProperty(path); - this.sendToDashboard({ type: type, path: packagedObject.fullpath, property: packagedObject }); + this.sendCommandToDashboard('update', packagedObject); + //this.sendToDashboard({ type: type, path: packagedObject.fullpath, property: packagedObject }); } - private _markForRefresh() { - if (this._timeoutId) { - clearTimeout(this._timeoutId); - } - - this._timeoutId = setTimeout(() => { - this.refresh(); - }, 10000); + public startClientSide(): void { + } - public startClientSide(): void { - document.addEventListener("DOMContentLoaded",() => { - if (Core.Messenger.isConnected) { - document.addEventListener("DOMNodeInserted",() => { - this._markForRefresh(); - }); - document.addEventListener("DOMNodeRemoved",() => { - this._markForRefresh(); - }); - } + public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { + //switch (receivedObject.type) { + // case "query": + // this._currentPropertyPath = receivedObject.path; + // this._packageAndSendObjectProperty(receivedObject.type); + // break; + // case "queryContent": + // this._packageAndSendObjectProperty(receivedObject.type, receivedObject.path); + // break; + // default: + // break; + //} + } - this.refresh(); - }); + public query(path: string) { + this._currentPropertyPath = path; + var packagedObject = this._getProperty(path); + this.sendCommandToDashboard('root', packagedObject); } - - public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { - switch (receivedObject.type) { - case "query": - this._currentPropertyPath = receivedObject.path; - this._packageAndSendObjectProperty(receivedObject.type); - break; - case "queryContent": - this._packageAndSendObjectProperty(receivedObject.type, receivedObject.path); - break; - default: - break; - } + public queryContent(path: string) { + var packagedObject = this._getProperty(path); + this.sendCommandToDashboard('content', packagedObject); } public refresh(): void { - this._packageAndSendObjectProperty('refresh'); + this.query(this._currentPropertyPath); + } + } + + ObjectExplorerClient.prototype.ClientCommands = { + query: function (data) { + var plugin = this; + plugin.query(data.path); + }, + queryContent: function (data) { + var plugin = this; + plugin.queryContent(data.path); } } diff --git a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.dashboard.ts b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.dashboard.ts index a16d5c7a..997a25a7 100644 --- a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.dashboard.ts +++ b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.dashboard.ts @@ -1,7 +1,7 @@ module VORLON { declare var $: any; - export class ObjectExplorerDashboard extends DashboardPlugin { + export class ObjectExplorerDashboard extends DashboardPlugin { private _containerDiv: HTMLElement; private _searchBoxInput: HTMLInputElement; private _filterInput: HTMLInputElement; @@ -9,8 +9,9 @@ private _searchUpBtn: HTMLButtonElement; private _treeDiv: HTMLElement; private _dashboardDiv: HTMLDivElement; - private _contentCallbacks; + private _contentCallbacks; private _currentPropertyPath: string; + private root: ObjDescriptorControl; constructor() { super("objectExplorer", "control.html", "control.css"); @@ -18,7 +19,7 @@ this._ready = false; this._contentCallbacks = {}; } - + public startDashboardSide(div: HTMLDivElement = null): void { this._dashboardDiv = div; @@ -28,9 +29,9 @@ this._searchBoxInput = this._containerDiv.querySelector("#txtPropertyName"); this._searchBtn = this._containerDiv.querySelector("#btnSearchProp"); this._searchUpBtn = this._containerDiv.querySelector("#btnSearchUp"); - this._treeDiv = this._containerDiv.querySelector("#treeViewObj"); + this._treeDiv = this._containerDiv.querySelector("#treeViewObj"); this._addLoader(); - + this._searchBtn.onclick = () => { var path = this._searchBoxInput.value; if (path) { @@ -48,95 +49,69 @@ } } } - + this._searchBoxInput.addEventListener("keydown",(evt) => { if (evt.keyCode === 13 || evt.keyCode === 9) { // Enter or tab var path = this._searchBoxInput.value; - if (path) { + if (path) { this.setCurrent(path); } } }); - + this._filterInput.addEventListener("input",(evt) => { //setTimeout(function(){}); this.filter(); }); - + this._ready = true; }); } - private _addLoader(){ + private _addLoader() { var loader = document.createElement("div"); loader.className = "loader"; loader.innerHTML = ' loading...'; this._treeDiv.appendChild(loader); } - - private setCurrent(path) { + + public setCurrent(path) { if (path !== this._currentPropertyPath) this._filterInput.value = ''; - + this._searchBoxInput.value = path; this._currentPropertyPath = path; - this._queryObjectContent(this._currentPropertyPath); + this.queryObjectContent(this._currentPropertyPath); this._empty(); this._treeDiv.scrollTop = 0; - this._addLoader(); + this._addLoader(); } - - private filter(){ + + private filter() { var path = this._filterInput.value.toLowerCase(); - var items = this._treeDiv.children; - for (var index=0, l=items.length ; index < l ; index++){ + var items = this._treeDiv.querySelectorAll("[data-propname]"); + for (var index = 0, l = items.length; index < l; index++) { var node = items[index]; var propname = node.getAttribute('data-propname'); - if (!propname || !path){ + if (!propname || !path) { node.style.display = ''; - }else{ - if (propname.indexOf(path) >= 0){ + } else { + if (propname.indexOf(path) >= 0) { node.style.display = ''; - }else{ + } else { node.style.display = 'none'; } } } } - private _queryObjectContent(objectPath: string) { - this.sendToClient({ type: "query", path: objectPath }); - } - - private _appendSpan(parent: HTMLElement, className: string, value: string): void { - var span = document.createElement("span"); - span.className = className; - span.innerHTML = value; - - parent.appendChild(span); + private queryObjectContent(objectPath: string) { + this.sendCommandToClient("query", { path: objectPath }); } - private _generateColorfullLink(link: HTMLAnchorElement, receivedObject: any): void { - this._appendSpan(link, "nodeName", receivedObject.name); - - if (receivedObject.type !== 'object') { - this._appendSpan(link, "nodeType", '(' + receivedObject.type + ')'); - } - - if (receivedObject.value) { - this._appendSpan(link, "nodeValue", receivedObject.value); - } - } - - private _generateButton(parentNode: HTMLElement, text: string, className: string, onClick: (button: HTMLElement) => void) { - var button = this._render("div", parentNode, className, text); - button.addEventListener("click", () => onClick(button)); - return button; - } - - private _sortedList(list : ObjPropertyDescriptor[]){ - if (list && list.length){ + private _sortedList(list: ObjExplorerObjDescriptor[]) { + if (list && list.length) { return list.sort(function (a, b) { var lowerAName = a.name.toLowerCase(); var lowerBName = b.name.toLowerCase(); @@ -148,11 +123,11 @@ return 0; }); } - + return []; } - - private _render(tagname: string, parentNode:HTMLElement, classname?:string, value?: string): HTMLElement { + + private _render(tagname: string, parentNode: HTMLElement, classname?: string, value?: string): HTMLElement { var elt = document.createElement(tagname); elt.className = classname || ''; if (value) @@ -160,116 +135,280 @@ parentNode.appendChild(elt); return elt; } - - private _generateTreeNode(parentNode: HTMLElement, receivedObject: ObjPropertyDescriptor, first = false): void { - var root = this._render("div", parentNode); - root.setAttribute('data-propname', receivedObject.name.toLowerCase()); - var nodeButton = null; - var fetchingNode = false; - var label = this._render("div", root, 'labels'); - var container = this._render("div", root, 'childNodes'); - container.style.display = 'none'; - - var renderChilds = () => { - if (receivedObject.contentFetched && receivedObject.content && receivedObject.content.length) { - var nodes = this._sortedList(receivedObject.content); - - for (var index = 0, l=nodes.length; index < l; index++) { - this._generateTreeNode(container, nodes[index]); + + private _empty() { + while (this._treeDiv.hasChildNodes()) { + this._treeDiv.removeChild(this._treeDiv.lastChild); + } + } + + public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + + } + + public setRoot(obj: ObjExplorerObjDescriptor) { + this._searchBoxInput.value = obj.fullpath; + this._currentPropertyPath = obj.fullpath; + if (this.root) { + this.root.dispose(); + this.root = null; + } + this._empty(); + + this.root = new ObjDescriptorControl(this, this._treeDiv, obj, true); + } + + public setContent(obj: ObjExplorerObjDescriptor) { + if (this.root) { + this.root.setContent(obj); + } + } + } + + class ObjDescriptorControl { + element: HTMLElement; + proto: ObjDescriptorControl; + item: ObjExplorerObjDescriptor; + plugin: ObjectExplorerDashboard; + childs: Array; + isRoot: boolean; + + constructor(plugin: ObjectExplorerDashboard, parent: HTMLElement, item: ObjExplorerObjDescriptor, isRoot: boolean = false) { + var elt = new FluentDOM('DIV', 'objdescriptor', parent); + this.element = elt.element; + this.isRoot = isRoot; + this.element.__vorlon = this; + this.item = item; + this.plugin = plugin; + this.childs = []; + this.render(elt); + } + + private clear() { + if (this.childs) { + this.childs.forEach(function (c) { + c.dispose(); + }); + this.childs = []; + } + if (this.proto) + this.proto.dispose(); + this.element.innerHTML = ""; + } + + public dispose() { + this.clear(); + this.element.__vorlon = null; + this.plugin = null; + this.element = null; + this.item = null; + } + + public setContent(item: ObjExplorerObjDescriptor) { + if (!item.fullpath) + return false; + + console.log("checking " + item.fullpath + "/" + this.item.fullpath + " (" + this.childs.length + ")"); + + if (item.fullpath == this.item.fullpath) { + this.item = item; + this.render(); + return true; + } else { + if (item.fullpath.indexOf(this.item.fullpath) === 0) { + for (var i = 0, l = this.childs.length; i < l; i++) { + if (this.childs[i].obj && this.childs[i].obj.setContent(item)) { + return true; + } } - container.style.display = ''; } - } - - var toggleNode = (button) => { - if (!fetchingNode && !receivedObject.contentFetched) { - fetchingNode = true; - var spinner = this._render("span", label, "loader", ' '); - this._contentCallbacks[receivedObject.fullpath] = (propertyData) => { - label.removeChild(spinner); - this._contentCallbacks[receivedObject.fullpath] = null; - receivedObject.contentFetched = true; - receivedObject.content = propertyData.content; - renderChilds(); - } + } - this.sendToClient({ - type: "queryContent", - path: receivedObject.fullpath - }); - } + if (this.proto && this.proto.setContent(item)) { + return true; + } + + return false; + } + + public render(elt?: FluentDOM) { + if (!elt) + elt = FluentDOM.forElement(this.element); + + this.clear(); - if (container.style.display === "none") { - container.style.display = ""; - button.innerHTML = "-"; + if (!this.item) { + return; + } + + if (!this.item.contentFetched) { + if (this.item.type === "notfound") { + elt.element.innerHTML = '
Nothing found, please change your filter (or use "window")...
'; } else { - container.style.display = "none"; - button.innerHTML = "+"; + elt.element.innerHTML = '
loading content...
'; } - }; - - if (receivedObject.type === 'object') { - nodeButton = this._generateButton(label, "+", "treeNodeButton",(button) => { - toggleNode(nodeButton); - }); + return; } - // Main node - var linkText = null; - - if (receivedObject.type === 'object') { - linkText = this._render("a", label, "treeNodeHeader"); - linkText.addEventListener("click",() => { - toggleNode(nodeButton); + if (this.item.proto) { + elt.append('DIV', 'objdescriptor-prototype expandable',(protoContainer) => { + var btn: FluentDOM; + protoContainer.append("DIV", "expand",(expandContainer) => { + btn = expandContainer.createChild("SPAN", "expand-btn").text("+"); + expandContainer.createChild("SPAN", "expand-label").text("[Prototype]"); + }).click((arg) => { + arg.stopPropagation(); + Tools.ToggleClass(protoContainer.element, "expanded",(expanded) => { + expanded ? btn.text("-") : btn.text("+"); + }); + }); + + protoContainer.append("DIV", "expand-content",(itemsContainer) => { + this.proto = new ObjDescriptorControl(this.plugin, itemsContainer.element, this.item.proto); + }); }); - linkText.href = "#"; - } else { - linkText = this._render("span", label); } - this._generateColorfullLink(linkText, receivedObject); - label.appendChild(linkText); + if (this.item.functions && this.item.functions.length) { + elt.append('DIV', 'objdescriptor-methods expandable',(methodsContainer) => { + var btn: FluentDOM; + methodsContainer.append("DIV", "expand",(expandContainer) => { + btn = expandContainer.createChild("SPAN", "expand-btn").text("+") + expandContainer.createChild("SPAN", "expand-label").text("[Methods]"); + }).click((arg) => { + arg.stopPropagation(); + Tools.ToggleClass(methodsContainer.element, "expanded",(expanded) => { + expanded ? btn.text("-") : btn.text("+"); + }); + }); + + methodsContainer.append("DIV", "expand-content",(itemsContainer) => { + this.item.functions.forEach((p) => { + itemsContainer.append("DIV", "obj-method",(methodItem) => { + if (this.isRoot) + methodItem.attr("data-propname",(this.item.fullpath + "." + p.name).toLowerCase()); - if (receivedObject.type === 'object') { - this._generateButton(label, "", "attachNode fa fa-reply",(button) => { - this.setCurrent(receivedObject.fullpath); + methodItem.createChild("SPAN", "method-name").text(p.name); + }); + }); + }); }); } - root.className = first ? "firstTreeNodeText" : "treeNodeText"; + if (this.item.properties && this.item.properties.length) { + elt.append('DIV', 'objdescriptor-properties',(propertiesContainer) => { + this.item.properties.forEach((p) => { + var propctrl = new ObjPropertyControl(this.plugin, propertiesContainer.element, p, this.item, this.isRoot); + this.childs.push(propctrl); + }); + }); + } + } - renderChilds(); + public getContent() { + this.plugin.sendCommandToClient("queryContent", { path: this.item.fullpath }); } + } - private _empty(){ - while (this._treeDiv.hasChildNodes()) { - this._treeDiv.removeChild(this._treeDiv.lastChild); + class ObjFunctionControl { + } + + class ObjPropertyControl { + parent: HTMLElement; + element: FluentDOM; + prop: ObjExplorerPropertyDescriptor; + obj: ObjDescriptorControl; + parentObj: ObjExplorerObjDescriptor; + plugin: ObjectExplorerDashboard; + public isRoot: boolean = false; + + constructor(plugin: ObjectExplorerDashboard, parent: HTMLElement, prop: ObjExplorerPropertyDescriptor, parentObj: ObjExplorerObjDescriptor, isRoot: boolean) { + this.plugin = plugin; + this.parent = parent; + this.parentObj = parentObj; + this.prop = prop; + this.isRoot = isRoot; + this.element = new FluentDOM("DIV", "property", parent); + if (prop.type !== "object") { + this.renderValueProperty(); + } else { + this.renderObjectProperty(); } } - - public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { - if (receivedObject.type === 'query' || receivedObject.type === 'refresh') { - this._empty(); - this._searchBoxInput.value = receivedObject.path; - this._currentPropertyPath = receivedObject.path; - if (receivedObject.property.content && receivedObject.property.content.length){ - var nodes = this._sortedList(receivedObject.property.content); - for (var index=0, length=nodes.length; index { + btn = expandContainer.createChild("SPAN", "expand-btn").text("+"); + expandContainer.createChild("SPAN", "expand-label").text(this.prop.name); + expandContainer.createChild("SPAN", "makeroot actionicon fa fa-external-link").click((arg) => { + this.plugin.setCurrent(this.prop.fullpath); + arg.stopPropagation(); + }); + }).click((arg) => { + arg.stopPropagation(); + Tools.ToggleClass(this.element.element, "expanded",(expanded) => { + if (!expanded) { + btn.text("+"); + return; + } + + btn.text("-"); + var elt = this.element.element.querySelector(".expand-content > .objdescriptor"); + if (elt) { + var ctrl = elt.__vorlon; + if (ctrl) { + setTimeout(() => { + ctrl.getContent(); + }, 0); + } + } + }); + }); + + this.element.append("DIV", "expand-content",(itemsContainer) => { + this.obj = new ObjDescriptorControl(this.plugin, itemsContainer.element, this.prop); + }); + } } + ObjectExplorerDashboard.prototype.DashboardCommands = { + root: function (data: ObjExplorerObjDescriptor) { + var plugin = this; + plugin.setRoot(data); + }, + + content: function (data: ObjExplorerObjDescriptor) { + var plugin = this; + if (data) { + plugin.setContent(data); + } + } + } // Register Core.RegisterDashboardPlugin(new ObjectExplorerDashboard()); } diff --git a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.interfaces.ts b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.interfaces.ts index bf16af06..74a4df2a 100644 --- a/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.interfaces.ts +++ b/Plugins/Vorlon/plugins/objectExplorer/vorlon.objectExplorer.interfaces.ts @@ -1,10 +1,21 @@ module VORLON { - export interface ObjPropertyDescriptor { + export interface ObjExplorerPropertyDescriptor { + name: string; type: string; + fullpath: string; + value?: any; + } + + export interface ObjExplorerFunctionDescriptor { name: string; + args: string[]; fullpath: string; + } + + export interface ObjExplorerObjDescriptor extends ObjExplorerPropertyDescriptor { + proto?: ObjExplorerObjDescriptor; + functions: Array; + properties: Array; contentFetched: boolean; - value?: any; - content: Array; } } diff --git a/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.client.ts b/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.client.ts index 8c678bab..1d9ecf79 100644 --- a/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.client.ts +++ b/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.client.ts @@ -7,10 +7,12 @@ constructor() { super("resourcesExplorer"); this._ready = true; - } - - public getID(): string { - return "RESOURCES"; + this._id = "RESOURCES"; + //this.debug = true; + + window.addEventListener("load", () => { + this.sendClientData(); + }); } public sendClientData(): void { @@ -36,20 +38,20 @@ message.localStorageList = this.localStorageList; message.sessionStorageList = this.sessionStorageList; message.cookiesList = this.cookiesList; - Core.Messenger.sendRealtimeMessage(this.getID(), message, RuntimeSide.Client, "message"); - } - - public startClientSide(): void { - var that = this; - window.onload = (event) => { - that.sendClientData(); - }; + this.sendCommandToDashboard("resourceitems", message); } public refresh(): void { this.sendClientData(); } } + + ResourcesExplorerClient.prototype.ClientCommands = { + refresh: function (data: any) { + var plugin = this; + plugin.refresh(); + } + }; //Register the plugin with vorlon core Core.RegisterClientPlugin(new ResourcesExplorerClient()); diff --git a/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.dashboard.ts b/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.dashboard.ts index cd397b86..256a2968 100644 --- a/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.dashboard.ts +++ b/Plugins/Vorlon/plugins/resourcesExplorer/vorlon.resourcesExplorer.dashboard.ts @@ -2,64 +2,91 @@ export class ResourcesExplorerDashboard extends DashboardPlugin { constructor() { super("resourcesExplorer", "control.html", "control.css"); - this._ready = true; - } - - public getID(): string { - return "RESOURCES"; + this._ready = false; + this._id = "RESOURCES"; + //this.debug = true; } private _containerLocalStorage: HTMLElement; private _containerSessionStorage: HTMLElement; private _containerCookies: HTMLElement; + public startDashboardSide(div: HTMLDivElement = null): void { - this._insertHtmlContentAsync(div,(filledDiv) => { + this._insertHtmlContentAsync(div, (filledDiv) => { this._containerLocalStorage = Tools.QuerySelectorById(div, "localStorageList"); this._containerSessionStorage = Tools.QuerySelectorById(div, "sessionStorageList"); this._containerCookies = Tools.QuerySelectorById(div, "cookiesList"); + this._ready = true; }) } - public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { - for (var i = 0; i < receivedObject.localStorageList.length; i++) { - var tr = document.createElement('tr'); - var tdKey = document.createElement('td'); - var tdValue = document.createElement('td'); + public processEntries(receivedObject: any): void { + if (!this._containerLocalStorage){ + console.warn("ResourcesExplorer dashboard receive client message but is not ready"); + return; + } + + this._containerLocalStorage.innerHTML = ""; + this._containerSessionStorage.innerHTML = ""; + this._containerCookies.innerHTML = ""; + + if (!receivedObject) + return; + + if (receivedObject.localStorageList) { + for (var i = 0; i < receivedObject.localStorageList.length; i++) { + var tr = document.createElement('tr'); + var tdKey = document.createElement('td'); + var tdValue = document.createElement('td'); - tdKey.innerHTML = receivedObject.localStorageList[i].key; - tdValue.innerHTML = receivedObject.localStorageList[i].value; + tdKey.innerHTML = receivedObject.localStorageList[i].key; + tdValue.innerHTML = receivedObject.localStorageList[i].value; - tr.appendChild(tdKey); - tr.appendChild(tdValue); - this._containerLocalStorage.appendChild(tr); + tr.appendChild(tdKey); + tr.appendChild(tdValue); + this._containerLocalStorage.appendChild(tr); + } } - for (var i = 0; i < receivedObject.sessionStorageList.length; i++) { - var tr = document.createElement('tr'); - var tdKey = document.createElement('td'); - var tdValue = document.createElement('td'); - tdKey.innerHTML = receivedObject.sessionStorageList[i].key; - tdValue.innerHTML = receivedObject.sessionStorageList[i].value; + if (receivedObject.sessionStorageList) { + for (var i = 0; i < receivedObject.sessionStorageList.length; i++) { + var tr = document.createElement('tr'); + var tdKey = document.createElement('td'); + var tdValue = document.createElement('td'); - tr.appendChild(tdKey); - tr.appendChild(tdValue); - this._containerSessionStorage.appendChild(tr); + tdKey.innerHTML = receivedObject.sessionStorageList[i].key; + tdValue.innerHTML = receivedObject.sessionStorageList[i].value; + + tr.appendChild(tdKey); + tr.appendChild(tdValue); + this._containerSessionStorage.appendChild(tr); + } } - for (var i = 0; i < receivedObject.cookiesList.length; i++) { - var tr = document.createElement('tr'); - var tdKey = document.createElement('td'); - var tdValue = document.createElement('td'); - tdKey.innerHTML = receivedObject.cookiesList[i].key; - tdValue.innerHTML = receivedObject.cookiesList[i].value; + if (receivedObject.cookiesList) { + for (var i = 0; i < receivedObject.cookiesList.length; i++) { + var tr = document.createElement('tr'); + var tdKey = document.createElement('td'); + var tdValue = document.createElement('td'); + + tdKey.innerHTML = receivedObject.cookiesList[i].key; + tdValue.innerHTML = receivedObject.cookiesList[i].value; - tr.appendChild(tdKey); - tr.appendChild(tdValue); - this._containerCookies.appendChild(tr); + tr.appendChild(tdKey); + tr.appendChild(tdValue); + this._containerCookies.appendChild(tr); + } } } } + ResourcesExplorerDashboard.prototype.DashboardCommands = { + resourceitems: function(data: any) { + var plugin = this; + plugin.processEntries(data); + } + }; + //Register the plugin with vorlon core Core.RegisterDashboardPlugin(new ResourcesExplorerDashboard()); } \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/unitTestRunner/control.html b/Plugins/Vorlon/plugins/unitTestRunner/control.html new file mode 100644 index 00000000..17f68d92 --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/control.html @@ -0,0 +1,36 @@ + + + + + + + + +  Run test + + +
+

Unit test

+ +
+
+

Results

+ + + + + + + + + + + + + + + +
NameModulePassedFailedTimeTotal
+
+ + diff --git a/Plugins/Vorlon/plugins/unitTestRunner/control.less b/Plugins/Vorlon/plugins/unitTestRunner/control.less new file mode 100644 index 00000000..19113a40 --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/control.less @@ -0,0 +1,48 @@ +.plugin-unittest { + + input[type="file"] { + padding: 2px; + } + + #dropPanel { + padding: 20px; + + .droppable { + background-color: #f7f7f7; + + textarea { + background-color: #f7f7f7; + } + } + + textarea { + width: 100%; + height: 120px; + } + } + + .test-results-panel { + padding: 20px; + + .test-results-list { + width: 100%; + margin: 20px 0 30px; + + th, + td { + padding: 5px 10px; + text-align: left; + } + + th { + background: #f7f7f7; + border-bottom: 1px solid #ddd; + font-weight: 400; + } + + td { + border-bottom: 1px solid #eee; + } + } + } +} diff --git a/Plugins/Vorlon/plugins/unitTestRunner/qunit.js b/Plugins/Vorlon/plugins/unitTestRunner/qunit.js new file mode 100644 index 00000000..317ec406 --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/qunit.js @@ -0,0 +1,3881 @@ +/*! + * QUnit 1.18.1-pre + * http://qunitjs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-06-18T11:09Z + */ + +(function( window ) { + +var QUnit, + config, + onErrorFnPrev, + loggingCallbacks = {}, + fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, + now = Date.now || function() { + return new Date().getTime(); + }, + globalStartCalled = false, + runStarted = false, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + defined = { + document: window.document !== undefined, + setTimeout: window.setTimeout !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }()) + }, + /** + * Provides a normalized error string, correcting an issue + * with IE 7 (and prior) where Error.prototype.toString is + * not properly implemented + * + * Based on http://es5.github.com/#x15.11.4.4 + * + * @param {String|Error} error + * @return {String} error message + */ + errorString = function( error ) { + var name, message, + errorString = error.toString(); + if ( errorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return errorString; + } + }, + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + objectValues = function( obj ) { + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[ key ]; + vals[ key ] = val === Object( val ) ? objectValues( val ) : val; + } + } + return vals; + }; + +QUnit = {}; + +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // by default, modify document.title when suite is done + altertitle: true, + + // by default, scroll to top of the page when suite is done + scrolltop: true, + + // when enabled, all tests must call expect() + requireExpects: false, + + // depth up-to which object will be dumped + maxDepth: 5, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + + "`window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: [], + + // The first unnamed module + currentModule: { + name: "", + tests: [] + }, + + callbacks: {} +}; + +// Push a loose unnamed module to the modules collection +config.modules.push( config.currentModule ); + +// Initialize more QUnit.config and QUnit.urlParams +(function() { + var i, current, + location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}; + + if ( params[ 0 ] ) { + for ( i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + if ( urlParams[ current[ 0 ] ] ) { + urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); + } else { + urlParams[ current[ 0 ] ] = current[ 1 ]; + } + } + } + + if ( urlParams.filter === true ) { + delete urlParams.filter; + } + + QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName + config.filter = urlParams.filter; + + if ( urlParams.maxDepth ) { + config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ? + Number.POSITIVE_INFINITY : + urlParams.maxDepth; + } + + config.testId = []; + if ( urlParams.testId ) { + + // Ensure that urlParams.testId is an array + urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); + for ( i = 0; i < urlParams.testId.length; i++ ) { + config.testId.push( urlParams.testId[ i ] ); + } + } + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = location.protocol === "file:"; + + // Expose the current QUnit version + QUnit.version = "1.18.1-pre"; +}()); + +// Root QUnit object. +// `QUnit` initialized at top of scope +extend( QUnit, { + + // call on start of module test to prepend name to all tests + module: function( name, testEnvironment ) { + var currentModule = { + name: name, + testEnvironment: testEnvironment, + tests: [] + }; + + // DEPRECATED: handles setup/teardown functions, + // beforeEach and afterEach should be used instead + if ( testEnvironment && testEnvironment.setup ) { + testEnvironment.beforeEach = testEnvironment.setup; + delete testEnvironment.setup; + } + if ( testEnvironment && testEnvironment.teardown ) { + testEnvironment.afterEach = testEnvironment.teardown; + delete testEnvironment.teardown; + } + + config.modules.push( currentModule ); + config.currentModule = currentModule; + }, + + // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. + asyncTest: function( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); + }, + + test: function( testName, expected, callback, async ) { + var test; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + test = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + test.queue(); + }, + + skip: function( testName ) { + var test = new Test({ + testName: testName, + skip: true + }); + + test.queue(); + }, + + // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. + // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. + start: function( count ) { + var globalStartAlreadyCalled = globalStartCalled; + + if ( !config.current ) { + globalStartCalled = true; + + if ( runStarted ) { + throw new Error( "Called start() outside of a test context while already started" ); + } else if ( globalStartAlreadyCalled || count > 1 ) { + throw new Error( "Called start() outside of a test context too many times" ); + } else if ( config.autostart ) { + throw new Error( "Called start() outside of a test context when " + + "QUnit.config.autostart was true" ); + } else if ( !config.pageLoaded ) { + + // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it + config.autostart = true; + return; + } + } else { + + // If a test is running, adjust its semaphore + config.current.semaphore -= count || 1; + + // Don't start until equal number of stop-calls + if ( config.current.semaphore > 0 ) { + return; + } + + // throw an Error if start is called more often than stop + if ( config.current.semaphore < 0 ) { + config.current.semaphore = 0; + + QUnit.pushFailure( + "Called start() while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; + } + } + + resumeProcessing(); + }, + + // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. + stop: function( count ) { + + // If there isn't a test running, don't allow QUnit.stop() to be called + if ( !config.current ) { + throw new Error( "Called stop() outside of a test context" ); + } + + // If a test is running, adjust its semaphore + config.current.semaphore += count || 1; + + pauseProcessing(); + }, + + config: config, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) === type; + }, + + objectType: function( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ] || ""; + + switch ( type ) { + case "Number": + if ( isNaN( obj ) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Date": + case "RegExp": + case "Function": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } + return undefined; + }, + + extend: extend, + + load: function() { + config.pageLoaded = true; + + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true ); + + config.blocking = false; + + if ( config.autostart ) { + resumeProcessing(); + } + }, + + stack: function( offset ) { + offset = ( offset || 0 ) + 2; + return sourceFromStacktrace( offset ); + } +}); + +// Register logging callbacks +(function() { + var i, l, key, + callbacks = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( QUnit.objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); + } + + config.callbacks[ key ].push( callback ); + }; + + // DEPRECATED: This will be removed on QUnit 2.0.0+ + // Stores the registered functions allowing restoring + // at verifyLoggingCallbacks() if modified + loggingCallbacks[ key ] = loggingCallback; + + return loggingCallback; + } + + for ( i = 0, l = callbacks.length; i < l; i++ ) { + key = callbacks[ i ]; + + // Initialize key collection of logging callback + if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; + } + + QUnit[ key ] = registerLoggingCallback( key ); + } +})(); + +// `onErrorFnPrev` initialized at top of scope +// Preserve other handlers +onErrorFnPrev = window.onerror; + +// Cover uncaught exceptions +// Returning true will suppress the default browser handler, +// returning false will let it run. +window.onerror = function( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend(function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: true } ) ); + } + return false; + } + + return ret; +}; + +function done() { + var runtime, passed; + + config.autorun = true; + + // Log the last module results + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + }); + } + delete config.previousModule; + + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; + + runLoggingCallbacks( "done", { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + }); +} + +// Doesn't support IE6 to IE9, it will return undefined on these browsers +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if ( e.stack ) { + stack = e.stack.split( "\n" ); + if ( /^error$/i.test( stack[ 0 ] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + + // Support: Safari <=6 only + } else if ( e.sourceURL ) { + + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} + +function sourceFromStacktrace( offset ) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if ( !error.stack ) { + try { + throw error; + } catch ( err ) { + error = err; + } + } + + return extractStacktrace( error, offset ); +} + +function synchronize( callback, last ) { + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process( last ); + } +} + +function process( last ) { + function next() { + process( last ); + } + var start = now(); + config.depth = ( config.depth || 0 ) + 1; + + while ( config.queue.length && !config.blocking ) { + if ( !defined.setTimeout || config.updateRate <= 0 || + ( ( now() - start ) < config.updateRate ) ) { + if ( config.current ) { + + // Reset async tracking for each phase of the Test lifecycle + config.current.usedAsync = false; + } + config.queue.shift()(); + } else { + setTimeout( next, 13 ); + break; + } + } + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); + } +} + +function begin() { + var i, l, + modulesLog = []; + + // If the test run hasn't officially begun yet + if ( !config.started ) { + + // Record the time of the test run's beginning + config.started = now(); + + verifyLoggingCallbacks(); + + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); + } + + // Avoid unnecessary information by not logging modules' test environments + for ( i = 0, l = config.modules.length; i < l; i++ ) { + modulesLog.push({ + name: config.modules[ i ].name, + tests: config.modules[ i ].tests + }); + } + + // The test run is officially beginning now + runLoggingCallbacks( "begin", { + totalTests: Test.count, + modules: modulesLog + }); + } + + config.blocking = false; + process( true ); +} + +function resumeProcessing() { + runStarted = true; + + // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) + if ( defined.setTimeout ) { + setTimeout(function() { + if ( config.current && config.current.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + begin(); + }, 13 ); + } else { + begin(); + } +} + +function pauseProcessing() { + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { + if ( config.current ) { + config.current.semaphore = 0; + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + } else { + throw new Error( "Test timed out" ); + } + resumeProcessing(); + }, config.testTimeout ); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + if ( hasOwn.call( window, key ) ) { + // in Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } +} + +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); + } + + deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[ i ] === b[ j ] ) { + result.splice( i, 1 ); + i--; + break; + } + } + } + return result; +} + +function extend( a, b, undefOnly ) { + for ( var prop in b ) { + if ( hasOwn.call( b, prop ) ) { + + // Avoid "Member not found" error in IE8 caused by messing with window.constructor + if ( !( prop === "constructor" && a === window ) ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; + } + } + } + } + + return a; +} + +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; + + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); + } +} + +// DEPRECATED: This will be removed on 2.0.0+ +// This function verifies if the loggingCallbacks were modified by the user +// If so, it will restore it, assign the given callback and print a console warning +function verifyLoggingCallbacks() { + var loggingCallback, userCallback; + + for ( loggingCallback in loggingCallbacks ) { + if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { + + userCallback = QUnit[ loggingCallback ]; + + // Restore the callback function + QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; + + // Assign the deprecated given callback + QUnit[ loggingCallback ]( userCallback ); + + if ( window.console && window.console.warn ) { + window.console.warn( + "QUnit." + loggingCallback + " was replaced with a new value.\n" + + "Please, check out the documentation on how to apply logging callbacks.\n" + + "Reference: http://api.qunitjs.com/category/callbacks/" + ); + } + } + } +} + +// from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +function Test( settings ) { + var i, l; + + ++Test.count; + + extend( this, settings ); + this.assertions = []; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); + + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; + } + } + + this.testId = generateHash( this.module.name, this.testName ); + + this.module.tests.push({ + name: this.testName, + testId: this.testId + }); + + if ( settings.skip ) { + + // Skipped tests will fully ignore any sent callback + this.callback = function() {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert( this ); + } +} + +Test.count = 0; + +Test.prototype = { + before: function() { + if ( + + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || + + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + }); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0, started: now() }; + runLoggingCallbacks( "moduleStart", { + name: this.module.name, + tests: this.module.tests + }); + } + + config.current = this; + + this.testEnvironment = extend( {}, this.module.testEnvironment ); + delete this.testEnvironment.beforeEach; + delete this.testEnvironment.afterEach; + + this.started = now(); + runLoggingCallbacks( "testStart", { + name: this.testName, + module: this.module.name, + testId: this.testId + }); + + if ( !config.pollution ) { + saveGlobal(); + } + }, + + run: function() { + var promise; + + config.current = this; + + if ( this.async ) { + QUnit.stop(); + } + + this.callbackStarted = now(); + + if ( config.notrycatch ) { + promise = this.callback.call( this.testEnvironment, this.assert ); + this.resolvePromise( promise ); + return; + } + + try { + promise = this.callback.call( this.testEnvironment, this.assert ); + this.resolvePromise( promise ); + } catch ( e ) { + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + QUnit.start(); + } + } + }, + + after: function() { + checkPollution(); + }, + + queueHook: function( hook, hookName ) { + var promise, + test = this; + return function runHook() { + config.current = test; + if ( config.notrycatch ) { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + return; + } + try { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + } catch ( error ) { + test.pushFailure( hookName + " failed on " + test.testName + ": " + + ( error.message || error ), extractStacktrace( error, 0 ) ); + } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function( handler ) { + var hooks = []; + + // Hooks are ignored on skipped tests + if ( this.skip ) { + return hooks; + } + + if ( this.module.testEnvironment && + QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); + } + + return hooks; + }, + + finish: function() { + config.current = this; + if ( config.requireExpects && this.expected === null ) { + this.pushFailure( "Expected number of assertions to be defined, but expect() was " + + "not called.", this.stack ); + } else if ( this.expected !== null && this.expected !== this.assertions.length ) { + this.pushFailure( "Expected " + this.expected + " assertions, but " + + this.assertions.length + " were run", this.stack ); + } else if ( this.expected === null && !this.assertions.length ) { + this.pushFailure( "Expected at least one assertion, but none were run - call " + + "expect(0) to accept zero assertions.", this.stack ); + } + + var i, + bad = 0; + + this.runtime = now() - this.started; + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[ i ].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + runLoggingCallbacks( "testDone", { + name: this.testName, + module: this.module.name, + skipped: !!this.skip, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + source: this.stack, + + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead + duration: this.runtime + }); + + // QUnit.reset() is deprecated and will be replaced for a new + // fixture reset function on QUnit 2.0/2.1. + // It's still called here for backwards compatibility handling + QUnit.reset(); + + config.current = undefined; + }, + + queue: function() { + var bad, + test = this; + + if ( !this.valid() ) { + return; + } + + function run() { + + // each of these can by async + synchronize([ + function() { + test.before(); + }, + + test.hooks( "beforeEach" ), + + function() { + test.run(); + }, + + test.hooks( "afterEach" ).reverse(), + + function() { + test.after(); + }, + function() { + test.finish(); + } + ]); + } + + // `bad` initialized at top of scope + // defer when previous test run passed, if storage is available + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); + + if ( bad ) { + run(); + } else { + synchronize( run, true ); + } + }, + + push: function( result, actual, expected, message, negative ) { + var source, + details = { + module: this.module.name, + name: this.testName, + result: result, + message: message, + actual: actual, + expected: expected, + testId: this.testId, + negative: negative || false, + runtime: now() - this.started + }; + + if ( !result ) { + source = sourceFromStacktrace(); + + if ( source ) { + details.source = source; + } + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: !!result, + message: message + }); + }, + + pushFailure: function( message, source, actual ) { + if ( !this instanceof Test ) { + throw new Error( "pushFailure() assertion outside test context, was " + + sourceFromStacktrace( 2 ) ); + } + + var details = { + module: this.module.name, + name: this.testName, + result: false, + message: message || "error", + actual: actual || null, + testId: this.testId, + runtime: now() - this.started + }; + + if ( source ) { + details.source = source; + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: false, + message: message + }); + }, + + resolvePromise: function( promise, phase ) { + var then, message, + test = this; + if ( promise != null ) { + then = promise.then; + if ( QUnit.objectType( then ) === "function" ) { + QUnit.stop(); + then.call( + promise, + QUnit.start, + function( error ) { + message = "Promise rejected " + + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + + " " + test.testName + ": " + ( error.message || error ); + test.pushFailure( message, extractStacktrace( error, 0 ) ); + + // else next test will carry the responsibility + saveGlobal(); + + // Unblock + QUnit.start(); + } + ); + } + } + }, + + valid: function() { + var include, + filter = config.filter && config.filter.toLowerCase(), + module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); + + // Internally-generated tests are always valid + if ( this.callback && this.callback.validTest ) { + return true; + } + + if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + return false; + } + + if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; + } + +}; + +// Resets the test setup. Useful for tests that modify the DOM. +/* +DEPRECATED: Use multiple tests instead of resetting inside a test. +Use testStart or testDone for custom cleanup. +This method will throw an error in 2.0, and will be removed in 2.1 +*/ +QUnit.reset = function() { + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + var fixture = defined.document && document.getElementById && + document.getElementById( "qunit-fixture" ); + + if ( fixture ) { + fixture.innerHTML = config.fixture; + } +}; + +QUnit.pushFailure = function() { + if ( !QUnit.config.current ) { + throw new Error( "pushFailure() assertion outside test context, in " + + sourceFromStacktrace( 2 ) ); + } + + // Gets current test obj + var currentTest = QUnit.config.current; + + return currentTest.pushFailure.apply( currentTest, arguments ); +}; + +// Based on Java's String.hashCode, a simple but not +// rigorously collision resistant hashing function +function generateHash( module, testName ) { + var hex, + i = 0, + hash = 0, + str = module + "\x1C" + testName, + len = str.length; + + for ( ; i < len; i++ ) { + hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + hex = ( 0x100000000 + hash ).toString( 16 ); + if ( hex.length < 8 ) { + hex = "0000000" + hex; + } + + return hex.slice( -8 ); +} + +function Assert( testContext ) { + this.test = testContext; +} + +// Assert helpers +QUnit.assert = Assert.prototype = { + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if ( arguments.length === 1 ) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + }, + + // Increment this Test's semaphore counter, then return a single-use function that + // decrements that counter a maximum of once. + async: function() { + var test = this.test, + popped = false; + + test.semaphore += 1; + test.usedAsync = true; + pauseProcessing(); + + return function done() { + if ( !popped ) { + test.semaphore -= 1; + popped = true; + resumeProcessing(); + } else { + test.pushFailure( "Called the callback returned from `assert.async` more than once", + sourceFromStacktrace( 2 ) ); + } + }; + }, + + // Exports test.push() to the user API + push: function( /* result, actual, expected, message, negative */ ) { + var assert = this, + currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if ( !currentTest ) { + throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + + if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { + currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", + sourceFromStacktrace( 2 ) ); + + // Allow this assertion to continue running anyway... + } + + if ( !( assert instanceof Assert ) ) { + assert = currentTest.assert; + } + return assert.test.push.apply( assert.test, arguments ); + }, + + ok: function( result, message ) { + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + + QUnit.dump.parse( result ) ); + this.push( !!result, result, true, message ); + }, + + notOk: function( result, message ) { + message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + + QUnit.dump.parse( result ) ); + this.push( !result, result, false, message, true ); + }, + + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.push( expected == actual, actual, expected, message ); + }, + + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.push( expected != actual, actual, expected, message, true ); + }, + + propEqual: function( actual, expected, message ) { + actual = objectValues( actual ); + expected = objectValues( expected ); + this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + }, + + notPropEqual: function( actual, expected, message ) { + actual = objectValues( actual ); + expected = objectValues( expected ); + this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + }, + + deepEqual: function( actual, expected, message ) { + this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + }, + + notDeepEqual: function( actual, expected, message ) { + this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + }, + + strictEqual: function( actual, expected, message ) { + this.push( expected === actual, actual, expected, message ); + }, + + notStrictEqual: function( actual, expected, message ) { + this.push( expected !== actual, actual, expected, message, true ); + }, + + "throws": function( block, expected, message ) { + var actual, expectedType, + expectedOutput = expected, + ok = false, + currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; + + // 'expected' is optional unless doing string comparison + if ( message == null && typeof expected === "string" ) { + message = expected; + expected = null; + } + + currentTest.ignoreGlobalErrors = true; + try { + block.call( currentTest.testEnvironment ); + } catch (e) { + actual = e; + } + currentTest.ignoreGlobalErrors = false; + + if ( actual ) { + expectedType = QUnit.objectType( expected ); + + // we don't want to validate thrown error + if ( !expected ) { + ok = true; + expectedOutput = null; + + // expected is a regexp + } else if ( expectedType === "regexp" ) { + ok = expected.test( errorString( actual ) ); + + // expected is a string + } else if ( expectedType === "string" ) { + ok = expected === errorString( actual ); + + // expected is a constructor, maybe an Error constructor + } else if ( expectedType === "function" && actual instanceof expected ) { + ok = true; + + // expected is an Error object + } else if ( expectedType === "object" ) { + ok = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a validation function which returns true if validation passed + } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { + expectedOutput = null; + ok = true; + } + } + + currentTest.assert.push( ok, actual, expectedOutput, message ); + } +}; + +// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word +// Known to us are: Closure Compiler, Narwhal +(function() { + /*jshint sub:true */ + Assert.prototype.raises = Assert.prototype[ "throws" ]; +}()); + +// Test for equality any JavaScript type. +// Author: Philippe Rathé +QUnit.equiv = (function() { + + // Call the o related callback with the given arguments. + function bindCallbacks( o, callbacks, args ) { + var prop = QUnit.objectType( o ); + if ( prop ) { + if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { + return callbacks[ prop ].apply( callbacks, args ); + } else { + return callbacks[ prop ]; // or undefined + } + } + } + + // the real equiv function + var innerEquiv, + + // stack to decide between skip/abort functions + callers = [], + + // stack to avoiding loops from circular referencing + parents = [], + parentsB = [], + + getProto = Object.getPrototypeOf || function( obj ) { + /* jshint camelcase: false, proto: true */ + return obj.__proto__; + }, + callbacks = (function() { + + // for string, boolean, number and null + function useStrictEquality( b, a ) { + + /*jshint eqeqeq:false */ + if ( b instanceof a.constructor || a instanceof b.constructor ) { + + // to catch short annotation VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function( b ) { + return isNaN( b ); + }, + + "date": function( b, a ) { + return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function( b, a ) { + return QUnit.objectType( b ) === "regexp" && + + // the regex itself + a.source === b.source && + + // and its modifiers + a.global === b.global && + + // (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline && + a.sticky === b.sticky; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[ callers.length - 1 ]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + // b could be an object literal here + if ( QUnit.objectType( b ) !== "array" ) { + return false; + } + + len = a.length; + if ( len !== b.length ) { + // safe and faster + return false; + } + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { + parents.pop(); + parentsB.pop(); + return false; + } + } + } + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + parents.pop(); + parentsB.pop(); + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, + + "object": function( b, a ) { + + /*jshint forin:false */ + var i, j, loop, aCircular, bCircular, + // Default to true + eq = true, + aProperties = [], + bProperties = []; + + // comparing constructors is more strict than using + // instanceof + if ( a.constructor !== b.constructor ) { + + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) || + ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) { + return false; + } + } + + // stack constructor before traversing properties + callers.push( a.constructor ); + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } + } + } + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + eq = false; + break; + } + } + + parents.pop(); + parentsB.pop(); + callers.pop(); // unstack, we are done + + for ( i in b ) { + bProperties.push( i ); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + }()); + + innerEquiv = function() { // can take multiple arguments + var args = [].slice.apply( arguments ); + if ( args.length < 2 ) { + return true; // end transition + } + + return ( (function( a, b ) { + if ( a === b ) { + return true; // catch the most you can + } else if ( a === null || b === null || typeof a === "undefined" || + typeof b === "undefined" || + QUnit.objectType( a ) !== QUnit.objectType( b ) ) { + + // don't lose time with error prone cases + return false; + } else { + return bindCallbacks( a, callbacks, [ b, a ] ); + } + + // apply transition with (1..n) arguments + }( args[ 0 ], args[ 1 ] ) ) && + innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); + }; + + return innerEquiv; +}()); + +// Based on jsDump by Ariel Flesler +// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html +QUnit.dump = (function() { + function quote( str ) { + return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; + } + function literal( o ) { + return o + ""; + } + function join( pre, arr, post ) { + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent( 1 ); + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { + return pre + post; + } + return [ pre, inner + arr, base + post ].join( s ); + } + function array( arr, stack ) { + var i = arr.length, + ret = new Array( i ); + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Array]"; + } + + this.up(); + while ( i-- ) { + ret[ i ] = this.parse( arr[ i ], undefined, stack ); + } + this.down(); + return join( "[", ret, "]" ); + } + + var reName = /^function (\w+)/, + dump = { + + // objType is used mostly internally, you can fix a (custom) type in advance + parse: function( obj, objType, stack ) { + stack = stack || []; + var res, parser, parserType, + inStack = inArray( obj, stack ); + + if ( inStack !== -1 ) { + return "recursion(" + ( inStack - stack.length ) + ")"; + } + + objType = objType || this.typeOf( obj ); + parser = this.parsers[ objType ]; + parserType = typeof parser; + + if ( parserType === "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); + stack.pop(); + return res; + } + return ( parserType === "string" ) ? parser : this.parsers.error; + }, + typeOf: function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj ) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj ) ) { + type = "date"; + } else if ( QUnit.is( "function", obj ) ) { + type = "function"; + } else if ( obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( + + // native arrays + toString.call( obj ) === "[object Array]" || + + // NodeList objects + ( typeof obj.length === "number" && obj.item !== undefined && + ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && + obj[ 0 ] === undefined ) ) ) + ) { + type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; + } else { + type = typeof obj; + } + return type; + }, + separator: function() { + return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; + }, + // extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { + if ( !this.multiline ) { + return ""; + } + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + } + return new Array( this.depth + ( extra || 0 ) ).join( chr ); + }, + up: function( a ) { + this.depth += a || 1; + }, + down: function( a ) { + this.depth -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[ name ] = parser; + }, + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + // + depth: 1, + maxDepth: QUnit.config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: function( error ) { + return "Error(\"" + error.message + "\")"; + }, + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + + // functions never have name in IE + name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; + + if ( name ) { + ret += " " + name; + } + ret += "( "; + + ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, dump.parse( fn, "functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + var keys, key, val, i, nonEnumerableProperties, + ret = []; + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Object]"; + } + + dump.up(); + keys = []; + for ( key in map ) { + keys.push( key ); + } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = [ "message", "name" ]; + for ( i in nonEnumerableProperties ) { + key = nonEnumerableProperties[ i ]; + if ( key in map && inArray( key, keys ) < 0 ) { + keys.push( key ); + } + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( dump.parse( key, "key" ) + ": " + + dump.parse( val, undefined, stack ) ); + } + dump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var len, i, val, + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag, + attrs = node.attributes; + + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[ i ].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[ i ].nodeName + "=" + + dump.parse( val, "attribute" ); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; + }, + + // function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array( l ); + while ( l-- ) { + + // 97 is 'a' + args[ l ] = String.fromCharCode( 97 + l ); + } + return " " + args.join( ", " ) + " "; + }, + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + "boolean": literal + }, + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return dump; +}()); + +// back compat +QUnit.jsDump = QUnit.dump; + +// For browser, export only select globals +if ( typeof window !== "undefined" ) { + + // Deprecated + // Extend assert methods to QUnit and Global scope through Backwards compatibility + (function() { + var i, + assertions = Assert.prototype; + + function applyCurrent( current ) { + return function() { + var assert = new Assert( QUnit.config.current ); + current.apply( assert, arguments ); + }; + } + + for ( i in assertions ) { + QUnit[ i ] = applyCurrent( assertions[ i ] ); + } + })(); + + (function() { + var i, l, + keys = [ + "test", + "module", + "expect", + "asyncTest", + "start", + "stop", + "ok", + "notOk", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws" + ]; + + for ( i = 0, l = keys.length; i < l; i++ ) { + window[ keys[ i ] ] = QUnit[ keys[ i ] ]; + } + })(); + + window.QUnit = QUnit; +} + +// For nodejs +if ( typeof module !== "undefined" && module && module.exports ) { + module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; +} + +// For CommonJS with exports, but without module.exports, like Rhino +if ( typeof exports !== "undefined" && exports ) { + exports.QUnit = QUnit; +} + +if ( typeof define === "function" && define.amd ) { + define( function() { + return QUnit; + } ); + QUnit.config.autostart = false; +} + +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +})() )); + +/*istanbul ignore next */ +// jscs:disable maximumLineLength +/* + * This file is a modified version of google-diff-match-patch's JavaScript implementation + * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), + * modifications are licensed as more fully set forth in LICENSE.txt. + * + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More Info: + * https://code.google.com/p/google-diff-match-patch/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick brown fox jumpsed} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) { + var deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; + // Set a deadline by which time the diff must be complete. + if ( typeof optDeadline === "undefined" ) { + if ( this.DiffTimeout <= 0 ) { + optDeadline = Number.MAX_VALUE; + } else { + optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000; + } + } + deadline = optDeadline; + + // Check for null inputs. + if ( text1 === null || text2 === null ) { + throw new Error( "Null input. (DiffMain)" ); + } + + // Check for equality (speedup). + if ( text1 === text2 ) { + if ( text1 ) { + return [ + [ DIFF_EQUAL, text1 ] + ]; + } + return []; + } + + if ( typeof optChecklines === "undefined" ) { + optChecklines = true; + } + + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix( text1, text2 ); + commonprefix = text1.substring( 0, commonlength ); + text1 = text1.substring( commonlength ); + text2 = text2.substring( commonlength ); + + // Trim off common suffix (speedup). + ///////// + commonlength = this.diffCommonSuffix( text1, text2 ); + commonsuffix = text1.substring( text1.length - commonlength ); + text1 = text1.substring( 0, text1.length - commonlength ); + text2 = text2.substring( 0, text2.length - commonlength ); + + // Compute the diff on the middle block. + diffs = this.diffCompute( text1, text2, checklines, deadline ); + + // Restore the prefix and suffix. + if ( commonprefix ) { + diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + } + if ( commonsuffix ) { + diffs.push( [ DIFF_EQUAL, commonsuffix ] ); + } + this.diffCleanupMerge( diffs ); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. + preIns = false; + // Is there a deletion operation before the last equality. + preDel = false; + // Is there an insertion operation after the last equality. + postIns = false; + // Is there a deletion operation after the last equality. + postDel = false; + while ( pointer < diffs.length ) { + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. + if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) { + // Candidate found. + equalities[ equalitiesLength++ ] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[ pointer ][ 1 ]; + } else { + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + } else { // An insertion or deletion. + if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { + postDel = true; + } else { + postIns = true; + } + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || + ( ( lastequality.length < this.DiffEditCost / 2 ) && + ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { + // Duplicate record. + diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] ); + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if (preIns && preDel) { + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { + var op, data, x, html = []; + for ( x = 0; x < diffs.length; x++ ) { + op = diffs[x][0]; // Operation (insert, delete, equal) + data = diffs[x][1]; // Text of change. + switch ( op ) { + case DIFF_INSERT: + html[x] = "" + data + ""; + break; + case DIFF_DELETE: + html[x] = "" + data + ""; + break; + case DIFF_EQUAL: + html[x] = "" + data + ""; + break; + } + } + return html.join(""); + }; + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerstart; + // Quick check for common null cases. + if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerstart = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerend; + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min(text1.length, text2.length); + pointermid = pointermax; + pointerend = 0; + while ( pointermin < pointermid ) { + if (text1.substring( text1.length - pointermid, text1.length - pointerend ) === + text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { + var diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; + + if ( !text1 ) { + // Just add some text (speedup). + return [ + [ DIFF_INSERT, text2 ] + ]; + } + + if (!text2) { + // Just delete some text (speedup). + return [ + [ DIFF_DELETE, text1 ] + ]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf( shorttext ); + if ( i !== -1 ) { + // Shorter text is inside the longer text (speedup). + diffs = [ + [ DIFF_INSERT, longtext.substring( 0, i ) ], + [ DIFF_EQUAL, shorttext ], + [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] + ]; + // Swap insertions for deletions if diff is reversed. + if ( text1.length > text2.length ) { + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + } + return diffs; + } + + if ( shorttext.length === 1 ) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch(text1, text2); + if (hm) { + // A half-match was found, sort out the return data. + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + midCommon = hm[4]; + // Send both pairs off for separate processing. + diffsA = this.DiffMain(text1A, text2A, checklines, deadline); + diffsB = this.DiffMain(text1B, text2B, checklines, deadline); + // Merge the results. + return diffsA.concat([ + [ DIFF_EQUAL, midCommon ] + ], diffsB); + } + + if (checklines && text1.length > 100 && text2.length > 100) { + return this.diffLineMode(text1, text2, deadline); + } + + return this.diffBisect(text1, text2, deadline); + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) { + var longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; + if (this.DiffTimeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return null; + } + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI(longtext, shorttext, i) { + var seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); + j = -1; + bestCommon = ""; + while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { + prefixLength = dmp.diffCommonPrefix(longtext.substring(i), + shorttext.substring(j)); + suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (bestCommon.length < suffixLength + prefixLength) { + bestCommon = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + bestLongtextA = longtext.substring(0, i - suffixLength); + bestLongtextB = longtext.substring(i + prefixLength); + bestShorttextA = shorttext.substring(0, j - suffixLength); + bestShorttextB = shorttext.substring(j + prefixLength); + } + } + if (bestCommon.length * 2 >= longtext.length) { + return [ bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI(longtext, shorttext, + Math.ceil(longtext.length / 4)); + // Check again based on the third quarter. + hm2 = diffHalfMatchI(longtext, shorttext, + Math.ceil(longtext.length / 2)); + if (!hm1 && !hm2) { + return null; + } else if (!hm2) { + hm = hm1; + } else if (!hm1) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + text1A, text1B, text2A, text2B; + if (text1.length > text2.length) { + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + } else { + text2A = hm[0]; + text2B = hm[1]; + text1A = hm[2]; + text1B = hm[3]; + } + midCommon = hm[4]; + return [ text1A, text1B, text2A, text2B, midCommon ]; + }; + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) { + var a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain(text1, text2, false, deadline); + + // Convert the diff back to original text. + this.diffCharsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push( [ DIFF_EQUAL, "" ] ); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while (pointer < diffs.length) { + switch ( diffs[pointer][0] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (countDelete >= 1 && countInsert >= 1) { + // Delete the offending records and add the merged ones. + diffs.splice(pointer - countDelete - countInsert, + countDelete + countInsert); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain(textDelete, textInsert, false, deadline); + for (j = a.length - 1; j >= 0; j--) { + diffs.splice( pointer, 0, a[j] ); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) { + var text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil((text1Length + text2Length) / 2); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array(vLength); + v2 = new Array(vLength); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for (x = 0; x < vLength; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[vOffset + 1] = 0; + v2[vOffset + 1] = 0; + delta = text1Length - text2Length; + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = (delta % 2 !== 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for (d = 0; d < maxD; d++) { + // Bail out if deadline is reached. + if ((new Date()).getTime() > deadline) { + break; + } + + // Walk the front path one step. + for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + k1Offset = vOffset + k1; + if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { + x1 = v1[k1Offset + 1]; + } else { + x1 = v1[k1Offset - 1] + 1; + } + y1 = x1 - k1; + while (x1 < text1Length && y1 < text2Length && + text1.charAt(x1) === text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1Offset] = x1; + if (x1 > text1Length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2Length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + k2Offset = vOffset + delta - k1; + if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[k2Offset]; + if (x1 >= x2) { + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + k2Offset = vOffset + k2; + if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { + x2 = v2[k2Offset + 1]; + } else { + x2 = v2[k2Offset - 1] + 1; + } + y2 = x2 - k2; + while (x2 < text1Length && y2 < text2Length && + text1.charAt(text1Length - x2 - 1) === + text2.charAt(text2Length - y2 - 1)) { + x2++; + y2++; + } + v2[k2Offset] = x2; + if (x2 > text1Length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2Length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + k1Offset = vOffset + delta - k2; + if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { + x1 = v1[k1Offset]; + y1 = vOffset + x1 - k1Offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if (x1 >= x2) { + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring(0, x); + text2a = text2.substring(0, y); + text1b = text1.substring(x); + text2b = text2.substring(y); + + // Compute both diffs serially. + diffs = this.DiffMain(text1a, text2a, false, deadline); + diffsb = this.DiffMain(text1b, text2b, false, deadline); + + return diffs.concat(diffsb); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) { + var changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while (pointer < diffs.length) { + if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found. + equalities[equalitiesLength++] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[pointer][1]; + } else { // An insertion or deletion. + if (diffs[pointer][0] === DIFF_INSERT) { + lengthInsertions2 += diffs[pointer][1].length; + } else { + lengthDeletions2 += diffs[pointer][1].length; + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastequality && (lastequality.length <= + Math.max(lengthInsertions1, lengthDeletions1)) && + (lastequality.length <= Math.max(lengthInsertions2, + lengthDeletions2))) { + // Duplicate record. + diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] ); + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + // Throw away the equality we just deleted. + equalitiesLength--; + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + lengthInsertions1 = 0; // Reset the counters. + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } + + // Normalize the diff. + if (changes) { + this.diffCleanupMerge(diffs); + } + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while (pointer < diffs.length) { + if (diffs[pointer - 1][0] === DIFF_DELETE && + diffs[pointer][0] === DIFF_INSERT) { + deletion = diffs[pointer - 1][1]; + insertion = diffs[pointer][1]; + overlapLength1 = this.diffCommonOverlap(deletion, insertion); + overlapLength2 = this.diffCommonOverlap(insertion, deletion); + if (overlapLength1 >= overlapLength2) { + if (overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2) { + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] ); + diffs[pointer - 1][1] = + deletion.substring(0, deletion.length - overlapLength1); + diffs[pointer + 1][1] = insertion.substring(overlapLength1); + pointer++; + } + } else { + if (overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] ); + diffs[pointer - 1][0] = DIFF_INSERT; + diffs[pointer - 1][1] = + insertion.substring(0, insertion.length - overlapLength2); + diffs[pointer + 1][0] = DIFF_DELETE; + diffs[pointer + 1][1] = + deletion.substring(overlapLength2); + pointer++; + } + } + pointer++; + } + pointer++; + } + }; + + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) { + var text1Length, text2Length, textLength, + best, length, pattern, found; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + // Eliminate the null case. + if (text1Length === 0 || text2Length === 0) { + return 0; + } + // Truncate the longer string. + if (text1Length > text2Length) { + text1 = text1.substring(text1Length - text2Length); + } else if (text1Length < text2Length) { + text2 = text2.substring(0, text1Length); + } + textLength = Math.min(text1Length, text2Length); + // Quick check for the worst case. + if (text1 === text2) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while (true) { + pattern = text1.substring(textLength - length); + found = text2.indexOf(pattern); + if (found === -1) { + return best; + } + length += found; + if (found === 0 || text1.substring(textLength - length) === + text2.substring(0, length)) { + best = length; + length++; + } + } + }; + + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // e.g. lineArray[4] === 'Hello\n' + lineHash = {}; // e.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[0] = ""; + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge(text) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while (lineEnd < text.length - 1) { + lineEnd = text.indexOf("\n", lineStart); + if (lineEnd === -1) { + lineEnd = text.length - 1; + } + line = text.substring(lineStart, lineEnd + 1); + lineStart = lineEnd + 1; + + if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : + (lineHash[line] !== undefined)) { + chars += String.fromCharCode( lineHash[ line ] ); + } else { + chars += String.fromCharCode(lineArrayLength); + lineHash[line] = lineArrayLength; + lineArray[lineArrayLength++] = line; + } + } + return chars; + } + + chars1 = diffLinesToCharsMunge(text1); + chars2 = diffLinesToCharsMunge(text2); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { + var x, chars, text, y; + for ( x = 0; x < diffs.length; x++ ) { + chars = diffs[x][1]; + text = []; + for ( y = 0; y < chars.length; y++ ) { + text[y] = lineArray[chars.charCodeAt(y)]; + } + diffs[x][1] = text.join(""); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) { + var pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes; + diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + commonlength; + while (pointer < diffs.length) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (countDelete + countInsert > 1) { + if (countDelete !== 0 && countInsert !== 0) { + // Factor out any common prefixies. + commonlength = this.diffCommonPrefix(textInsert, textDelete); + if (commonlength !== 0) { + if ((pointer - countDelete - countInsert) > 0 && + diffs[pointer - countDelete - countInsert - 1][0] === + DIFF_EQUAL) { + diffs[pointer - countDelete - countInsert - 1][1] += + textInsert.substring(0, commonlength); + } else { + diffs.splice( 0, 0, [ DIFF_EQUAL, + textInsert.substring( 0, commonlength ) + ] ); + pointer++; + } + textInsert = textInsert.substring(commonlength); + textDelete = textDelete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix(textInsert, textDelete); + if (commonlength !== 0) { + diffs[pointer][1] = textInsert.substring(textInsert.length - + commonlength) + diffs[pointer][1]; + textInsert = textInsert.substring(0, textInsert.length - + commonlength); + textDelete = textDelete.substring(0, textDelete.length - + commonlength); + } + } + // Delete the offending records and add the merged ones. + if (countDelete === 0) { + diffs.splice( pointer - countInsert, + countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); + } else if (countInsert === 0) { + diffs.splice( pointer - countDelete, + countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); + } else { + diffs.splice( pointer - countDelete - countInsert, + countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] ); + } + pointer = pointer - countDelete - countInsert + + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1; + } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if (diffs[diffs.length - 1][1] === "") { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] === DIFF_EQUAL && + diffs[pointer + 1][0] === DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length - + diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) { + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + + diffs[pointer][1].substring(0, diffs[pointer][1].length - + diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === + diffs[ pointer + 1 ][ 1 ] ) { + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = + diffs[pointer][1].substring(diffs[pointer + 1][1].length) + + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + this.diffCleanupMerge(diffs); + } + }; + + return function(o, n) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain(o, n); + //console.log(output); + diff.diffCleanupEfficiency(output); + text = diff.diffPrettyHtml(output); + + return text; + }; +}()); +// jscs:enable + +(function() { + +// Deprecated QUnit.init - Ref #530 +// Re-initialize the configuration options +QUnit.init = function() { + var tests, banner, result, qunit, + config = QUnit.config; + + config.stats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0 }; + config.started = 0; + config.updateRate = 1000; + config.blocking = false; + config.autostart = true; + config.autorun = false; + config.filter = ""; + config.queue = []; + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + qunit = id( "qunit" ); + if ( qunit ) { + qunit.innerHTML = + "

" + escapeText( document.title ) + "

" + + "

" + + "
" + + "

" + + "
    "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
     "; + } +}; + +// Don't load the HTML Reporter on non-Browser environments +if ( typeof window === "undefined" ) { + return; +} + +var config = QUnit.config, + hasOwn = Object.prototype.hasOwnProperty, + defined = { + document: window.document !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }()) + }, + modulesList = []; + +/** +* Escape text for attribute or text content. +*/ +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); +} + +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { + if ( elem.addEventListener ) { + + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; + } + + fn.call( elem, event ); + }); + } +} + +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[ i ], type, fn ); + } +} + +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} + +function toggleClass( elem, name ) { + if ( hasClass( elem, name ) ) { + removeClass( elem, name ); + } else { + addClass( elem, name ); + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } + + // trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} + +function id( name ) { + return defined.document && document.getElementById && document.getElementById( name ); +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + len = config.urlConfig.length, + urlConfigHtml = ""; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( config[ val.id ] === undefined ) { + config[ val.id ] = QUnit.urlParams[ val.id ]; + } + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + config[ field.name ] = value || false; + if ( value ) { + addClass( id( "qunit-tests" ), "hidepass" ); + } else { + removeClass( id( "qunit-tests" ), "hidepass" ); + } + + // It is not necessary to refresh the whole page + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, + querystring = "?"; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + if ( hasOwn.call( params, key ) ) { + if ( params[ key ] === undefined ) { + continue; + } + querystring += encodeURIComponent( key ); + if ( params[ key ] !== true ) { + querystring += "=" + encodeURIComponent( params[ key ] ); + } + querystring += "&"; + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var selectedModule, + modulesList = id( "qunit-modulefilter" ), + filter = id( "qunit-filter-input" ).value; + + selectedModule = modulesList ? + decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : + undefined; + + window.location = setUrl({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + filter: ( filter === "" ) ? undefined : filter, + + // Remove testId filter + testId: undefined + }); +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); + + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + + return urlConfigContainer; +} + +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; + }); + + return filter; +} + +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = ""; + + if ( !modulesList.length ) { + return false; + } + + modulesList.sort(function( a, b ) { + return a.localeCompare( b ); + }); + + moduleFilterHtml += "" + + ""; + + return moduleFilterHtml; +} + +function toolbarModuleFilter() { + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !toolbar || !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + + toolbar.appendChild( moduleFilter ); +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
     "; + } +} + +function storeFixture() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin(function( details ) { + var qunit = id( "qunit" ); + + // Fixture is the only one necessary to run without the #qunit element + storeFixture(); + + if ( qunit ) { + qunit.innerHTML = + "

    " + escapeText( document.title ) + "

    " + + "

    " + + "
    " + + "

    " + + "
      "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + appendTestsList( details.modules ); + toolbarModuleFilter(); + + if ( qunit && config.hidepassed ) { + addClass( qunit.lastChild, "hidepass" ); + } +}); + +QUnit.done(function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
      ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && defined.document && document.title ) { + + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +}); + +function getNameHtml( name, module ) { + var nameHtml = ""; + + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } + + nameHtml += "" + escapeText( name ) + ""; + + return nameHtml; +} + +QUnit.testStart(function( details ) { + var running, testBlock, bad; + + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + + running.innerHTML = ( bad ? + "Rerunning previously failed test:
      " : + "Running:
      " ) + + getNameHtml( details.name, details.module ); + } + +}); + +function stripHtml( string ) { + // strip tags, html entity and whitespaces + return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); +} + +QUnit.log(function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + if ( details.negative ) { + expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); + } else { + expected = escapeText( QUnit.dump.parse( details.expected ) ); + } + + actual = escapeText( QUnit.dump.parse( details.actual ) ); + message += ""; + + if ( actual !== expected ) { + + message += ""; + + // Don't show diff if actual or expected are booleans + if ( !( /^(true|false)$/.test( actual ) ) && + !( /^(true|false)$/.test( expected ) ) ) { + diff = QUnit.diff( expected, actual ); + showDiff = stripHtml( diff ).length !== + stripHtml( expected ).length + + stripHtml( actual ).length; + } + + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; + } + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
      Expected:
      " +
      +			expected +
      +			"
      Result:
      " +
      +				actual + "
      Diff:
      " +
      +					diff + "
      Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

      Hint: Use QUnit.dump.maxDepth to " + + " run with a higher max depth or " + + "Rerun without max depth.

      Source:
      " +
      +				escapeText( details.source ) + "
      "; + + // this occours when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
      Source:
      " +
      +			escapeText( details.source ) + "
      "; + } + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +}); + +QUnit.testDone(function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + } + } + + if ( bad === 0 ) { + addClass( assertList, "qunit-collapsed" ); + } + + // testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + }); + + testItem.className = bad ? "fail" : "pass"; + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } + + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + }); + testItem.appendChild( sourceName ); + } +}); + +if ( defined.document ) { + + // Avoid readyState issue with phantomjs + // Ref: #818 + var notPhantom = ( function( p ) { + return !( p && p.version && p.version.major > 0 ); + } )( window.phantom ); + + if ( notPhantom && document.readyState === "complete" ) { + QUnit.load(); + } else { + addEvent( window, "load", QUnit.load ); + } +} else { + config.pageLoaded = true; + config.autorun = true; +} + +})(); diff --git a/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.client.ts b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.client.ts new file mode 100644 index 00000000..875cced4 --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.client.ts @@ -0,0 +1,70 @@ +module VORLON { + + declare var QUnit; + + export class UnitTestRunnerClient extends ClientPlugin { + //public localStorageList: KeyValue[] = []; + + constructor() { + super("unitTestRunner"); + this._ready = false; + } + + public getID(): string { + return "UNITTEST"; + } + + public startClientSide(): void { + this._loadNewScriptAsync("qunit.js",() => { + var self = this; + this._ready = true; + QUnit.testDone((details) => { + //console.log("QUnit.testDone"); + //console.log(details); + var message: any = {}; + message.commandType = "testDone"; + message.name = details.name; + message.module = details.module; + message.failed = details.failed; + message.passed = details.passed; + message.total = details.total; + message.runtime = details.runtime; + this.sendToDashboard(message); + }); + QUnit.done((details) => { + //console.log("QUnit.done"); + //console.log(details); + var message: any = {}; + message.commandType = "done"; + message.failed = details.failed; + message.passed = details.passed; + message.total = details.total; + message.runtime = details.runtime; + this.sendToDashboard(message); + }); + }); + } + + public refresh(): void { + + } + + public runTest(testContent: any): void { + eval(testContent); + } + + public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { + + } + } + + UnitTestRunnerClient.prototype.ClientCommands = { + runTest: function (data) { + var plugin = this; + plugin.runTest(data); + } + } + + //Register the plugin with vorlon core + Core.RegisterClientPlugin(new UnitTestRunnerClient()); +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.dashboard.ts b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.dashboard.ts new file mode 100644 index 00000000..c3eb086a --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.dashboard.ts @@ -0,0 +1,127 @@ +module VORLON { + export class UnitTestRunnerDashboard extends DashboardPlugin { + constructor() { + super("unitTestRunner", "control.html", "control.css"); + this._ready = true; + } + + public getID(): string { + return "UNITTEST"; + } + + private _btnRunTest: HTMLElement; + private _inputFile: HTMLElement; + private _dropPanel: HTMLElement; + private _txtRunTest: HTMLTextAreaElement; + private _containerList: HTMLElement; + private _containerSummary: HTMLElement; + + public handleFileSelect(files: FileList): void { + this._txtRunTest.innerHTML = ""; + var reader = []; + for (var i = 0, f; f = files[i]; i++) { + reader.push(new FileReader()); + reader[i].onload = (e) => { + this._txtRunTest.innerHTML += e.target.result; + }; + reader[i].readAsText(f); + } + } + public startDashboardSide(div: HTMLDivElement = null): void { + var self = this; + + this._insertHtmlContentAsync(div,(filledDiv) => { + this._btnRunTest = Tools.QuerySelectorById(div, "runTest"); + this._inputFile = Tools.QuerySelectorById(div, "files"); + this._inputFile.onchange = (evt) => { + this.handleFileSelect((evt.target).files); + }; + this._dropPanel = Tools.QuerySelectorById(div, "dropPanel"); + this._dropPanel.ondrop = (evt) => { + evt.stopPropagation(); + evt.preventDefault(); + this.handleFileSelect(evt.dataTransfer.files); + }; + this._dropPanel.ondragover = (evt) => { + evt.stopPropagation(); + evt.preventDefault(); + evt.dataTransfer.dropEffect = 'copy'; + }; + this._dropPanel.ondragenter = (evt) => { + evt.stopPropagation(); + evt.preventDefault(); + self._dropPanel.classList.add("droppable"); + }; + this._dropPanel.ondragleave= (evt) => { + evt.stopPropagation(); + evt.preventDefault(); + self._dropPanel.classList.remove("droppable"); + }; + this._txtRunTest = Tools.QuerySelectorById(div, "txtRunTest"); + this._btnRunTest.addEventListener("run", () => { + this.sendCommandToClient("runTest", this._txtRunTest.value); + }); + + this._containerList = Tools.QuerySelectorById(div, "testResultsList"); + this._containerSummary = Tools.QuerySelectorById(div, "testResultsSummary"); + }) + } + public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + //console.log(receivedObject); + switch (receivedObject.commandType) { + case "testDone": + var tr = document.createElement('tr'); + var tdName = document.createElement('td'); + var tdModule = document.createElement('td'); + var tdPassed = document.createElement('td'); + var tdFailed = document.createElement('td'); + var tdTime = document.createElement('td'); + var tdTotal = document.createElement('td'); + + tdName.innerHTML = receivedObject.name; + tdModule.innerHTML = receivedObject.module; + tdPassed.innerHTML = receivedObject.passed; + tdFailed.innerHTML = receivedObject.failed; + tdTime.innerHTML = receivedObject.runtime; + tdTotal.innerHTML = receivedObject.total; + + tr.appendChild(tdName); + tr.appendChild(tdModule); + tr.appendChild(tdPassed); + tr.appendChild(tdFailed); + tr.appendChild(tdTime); + tr.appendChild(tdTotal); + + this._containerList.appendChild(tr); + break; + case "done": + var tdName = document.createElement('th'); + var tdModule = document.createElement('th'); + var tdPassed = document.createElement('th'); + var tdFailed = document.createElement('th'); + var tdTime = document.createElement('th'); + var tdTotal = document.createElement('th'); + + tdPassed.innerHTML = receivedObject.passed; + tdFailed.innerHTML = receivedObject.failed; + //** Need to fix because receivedObject.runtime return a wrong value + // it's a Qunit problem + tdTime.innerHTML = "";// receivedObject.runtime; + //** + tdTotal.innerHTML = receivedObject.total; + + this._containerSummary.innerHTML = ""; + this._containerSummary.appendChild(tdName); + this._containerSummary.appendChild(tdModule); + this._containerSummary.appendChild(tdPassed); + this._containerSummary.appendChild(tdFailed); + this._containerSummary.appendChild(tdTime); + this._containerSummary.appendChild(tdTotal); + break; + } + } + } + + //Register the plugin with vorlon core + Core.RegisterDashboardPlugin(new UnitTestRunnerDashboard()); +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.interfaces.ts b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.interfaces.ts new file mode 100644 index 00000000..97191ee8 --- /dev/null +++ b/Plugins/Vorlon/plugins/unitTestRunner/vorlon.unitTestRunner.interfaces.ts @@ -0,0 +1,6 @@ +module VORLON { + //export class KeyValue { + // public key: string; + // public value: string; + //} +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/vorlontheme.less b/Plugins/Vorlon/plugins/vorlontheme.less new file mode 100644 index 00000000..7f2c24a5 --- /dev/null +++ b/Plugins/Vorlon/plugins/vorlontheme.less @@ -0,0 +1,3 @@ +@lightblue: #00a5b3; +@lightpurple : #6b2d81; +@darkpurple : #1d0c3d; \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/webstandards/control.html b/Plugins/Vorlon/plugins/webstandards/control.html new file mode 100644 index 00000000..7306644a --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/control.html @@ -0,0 +1,22 @@ +
      + + + +
      + +
      +
      +
      +
      +
      +
      +
      + Analyze your page by clicking on the play button above, and select rules to display details. +
      +
      +
      +
      diff --git a/Plugins/Vorlon/plugins/webstandards/control.less b/Plugins/Vorlon/plugins/webstandards/control.less new file mode 100644 index 00000000..267f581a --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/control.less @@ -0,0 +1,222 @@ +@import "../vorlontheme.less"; + +.plugin-webstandards { + #webstandardspanel { + width: 100%; + height: 100%; + position: relative; + + x-controlbar { + .loadingstate { + font-size: 16pt; + line-height: 26px; + margin-left: 10px; + display: none; + } + } + + #webstandards-results{ + position: absolute; + left: 0; + right : 0; + bottom : 0; + top : 36px; + display: flex; + flex-flow : row nowrap; + + #webstandards-rulespanel{ + width: 300px; + min-width: 300px; + height : 100%; + max-width : 30%; + background-color: #EEE; + overflow-x: hidden; + overflow-y: auto; + + .rule{ + .state{ + display: inline-block; + width: 22px; + height : 22px; + line-height: 18px; + text-align: center; + border-radius: 50%; + border : 2px solid #999; + box-sizing: border-box; + margin-right : 8px; + + &.fa-check{ + color : seagreen; + border-color : seagreen; + } + + &.fa-close{ + color : indianred; + border-color : indianred; + } + } + .title{ + padding : 0.3em 1em; + font-size: 0.9em; + cursor : pointer; + } + + &.collapsible { + &.collapsed{ + >.childs{ + display: none; + } + } + } + + &.level1{ + >.title{ + background-color: #999; + color : white; + text-transform: uppercase; + } + } + + &.level2, &.level3, &.level4{ + &:hover{ + background-color: #AAA; + } + + &:hover.selected, &.selected{ + background-color: #CCC; + } + } + } + } + + #webstandards-ruledetailpanel{ + flex: 1; + + .empty{ + margin : auto; + margin-top : 10%; + width: 80%; + max-width: 400px; + color: #BBB; + text-align: center; + } + + h1.title{ + display: -webkit-flex; + display: flex; + flex-flow: row nowrap; + align-items : center; + .state{ + display: inline-block; + width: 28px; + height : 28px; + line-height: 24px; + text-align: center; + border-radius: 50%; + border : 2px solid #999; + box-sizing: border-box; + margin-right : 8px; + + &.fa-check{ + color : seagreen; + border-color : seagreen; + } + + &.fa-close{ + color : indianred; + border-color : indianred; + } + } + } + + .ruledetailpanel-content { + width: 100%; + height : 100%; + box-sizing: border-box; + padding : 20px; + overflow-x: hidden; + overflow-y: auto; + + >.description{ + margin-bottom: 1em; + } + + strong{ + font-weight: 600; + } + + .item { + >.items{ + margin-top : 10px; + } + } + + .blockitems{ + >.items{ + margin-top : 20px; + >.item{ + border : 1px solid #AAA; + padding : 10px; + margin-bottom: 10px; + background-color: #EEE; + } + } + } + + .itemslist{ + margin-bottom: 0.5em; + >.title{ + background-color: #555; + color: white; + padding : 0.2em 0.3em; + margin-bottom: 0.2em; + } + + + >.items{ + padding-left : 20px; + margin-top : 0; + + >.item{ + >.title{ + font-weight: 500; + + } + + >.content{ + color: #777; + font-size : 0.9em; + } + + >.items{ + padding-left : 20px; + >.item{ + color: #AAA; + font-style: italic; + font-size : 0.9em; + } + } + } + } + } + } + } + } + + &.loading { + x-controlbar { + .loadingstate { + display: block; + } + } + + .contentpanel { + position: absolute; + top: 36px; + bottom: 0; + left: 0; + right: 0; + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/dashboard.ts b/Plugins/Vorlon/plugins/webstandards/dashboard.ts new file mode 100644 index 00000000..7dc4e6bd --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/dashboard.ts @@ -0,0 +1,576 @@ +declare var cssjs: any; + +module VORLON { + var _webstandardsRefreshLoop; + var rulesLabels = { + "webstandards": "Web standards", + "accessibility": "Accessibility", + "performances": "Performances", + "mobileweb": "Mobile web", + } + + export class WebStandardsDashboard extends DashboardPlugin { + constructor() { + super("webstandards", "control.html", "control.css"); + this._id = "WEBSTANDARDS"; + //this.debug = true; + this._ready = true; + } + + private _startCheckButton: HTMLButtonElement; + private _cancelCheckButton: HTMLButtonElement; + private _rootDiv: HTMLElement; + private _currentAnalyze = null; + private _rulesPanel: WebStandardsRulesPanel = null; + private _ruleDetailPanel: WebStandardsRuleDetailPanel = null; + public analyzeCssFallback: boolean = true; + + public startDashboardSide(div: HTMLDivElement = null): void { + var script = document.createElement("SCRIPT"); + script.src = "/javascripts/css.js"; + document.body.appendChild(script); + + this._insertHtmlContentAsync(div, (filledDiv) => { + this._ruleDetailPanel = new WebStandardsRuleDetailPanel(this, filledDiv.querySelector('#webstandards-ruledetailpanel')); + this._rulesPanel = new WebStandardsRulesPanel(this, filledDiv.querySelector('#webstandards-rulespanel'), this._ruleDetailPanel); + + this._startCheckButton = filledDiv.querySelector('#startCheck'); + this._cancelCheckButton = filledDiv.querySelector('#cancelCheck'); + this._rootDiv = filledDiv; + + this._startCheckButton.addEventListener("click", (evt) => { + this._startCheckButton.disabled = true; + this._cancelCheckButton.disabled = false; + this._currentAnalyze = { + processing: true, + id: VORLON.Tools.CreateGUID() + }; + this._rootDiv.classList.add("loading"); + this._rulesPanel.clear("analyze in progress..."); + this.sendCommandToClient('startNewAnalyze', { id: this._currentAnalyze.id, analyzeCssFallback: this.analyzeCssFallback }); + }); + + this._cancelCheckButton.addEventListener("click", (evt) => { + this._startCheckButton.disabled = false; + this._cancelCheckButton.disabled = true; + this._currentAnalyze.processing = false; + this._currentAnalyze.canceled = true; + this._rootDiv.classList.remove("loading"); + this._rulesPanel.clear("retry by clicking on start button"); + }); + + clearInterval(_webstandardsRefreshLoop); + _webstandardsRefreshLoop = setInterval(() => { + this.checkLoadingState(); + }, 3000); + }); + } + + checkLoadingState() { + if (this._currentAnalyze && this._currentAnalyze.pendingLoad <= 0) { + this.trace("resource load completed"); + this._currentAnalyze.processing = false; + } + + if (!this._currentAnalyze || this._currentAnalyze.ended || this._currentAnalyze.canceled) { + return; + } + + if (this._currentAnalyze.processing) { + + } else { + this.endAnalyze(this._currentAnalyze); + this._rootDiv.classList.remove("loading"); + this._currentAnalyze.ended = true; + this._ruleDetailPanel.setMessage("click on a rule in the result panel to show details"); + this._rulesPanel.setRules(this._currentAnalyze); + this._startCheckButton.disabled = false; + this._cancelCheckButton.disabled = true; + } + } + + receiveHtmlContent(data: { id: string, html: string, doctype: any, url: any, browserDetection: any, stylesheetErrors: any }) { + if (!this._currentAnalyze) { + this._currentAnalyze = { processing: true }; + } + + if (!this._currentAnalyze.files) { + this._currentAnalyze.files = {}; + } + + this._currentAnalyze.lastActivity = new Date(); + + this._currentAnalyze.html = data.html; + this._currentAnalyze.doctype = data.doctype; + this._currentAnalyze.location = data.url; + this._currentAnalyze.browserDetection = data.browserDetection; + + var fragment: HTMLDocument = document.implementation.createHTMLDocument("analyze"); + fragment.documentElement.innerHTML = data.html; + this._currentAnalyze.pendingLoad = 0; + + this._currentAnalyze.files.scripts = {}; + var scripts = fragment.querySelectorAll("script"); + var nbScripts = scripts.length; + for (var i = 0; i < scripts.length; i++) { + var s = scripts[i]; + var src = s.attributes.getNamedItem("src"); + if (src && src.value) { + var isVorlon = src.value.indexOf('vorlon.js') > 0 || src.value.indexOf('vorlon.min.js') > 0 || src.value.indexOf('vorlon.max.js') > 0; + if (!isVorlon) { + this._currentAnalyze.files.scripts[src.value] = { loaded: false, content: null }; + this.sendCommandToClient('fetchDocument', { url: src.value, id: this._currentAnalyze.id, type: "script" }); + this._currentAnalyze.pendingLoad++; + this.trace("request file " + src.value + " " + this._currentAnalyze.pendingLoad); + } + } + } + + this._currentAnalyze.files.stylesheets = {}; + var stylesheets = fragment.querySelectorAll("link[rel=stylesheet]"); + var nbStylesheets = stylesheets.length; + for (var i = 0; i < stylesheets.length; i++) { + var s = stylesheets[i]; + var href = s.attributes.getNamedItem("href"); + if (href) { + this._currentAnalyze.files.stylesheets[href.value] = { loaded: false, content: null }; + this.sendCommandToClient('fetchDocument', { url: href.value, id: this._currentAnalyze.id, type: "stylesheet", analyzeCssFallback: this.analyzeCssFallback }); + this._currentAnalyze.pendingLoad++; + this.trace("request file " + href.value + " " + this._currentAnalyze.pendingLoad); + } + } + if (!this._currentAnalyze.fallBackErrorList) + this._currentAnalyze.fallBackErrorList = []; + + if (data.stylesheetErrors) + this._currentAnalyze.fallBackErrorList.push(data.stylesheetErrors); + this._currentAnalyze.results = {}; + this.prepareAnalyze(this._currentAnalyze) + this.analyzeDOM(fragment, data.html, this._currentAnalyze); + + } + + receiveDocumentContent(data: { id: string, url: string, content: string, error?: string, encoding?: string, contentLength?: string, status: number, stylesheetErrors: any }) { + var item = null; + var itemContainer = null; + + this._currentAnalyze.lastActivity = new Date(); + + + for (var n in this._currentAnalyze.files) { + var container = this._currentAnalyze.files[n]; + if (container[data.url]) { + item = container[data.url]; + itemContainer = n; + } + } + + if (item) { + this._currentAnalyze.pendingLoad--; + item.loaded = true; + item.encoding = data.encoding; + item.content = data.content; + item.contentLength = data.contentLength; + item.error = data.error; + item.status = data.status; + + if (data.error) { + item.loaded = false; + } + + + } + + if (itemContainer === "stylesheets") { + if (this.analyzeCssFallback) { + if (!this._currentAnalyze.fallBackErrorList) + this._currentAnalyze.fallBackErrorList = []; + if (data.stylesheetErrors) + this._currentAnalyze.fallBackErrorList.push(data.stylesheetErrors); + + } + else { + this._currentAnalyze.fallBackErrorList = null; + } + this.analyzeCssDocument(data.url, data.content, this._currentAnalyze); + } + + if (itemContainer === "scripts") { + this.analyzeJsDocument(data.url, data.content, this._currentAnalyze); + } + this.trace("receive content " + data.url + " " + this._currentAnalyze.pendingLoad); + } + + analyzeDOM(document: HTMLDocument, htmlContent: string, analyze) { + var generalRules = []; + var commonRules = []; + + var rules = { + domRulesIndex: {}, + domRulesForAllNodes: [] + }; + + //we index rules based on target node types + for (var n in VORLON.WebStandards.Rules.DOM) { + var rule = VORLON.WebStandards.Rules.DOM[n]; + if (rule) { + var rulecheck = this.initialiseRuleSummary(rule, analyze); + if (rule.prepare) { + rule.prepare(rulecheck, analyze, htmlContent); + } + + if (rule.generalRule) { + generalRules.push(rule); + } else { + commonRules.push(rule); + if (rule.nodeTypes.length) { + rule.nodeTypes.forEach(function (n) { + n = n.toUpperCase(); + if (!rules.domRulesIndex[n]) + rules.domRulesIndex[n] = []; + + rules.domRulesIndex[n].push(rule); + }); + } else { + rules.domRulesForAllNodes.push(rule); + } + } + } + } + + this.analyzeDOMNode(document, rules, analyze, htmlContent); + + generalRules.forEach((rule) => { + this.applyDOMNodeRule(document, rule, analyze, htmlContent); + }); + } + + analyzeDOMNode(node: Node, rules: any, analyze, htmlContent: string) { + if (node.nodeName === "STYLE") { + this.analyzeCssDocument("inline", (node).innerHTML, analyze); + } + + if (node.nodeName === "SCRIPT") { + var domnode = node; + var scriptType = domnode.getAttribute("type"); + var hasContent = domnode.innerHTML.trim().length > 0; + + if (!scriptType || scriptType == "text/javascript" && hasContent) { + this.analyzeJsDocument("inline", domnode.innerHTML, analyze); + } + } + + var specificRules = rules.domRulesIndex[node.nodeName.toUpperCase()]; + if (specificRules && specificRules.length) { + specificRules.forEach((r) => { + this.applyDOMNodeRule(node, r, analyze, htmlContent); + }); + } + + if (rules.domRulesForAllNodes && rules.domRulesForAllNodes.length) { + rules.domRulesForAllNodes.forEach((r) => { + this.applyDOMNodeRule(node, r, analyze, htmlContent); + }); + } + + for (var i = 0, l = node.childNodes.length; i < l; i++) { + this.analyzeDOMNode(node.childNodes[i], rules, analyze, htmlContent); + } + } + + initialiseRuleSummary(rule, analyze) { + var tokens = rule.id.split('.'); + var current = analyze.results; + var id = ""; + current.rules = current.rules || {}; + tokens.forEach(function (t) { + id = (id.length > 0) ? "." + t : t; + + if (!current.rules) { + current.rules = {}; + } + + if (!current.rules[t]) + current.rules[t] = { id: id }; + + current = current.rules[t]; + }); + + if (current.failed === undefined) { + current.failed = false; + current.title = rule.title; + current.description = rule.description; + } + + return current; + } + + applyDOMNodeRule(node: Node, rule: IDOMRule, analyze, htmlContent: string) { + var current = this.initialiseRuleSummary(rule, analyze); + rule.check(node, current, analyze, htmlContent); + } + + analyzeCssDocument(url, content, analyze) { + var parser = new cssjs(); + var parsed = parser.parseCSS(content); + this.trace("processing css " + url); + + //we index rules based on target node types + for (var n in VORLON.WebStandards.Rules.CSS) { + var rule = VORLON.WebStandards.Rules.CSS[n]; + if (rule) { + var current = this.initialiseRuleSummary(rule, analyze); + rule.check(url, parsed, current, analyze); + } + } + } + + analyzeFiles(analyze) { + for (var n in VORLON.WebStandards.Rules.Files) { + var rule = VORLON.WebStandards.Rules.Files[n]; + if (rule) { + var current = this.initialiseRuleSummary(rule, analyze); + rule.check(current, analyze); + } + } + } + + analyzeJsDocument(url, content, analyze) { + this.trace("processing script " + url); + for (var n in VORLON.WebStandards.Rules.JavaScript) { + var rule = VORLON.WebStandards.Rules.JavaScript[n]; + if (rule) { + var current = this.initialiseRuleSummary(rule, analyze); + rule.check(url, content, current, analyze); + } + } + } + + prepareAnalyze(analyze) { + for (var n in VORLON.WebStandards.Rules.CSS) { + var cssrule = VORLON.WebStandards.Rules.CSS[n]; + if (cssrule) { + var current = this.initialiseRuleSummary(cssrule, analyze); + if (cssrule.prepare) + cssrule.prepare(current, analyze); + } + } + + for (var n in VORLON.WebStandards.Rules.JavaScript) { + var scriptrule = VORLON.WebStandards.Rules.JavaScript[n]; + if (scriptrule) { + var current = this.initialiseRuleSummary(scriptrule, analyze); + if (scriptrule.prepare) + scriptrule.prepare(current, analyze); + } + } + } + + endAnalyze(analyze) { + for (var n in VORLON.WebStandards.Rules.DOM) { + var rule = VORLON.WebStandards.Rules.DOM[n]; + if (rule && !rule.generalRule && rule.endcheck) { + var current = this.initialiseRuleSummary(rule, analyze); + rule.endcheck(current, analyze, this._currentAnalyze.html); + } + } + + for (var n in VORLON.WebStandards.Rules.CSS) { + var cssrule = VORLON.WebStandards.Rules.CSS[n]; + if (cssrule) { + var current = this.initialiseRuleSummary(cssrule, analyze); + if (cssrule.endcheck) + cssrule.endcheck(current, analyze); + } + } + + for (var n in VORLON.WebStandards.Rules.JavaScript) { + var scriptrule = VORLON.WebStandards.Rules.JavaScript[n]; + if (scriptrule) { + var current = this.initialiseRuleSummary(scriptrule, analyze); + if (scriptrule.endcheck) + scriptrule.endcheck(current, analyze); + } + } + + this.analyzeFiles(this._currentAnalyze); + } + } + + WebStandardsDashboard.prototype.DashboardCommands = { + htmlContent: function (data: any) { + var plugin = this; + plugin.receiveHtmlContent(data); + }, + + documentContent: function (data: any) { + var plugin = this; + plugin.receiveDocumentContent(data); + } + }; + + //Register the plugin with vorlon core + Core.RegisterDashboardPlugin(new WebStandardsDashboard()); + + class WebStandardsRulesPanel { + element: HTMLElement; + detailpanel: WebStandardsRuleDetailPanel; + selectedRuleElt: HTMLElement; + plugin: WebStandardsDashboard; + + constructor(plugin: WebStandardsDashboard, element: Element, detailpanel: WebStandardsRuleDetailPanel) { + this.plugin = plugin; + this.element = element; + this.element.style.display = "none"; + this.detailpanel = detailpanel; + } + + clear(msg) { + this.element.style.display = "none"; + this.detailpanel.clear(msg); + } + + setRules(analyze) { + this.plugin.trace("RENDER ANALYZE"); + this.plugin.trace(analyze); + this.element.style.display = ""; + this.element.innerHTML = ""; + this.renderRules(analyze.results.rules, this.element, 1); + } + + renderRules(rules, parent: HTMLElement, level: number) { + var items = []; + for (var n in rules) { + var rule = rules[n]; + //if (rule.rules || rule.failed){ + if (!rule.title) { + rule.title = rulesLabels[rule.id]; + } + if (!rule.title) { + rule.title = n; + } + items.push(rule); + //} + } + + items.sort(function (a, b) { + return a.title.localeCompare(b.title); + }) + + items.forEach((rule) => { + this.renderRule(rule, parent, level); + }) + } + + renderRule(rule, parent: HTMLElement, level: number) { + var ruleitem = new FluentDOM('DIV', 'rule level' + level, parent); + ruleitem.append('DIV', 'title', (title) => { + if (rule.failed !== undefined) { + title.createChild("SPAN", "state fa " + (rule.failed ? "fa-close" : "fa-check")); + } + title.createChild("SPAN").text(rule.title); + if (rule.rules) { + title.click(() => { + ruleitem.toggleClass("collapsed"); + ruleitem.toggleClass("expanded"); + }); + } + else { + title.click(() => { + if (this.selectedRuleElt) { + this.selectedRuleElt.classList.remove("selected"); + } + ruleitem.element.classList.add("selected"); + this.selectedRuleElt = ruleitem.element; + this.detailpanel.setRule(rule); + }); + } + }); + + if (rule.rules) { + ruleitem.addClass("collapsible"); + ruleitem.addClass("collapsed"); + ruleitem.append('DIV', 'childs', (childs) => { + this.renderRules(rule.rules, childs.element, level + 1); + }); + } + } + } + + class WebStandardsRuleDetailPanel { + element: HTMLElement; + plugin: WebStandardsDashboard; + + constructor(plugin: WebStandardsDashboard, element: Element) { + this.element = element; + this.plugin = plugin; + } + + setRule(rule) { + this.element.innerHTML = ""; + + var fluent = FluentDOM.forElement(this.element); + fluent.append("DIV", "ruledetailpanel-content", (content) => { + content.append("DIV", "item", (item) => { + if (rule.type) + item.addClass(rule.type); + + item.append("H1", "title", (title) => { + title.createChild("SPAN", "state fa " + (rule.failed ? "fa-close" : "fa-check")); + title.createChild("SPAN", "text").html(rule.title); + }); + + if (rule.description) { + item.append("DIV", "description", (desc) => { + desc.html(rule.description); + }); + } + + if (rule.items && rule.items.length) { + item.append("DIV", "items", (itemselt) => { + rule.items.forEach((item) => { + this.renderItem(item, itemselt); + }); + }); + } + }); + }); + } + + renderItem(item, parent: FluentDOM) { + parent.append("DIV", "item", (itemelt) => { + if (item.type) { + itemelt.addClass(item.type); + } + if (item.title && item.alert) { + itemelt.createChild("SPAN", "state fa " + (item.failed ? "fa-close" : "fa-check")).html(item.title); + } + else if (item.title) { + itemelt.createChild("DIV", "title").html(item.title); + } + if (item.message) { + itemelt.createChild("DIV", "message").html(item.message); + } + if (item.content) { + itemelt.createChild("DIV", "content").html(item.content); + } + + if (item.items && item.items.length) { + itemelt.append("DIV", "items", (itemselt) => { + item.items.forEach((item) => { + this.renderItem(item, itemselt); + }); + }); + } + }); + } + + clear(msg) { + this.setMessage(msg); + } + + setMessage(msg) { + this.element.innerHTML = '
      ' + msg + '
      '; + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/accessibility.avoidmetarefresh.ts b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.avoidmetarefresh.ts new file mode 100644 index 00000000..1ea4a7f3 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.avoidmetarefresh.ts @@ -0,0 +1,17 @@ +module VORLON.WebStandards.Rules.DOM { + + export var avoidMetaRefresh = { + id: "accessibility.avoid-meta-refresh", + title: "avoid meta refresh", + description : "Reading a webpage with your fingers is a lot harder and slower. Avoid auto refreshing your page to allow blind people to read your content.", + nodeTypes: ["meta"], + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + var equiv = node.getAttribute("http-equiv"); + if (equiv && equiv.toLowerCase() == "refresh"){ + rulecheck.failed = true; + } + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/accessibility.imagesalt.ts b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.imagesalt.ts new file mode 100644 index 00000000..f1fe6290 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.imagesalt.ts @@ -0,0 +1,32 @@ +module VORLON.WebStandards.Rules.DOM { + + export var imagesShouldHaveAlt = { + id: "accessibility.images-should-have-alt", + title: "images should have alt attribute", + description : "Add alt attribute on images to enable blind people to get meaning for images.", + nodeTypes: ["IMG", "AREA"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + rulecheck.nbfailed = 0; + rulecheck.nbcheck = 0; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + //console.log("check alt images "); + var altattr = node.attributes.getNamedItem("alt"); + + rulecheck.nbcheck++; + if (!altattr || !altattr.value) { + rulecheck.nbfailed++; + rulecheck.failed = true; + rulecheck.items.push({ + content : VORLON.Tools.htmlToString(node.outerHTML) + }) + } else { + } + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/accessibility.labelmusthavefor.ts b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.labelmusthavefor.ts new file mode 100644 index 00000000..294e0a13 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.labelmusthavefor.ts @@ -0,0 +1,29 @@ +module VORLON.WebStandards.Rules.DOM { + + export var labelMustHaveFor = { + id: "accessibility.label-must-have-for", + title: "label tag must have a \"for\" attribute", + description : "label tag is intended to be used with input field. Label tags help people with disabilities to identify input fields.", + nodeTypes: ["label"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + rulecheck.nbfailed = 0; + rulecheck.nbcheck = 0; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + var forAttr = node.getAttribute("for"); + rulecheck.nbcheck++; + if (!forAttr) { + rulecheck.nbfailed++; + rulecheck.failed = true; + rulecheck.items.push({ + content : VORLON.Tools.htmlToString(node.outerHTML) + }) + } + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/accessibility.usearia.ts b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.usearia.ts new file mode 100644 index 00000000..ff7708dd --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.usearia.ts @@ -0,0 +1,67 @@ +module VORLON.WebStandards.Rules.DOM { + var ariaAttributes = [ + 'aria-atomic', + 'aria-busy', + 'aria-controls', + 'aria-describedby', + 'aria-disabled', + 'aria-dropeffect', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-label', + 'aria-labelledby', + 'aria-live', + 'aria-owns', + 'aria-relevant', + 'aria-autocomplete', + 'aria-checked', + 'aria-expanded', + 'aria-level', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-pressed', + 'aria-readonly', + 'aria-required', + 'aria-selected', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + 'aria-activedescendant', + 'aria-posinset', + 'aria-setsize']; + + export var useAriaAttributes = { + id: "accessibility.use-aria", + title: "use aria attributes", + description : "Use accessibility attributes like aria-label to provide meaningful information for people with visual disabilities.", + nodeTypes: [], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.ariaCount = 0; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlstring : string) { + if (!node.getAttribute) //not an HTML element + return; + + ariaAttributes.forEach(function(a){ + if(node.getAttribute(a)){ + rulecheck.ariaCount++; + } + }) + }, + + endcheck : function(rulecheck, analyzeSummary, htmlstring : string){ + if (rulecheck.ariaCount==0){ + rulecheck.failed = true; + } + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/accessibility.uselangattribute.ts b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.uselangattribute.ts new file mode 100644 index 00000000..d9d2deaf --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/accessibility.uselangattribute.ts @@ -0,0 +1,20 @@ +module VORLON.WebStandards.Rules.DOM { + + export var useLangAttribute = { + id: "accessibility.use-lang-attribute", + title: "add lang attr on HTML tag", + description : "lang attribute on HTML tag helps people finding out if they will understand content without having to get deep into it", + nodeTypes: ["html"], + + check: function(node: Element, rulecheck: any, analyzeSummary: any, htmlString: string) { + var min = htmlString.toLowerCase(); + var start = min.indexOf("", start); + var htmltag = min.substr(start, end-start); + if (!(htmltag.indexOf(' lang=') >= 0)){ + rulecheck.failed = true; + } + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.deviceicons.ts b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.deviceicons.ts new file mode 100644 index 00000000..77956a2e --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.deviceicons.ts @@ -0,0 +1,54 @@ +module VORLON.WebStandards.Rules.DOM { + + export var deviceIcons = { + id: "mobileweb.deviceIcons", + title: "define platform icons", + description: "Platform icons helps user pinning your website with an icon that fits well on mobile device home.", + nodeTypes: ["meta", "link"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + rulecheck.hasWindowsIcons = false; + rulecheck.hasWindowsNotification = false; + rulecheck.hasIOSIcons = false; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + if (node.nodeName == "LINK") { + var rel = node.getAttribute("rel"); + if (rel && rel == "apple-touch-icon-precomposed") { + rulecheck.hasIOSIcons = true; + } + } else if (node.nodeName == "META") { + var name = node.getAttribute("name"); + if (name) { + if (name.toLowerCase() == "msapplication-notification") { + rulecheck.hasWindowsNotification = true; + } else if (name.toLowerCase().indexOf("msapplication-") == 0) { + rulecheck.hasWindowsIcons = true; + } + } + } + }, + + endcheck: function(rulecheck, analyzeSummary, htmlstring: string) { + if (!rulecheck.hasIOSIcons) { + rulecheck.failed = true; + rulecheck.items.push({ + title: VORLON.Tools.htmlToString('add Apple - iOS icons by adding link tags like ') + }); + } + + if (!rulecheck.hasWindowsIcons) { + rulecheck.failed = true; + //https://msdn.microsoft.com/en-us/library/dn255024(v=vs.85).aspx + rulecheck.items.push({ + title: VORLON.Tools.htmlToString('add Microsoft - Windows tiles by adding meta tags like ') + }); + } + + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.mediaqueries.ts b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.mediaqueries.ts new file mode 100644 index 00000000..b55955aa --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.mediaqueries.ts @@ -0,0 +1,91 @@ +module VORLON.WebStandards.Rules.CSS { + export var mobileMediaqueries = { + id: "mobileweb.usemediaqueries", + title: "use responsive approaches", + description: "Even if your website target only certain devices, you may have users with unexpected devices or screen ratio.", + + prepare: function(rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + if (rulecheck.cssnbqueries == undefined) rulecheck.cssnbqueries = 0; + }, + + check: function (url, ast, rulecheck: any, analyzeSummary: any) { + //console.log("check css prefixes"); + + this.checkNodes(url, rulecheck, ast); + }, + + checkNodes: function (url, rulecheck, ast) { + if (!ast) + return; + + ast.forEach((node, i) => { + var nodeitem = null; + + //scan content for media queries + if (node.type === "media") { + var media = node.selector; + if (media){ + media = media.toLowerCase(); + if (media.indexOf("width") >= 0 || media.indexOf("height") >= 0){ + rulecheck.cssnbqueries++; + } + } + } + }); + }, + + endcheck: function(rulecheck: any, analyzeSummary: any){ + } + } +} + +module VORLON.WebStandards.Rules.DOM { + export var mobileMediaqueries = { + id: "mobileweb.usemediaqueries", + title: "use responsive approaches", + description: "Even if your website target only certain devices, you may have users with unexpected devices or screen ratio.", + nodeTypes: ["link"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + if (rulecheck.domnbqueries == undefined) rulecheck.domnbqueries = 0; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlstring : string) { + if (!node.getAttribute) //not an HTML element + return; + + var rel = node.getAttribute("rel"); + if (rel && rel.toLocaleLowerCase() == "stylesheet"){ + var media = node.getAttribute("media"); + if (media){ + media = media.toLowerCase(); + if (media.indexOf("width") >= 0 || media.indexOf("height") >= 0){ + rulecheck.domnbqueries++; + } + } + } + }, + + endcheck : function(rulecheck, analyzeSummary, htmlstring : string){ + //console.log("media queries css:" + rulecheck.cssnbqueries + ", dom:" + rulecheck.domnbqueries); + if (rulecheck.cssnbqueries==0 && rulecheck.domnbqueries==0){ + if (rulecheck.cssnbqueries==0){ + rulecheck.failed = true; + rulecheck.items.push({ + title : 'your css (either files or inline) does not use any media queries' + }); + } + + if (rulecheck.domnbqueries==0){ + rulecheck.failed = true; + rulecheck.items.push({ + title : 'your link tags does not use any media queries' + }); + } + } + } + } +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.viewport.ts b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.viewport.ts new file mode 100644 index 00000000..e25cb750 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/mobileweb.viewport.ts @@ -0,0 +1,22 @@ +module VORLON.WebStandards.Rules.DOM { + + export var useViewport = { + id: "mobileweb.use-viewport", + title: "use meta viewport", + description: "Use meta viewport tag to choose how your website will get scaled on smaller devices like phones. Define at least <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">", + nodeTypes: ["meta"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.failed = true; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + var viewportattr = node.getAttribute("name"); + if (viewportattr && viewportattr.toLowerCase() == "viewport") { + rulecheck.failed = false; + } + + } + } + +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/performances.bundle.ts b/Plugins/Vorlon/plugins/webstandards/rules/performances.bundle.ts new file mode 100644 index 00000000..42b5c9e2 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/performances.bundle.ts @@ -0,0 +1,42 @@ +module VORLON.WebStandards.Rules.Files { + var cssFilesLimit = 5; + var scriptsFilesLimit = 5; + + export var filesBundle = { + id: "performances.bundles", + title: "try bundling your files", + description: "Multiple http requests makes your site slower, especially on mobile devices", + + check: function(rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + var countStylesheets = 0; + for (var n in analyzeSummary.files.stylesheets){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection) + countStylesheets++; + } + + if (countStylesheets > cssFilesLimit){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "You have more than " + cssFilesLimit + " stylesheets in your page, consider bundling your stylesheets." + }); + } + + var countScripts = 0; + for (var n in analyzeSummary.files.scripts){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection) + countScripts++; + } + + if (countScripts > scriptsFilesLimit){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "You have more than " + scriptsFilesLimit + " scripts files in your page, consider bundling your scripts." + }); + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/performances.contentencoding.ts b/Plugins/Vorlon/plugins/webstandards/rules/performances.contentencoding.ts new file mode 100644 index 00000000..cc13f347 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/performances.contentencoding.ts @@ -0,0 +1,31 @@ +module VORLON.WebStandards.Rules.Files { + export var contentEncoding = { + id: "performances.contentencoding", + title: "encode static content", + description: "content encoding like gzip or deflate helps reducing the network bandwith required to display your website, it is especially important for mobile devices. Use content encoding for static files like CSS and JavaScript files.", + + check: function(rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + for (var n in analyzeSummary.files.stylesheets){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection && analyzeSummary.files.stylesheets[n].encoding && analyzeSummary.files.stylesheets[n].encoding == "none"){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "use content encoding for " + n + }); + } + } + + for (var n in analyzeSummary.files.scripts){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection && analyzeSummary.files.scripts[n].encoding && analyzeSummary.files.scripts[n].encoding == "none"){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "use content encoding for " + n + }); + } + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/performances.minification.ts b/Plugins/Vorlon/plugins/webstandards/rules/performances.minification.ts new file mode 100644 index 00000000..4c63be0b --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/performances.minification.ts @@ -0,0 +1,52 @@ +module VORLON.WebStandards.Rules.Files { + export var filesMinification = { + id: "performances.minification", + title: "minify static files", + description: "minification helps reducing the network bandwith required to display your website, it is especially important for mobile devices. Minify static files like CSS and JavaScript files.", + + check: function(rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + for (var n in analyzeSummary.files.stylesheets){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection){ + var charPerLines = this.getAverageCharacterPerLine(analyzeSummary.files.stylesheets[n].content); + if (charPerLines < 50){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "minify " + n + }); + } + } + } + + for (var n in analyzeSummary.files.scripts){ + var isVorlonInjection = n.toLowerCase().indexOf("vorlon/plugins") >= 0; + if (!isVorlonInjection){ + var charPerLines = this.getAverageCharacterPerLine(analyzeSummary.files.scripts[n].content); + if (charPerLines < 50){ + rulecheck.failed = true; + rulecheck.items.push({ + title : "minify " + n + }); + } + } + } + }, + + getAverageCharacterPerLine : function(content : string){ + if (!content) + return 1000; + + var lines = content.split('\n'); + if (lines.length == 0) + return 1000; + + var total = 0; + lines.forEach(function(l){ + total+= l.length; + }); + return total / lines.length + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.activex.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.activex.ts new file mode 100644 index 00000000..f645bf24 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.activex.ts @@ -0,0 +1,43 @@ +module VORLON.WebStandards.Rules.DOM { + export var dontUsePlugins = { + id: "webstandards.dont-use-plugins", + title: "object and embed", + description : "With HTML5 embed or object tags can often be replaced with HTML5 features.", + nodeTypes: ["EMBED", "OBJECT"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + //console.log("check for plugins"); + + var source :string = null, data:string = null, type:string = null; + + var source = node.getAttribute("src"); + if (source) source = source.toLowerCase(); else source = ""; + + var data = node.getAttribute("data"); + if (data) data = data.toLowerCase(); else data = ""; + + var type = node.getAttribute("type"); + if (type) type = type.toLowerCase(); else type = ""; + + if (source.indexOf(".swf") > 0 || data.indexOf("swf") > 0){ + rulecheck.failed = true; + rulecheck.items.push({ message: "consider using HTML5 instead of Flash", content : VORLON.Tools.htmlToString((node).outerHTML) }) + } + else if (type.indexOf("silverlight") > 0){ + rulecheck.failed = true; + rulecheck.items.push({ message: "consider using HTML5 instead of Silverlight", content : VORLON.Tools.htmlToString((node).outerHTML) }) + } else if (source.indexOf(".svg") > 0 || data.indexOf("svg") > 0) { + rulecheck.failed = true; + rulecheck.items.push({ message: "dont't use SVG with " + node.nodeType, content : VORLON.Tools.htmlToString((node).outerHTML) }) + } else { + rulecheck.failed = true; + rulecheck.items.push({ message: "use HTML5 instead of embed or object elements", content : VORLON.Tools.htmlToString((node).outerHTML) }) + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserdetection.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserdetection.ts new file mode 100644 index 00000000..6c01edd3 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserdetection.ts @@ -0,0 +1,70 @@ +module VORLON.WebStandards.Rules.DOM { + export var browserdetection = { + id: "webstandards.avoid-browser-detection", + exceptions : [ + "ajax.googleapis.com", + "ajax.aspnetcdn.com", + "ajax.microsoft.com", + "jquery", + "mootools", + "prototype", + "protoaculous", + "google-analytics.com", + "partner.googleadservices.com" + ], + title: "avoid browser detection", + description: "Nowadays, browser have very similar user agent, and browser feature moves very fast. Browser detection leads to britle code. Consider using feature detection instead.", + nodeTypes: ["#comment"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + }, + + check: function(node: Node, rulecheck: any, analyzeSummary: any, htmlString: string) { + + }, + + isException : function(file){ + if (!file) + return false; + + return this.exceptions.some((e) =>{ + return file.indexOf(e) >= 0; + }) + }, + + inspectAccesses : function(root, property, rulecheck){ + var items = root[property]; + + if (items && items.length){ + items.forEach((item) => { + var isException = this.isException(item.file); + if (!isException){ + rulecheck.failed = true; + var stacked = item.stack.split("\n"); + var check = { + title : "access to window.navigator." + property, + content : stacked.slice(3, stacked.length).join("
      ") + }; + if (item.file){ + check.title = check.title + " from " + item.file; + } + rulecheck.items.push(check); + }else{ + + } + }); + } + }, + + endcheck: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + var detection = analyzeSummary.browserDetection; + this.inspectAccesses(detection, "userAgent", rulecheck); + this.inspectAccesses(detection, "appVersion", rulecheck); + this.inspectAccesses(detection, "appName", rulecheck); + this.inspectAccesses(detection, "product", rulecheck); + this.inspectAccesses(detection, "vendor", rulecheck); + }, + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserinterop.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserinterop.ts new file mode 100644 index 00000000..98ee5ae3 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.browserinterop.ts @@ -0,0 +1,104 @@ +module VORLON.WebStandards.Rules.DOM { + export var browserinterop = { + id: "webstandards.avoid-browser-specific-content", + title: "avoid browser specific content", + description: "Avoid serving content based on user-agent. Browsers evolve fast and user-agent based content may frustrate your users by not getting the best content for their preferred browser.", + nodeTypes: ["#comment"], + + userAgents: { + "Microsoft Edge": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240', + "Microsoft IE11": 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko', + "Google Chrome": 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36', + "Firefox": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0' + }, + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + analyzeSummary.files.browserInterop = {}; + + for (var n in this.userAgents) { + this.fetchHTMLDocument(n, this.userAgents[n], analyzeSummary); + } + }, + + check: function(node: Node, rulecheck: any, analyzeSummary: any, htmlString: string) { + + }, + + endcheck: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + var detection = analyzeSummary.files.browserInterop; + var comparisons = {}; + + for (var n in detection) { + for (var b in detection) { + if (b != n && !comparisons[n + b]) { + //console.log("comparing content from " + n + " and " + b); + comparisons[b + n] = true; + if (detection[b].loaded && detection[n].loaded && detection[b].content != detection[n].content) { + rulecheck.failed = true; + rulecheck.items.push({ + title: n + " and " + b + " received different content" + }) + } + } + } + } + }, + + fetchHTMLDocument: function(browser, userAgent, analyzeSummary) { + var xhr = null; + var timeoutRef = null; + var completed = false; + var serverurl = (VORLON.Core._messenger)._serverUrl; + if (serverurl[serverurl.length - 1] !== '/') + serverurl = serverurl + "/"; + var documentUrl = serverurl + "httpproxy/fetch?fetchurl=" + encodeURIComponent(analyzeSummary.location.href) + "&fetchuseragent=" + encodeURIComponent(userAgent); + console.log("getting HTML reference for " + browser + " " + documentUrl); + + try { + xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState == 4) { + completed = true; + clearTimeout(timeoutRef); + analyzeSummary.pendingLoad--; + //console.log("received content for " + browser + "(" + xhr.status + ") " + analyzeSummary.pendingLoad); + if (xhr.status == 200) { + analyzeSummary.files.browserInterop[browser] = { + loaded: true, url: analyzeSummary.location.href, userAgent: userAgent, status: xhr.status, content: xhr.responseText + } + } + else { + analyzeSummary.files.browserInterop[browser] = { + loaded: false, url: analyzeSummary.location.href, userAgent: userAgent, status: xhr.status, content: null, error: xhr.statusText + } + } + } + }; + + xhr.open("GET", documentUrl, true); + analyzeSummary.pendingLoad++; + xhr.send(null); + //console.log("request file " + browser + " " + analyzeSummary.pendingLoad); + timeoutRef = setTimeout(() => { + if (!completed) { + completed = true; + analyzeSummary.pendingLoad--; + console.warn("fetch timeout for " + browser); + xhr.abort(); + analyzeSummary.files.browserInterop[browser] = { + loaded: false, url: analyzeSummary.location.href, userAgent: userAgent, status: xhr.status, content: null, error: "timeout" + } + } + }, 20 * 1000); + + } catch (e) { + analyzeSummary.pendingLoad--; + console.error(e); + console.warn("received error for " + browser + "(" + xhr.status + ") " + analyzeSummary.pendingLoad); + analyzeSummary.files.browserInterop[browser] = { loaded: false, url: analyzeSummary.location.href, status: 0, content: null, error: e.message }; + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.conditionalcomments.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.conditionalcomments.ts new file mode 100644 index 00000000..7d7637b3 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.conditionalcomments.ts @@ -0,0 +1,33 @@ +module VORLON.WebStandards.Rules.DOM { + export var dontUseBrowserConditionalComment = { + id: "webstandards.avoid-browser-specific-css", + title: "avoid conditional comments", + description: "Conditional comments are not the best way to adapt your website to target browser, and support is dropped for IE > 9.", + nodeTypes: ["#comment"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + }, + + check: function(node: Node, rulecheck: any, analyzeSummary: any, htmlString: string) { + //console.log("checking comment " + node.nodeValue); + var commentContent = node.nodeValue.toLowerCase(); + + var hasConditionalComment = + commentContent.indexOf("[if ie ") >= 0 || + commentContent.indexOf("[if !ie]") >= 0 || + commentContent.indexOf("[if gt ie ") >= 0 || + commentContent.indexOf("[if gte ie ") >= 0 || + commentContent.indexOf("[if lt ie ") >= 0 || + commentContent.indexOf("[if lte ie ") >= 0; + + if (hasConditionalComment) { + rulecheck.failed = true; + rulecheck.items.push({ + title: VORLON.Tools.htmlToString(node.nodeValue) + }); + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssfallback.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssfallback.ts new file mode 100644 index 00000000..6deb29e5 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssfallback.ts @@ -0,0 +1,66 @@ +module VORLON.WebStandards.Rules.CSS { + + + + export var cssfallback = { + id: "webstandards.cssfallback", + title: "incorrect use of css fallback", + description: "Ensure css fallback.", + check: function(url, ast, rulecheck: any, analyzeSummary: any) {}, + endcheck: function(rulecheck: any, analyzeSummary: any) { + //console.log("check css css fallback"); + + var nodes: any = []; + + rulecheck.items = []; + var failed = false; + if (analyzeSummary.fallBackErrorList === null) { + rulecheck.title = "(disabled !) incorrect use of css fallback"; + failed = true; + var np = { + title: "the check of css Fallback is disabled", + type: "blockitems", + failed:true, + items: [] + } + rulecheck.items.push(np); + } + else{ + for (var ii=0;ii< analyzeSummary.fallBackErrorList.length;ii++) { + + + + + for (var fallErrorFile in analyzeSummary.fallBackErrorList[ii]) { + failed = true; + var proprules = { + title: fallErrorFile, + type: "itemslist", + items: [] + } + for (var errorFile in analyzeSummary.fallBackErrorList[ii][fallErrorFile]) { + var peroor = { + failed: true, + id: "." + analyzeSummary.fallBackErrorList[ii][fallErrorFile][errorFile][ind], + items: [], + title: errorFile + } + proprules.items.push(peroor); + + for (var ind = 0; ind < analyzeSummary.fallBackErrorList[ii][fallErrorFile][errorFile].length; ind++) { + peroor.items.push({ + failed: true, id: "." + analyzeSummary.fallBackErrorList[ii][fallErrorFile][errorFile][ind], items: [], + title: "from " + analyzeSummary.fallBackErrorList[ii][fallErrorFile][errorFile][ind] + " to " + analyzeSummary.fallBackErrorList[ii][fallErrorFile][errorFile][ind].replace("-webkit-", "").replace("-moz-", "").replace("-o-", "").replace("-ms-", ""), type: "error" + }); + } + if (proprules.items.length) { + rulecheck.items.push(proprules); + } + } + } } + } + rulecheck.failed = failed; + + }, + } +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssprefixes.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssprefixes.ts new file mode 100644 index 00000000..ed4762ae --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.cssprefixes.ts @@ -0,0 +1,153 @@ +module VORLON.WebStandards.Rules.CSS { + var compatiblePrefixes = { + 'animation': 'webkit', + 'animation-delay': 'webkit', + 'animation-direction': 'webkit', + 'animation-duration': 'webkit', + 'animation-fill-mode': 'webkit', + 'animation-iteration-count': 'webkit', + 'animation-name': 'webkit', + 'animation-play-state': 'webkit', + 'animation-timing-function': 'webkit', + 'appearance': 'webkit moz', + 'border-end': 'webkit moz', + 'border-end-color': 'webkit moz', + 'border-end-style': 'webkit moz', + 'border-end-width': 'webkit moz', + 'border-image': 'webkit o', + 'border-start': 'webkit moz', + 'border-start-color': 'webkit moz', + 'border-start-style': 'webkit moz', + 'border-start-width': 'webkit moz', + 'box-sizing': 'webkit', + 'column-count': 'webkit moz', + 'column-gap': 'webkit moz', + 'column-rule': 'webkit moz', + 'column-rule-color': 'webkit moz', + 'column-rule-style': 'webkit moz', + 'column-rule-width': 'webkit moz', + 'column-width': 'webkit moz', + 'hyphens': 'webkit moz ms', + 'margin-end': 'webkit moz', + 'margin-start': 'webkit moz', + 'padding-end': 'webkit moz', + 'padding-start': 'webkit moz', + 'tab-size': 'webkit moz o', + 'text-size-adjust': 'webkit moz ms', + 'transform': 'webkit ms', + 'transform-origin': 'webkit ms', + 'transition': 'webkit moz o', + 'transition-delay': 'webkit moz o', + 'transition-duration': 'webkit', + 'transition-property': 'webkit', + 'transition-timing-function': 'webkit', + 'user-select': 'webkit moz ms' + }; + + var variations, + prefixed, + arrayPush = Array.prototype.push, + applyTo: Array = new Array(); + + for (var prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = []; + prefixed = compatiblePrefixes[prop].split(' '); + for (var i = 0, len = prefixed.length; i < len; i++) { + variations.push('-' + prefixed[i] + '-' + prop); + } + compatiblePrefixes[prop] = variations; + variations.forEach((obj, i) => { + applyTo[obj] = i; + }); + } + } + + export var cssprefixes = { + id: "webstandards.prefixes", + title: "incorrect use of prefixes", + description: "Ensure you use all vendor prefixes and unprefixed version for HTML5 CSS properties.", + check: function (url, ast, rulecheck: any, analyzeSummary: any) { + //console.log("check css prefixes"); + + var nodes: any = []; + var filerules = { + title: url, + type: "itemslist", + items: [] + } + rulecheck.items = rulecheck.items || []; + this.checkNodes(url, compatiblePrefixes, filerules, ast, nodes); + + if (filerules.items.length) { + rulecheck.items.push(filerules); + rulecheck.failed = true; + } + }, + + unprefixedPropertyName: function (property) { + return property.replace("-webkit-", "").replace("-moz-", "").replace("-o-", "").replace("-ms-", ""); + }, + + getMissingPrefixes: function (compatiblePrefixes, node, property) { + var allProperty = compatiblePrefixes[property]; + var prefixes = []; + + + allProperty.forEach((prop, y) => { + var hasPrefix = node.rules.some((r) => { return r.directive == prop }); + if (!hasPrefix) { + prefixes.push(prop); + } + }); + + return prefixes; + }, + + checkNodes: function (url, compatiblePrefixes, rulecheck, ast, nodes) { + if (!ast) + return; + + ast.forEach((node, i) => { + var nodeitem = null; + + if (node.rules && node.rules.length > 0) { + var checked = {}; + for (var x = 0, len = node.rules.length; x < len; x++) { + var property = node.rules[x].directive; + var unprefixed = this.unprefixedPropertyName(property); + + if (!checked[unprefixed] && compatiblePrefixes.hasOwnProperty(unprefixed)) { + if (compatiblePrefixes[unprefixed].indexOf(unprefixed) == -1) + compatiblePrefixes[unprefixed].push(unprefixed); + + var missings = this.getMissingPrefixes(compatiblePrefixes, node, unprefixed); + if (missings.length) { + if (!nodeitem) { + rulecheck.failed = true; + rulecheck.items = rulecheck.items || []; + nodeitem = { + title: node.selector, + items: [] + } + rulecheck.items.push(nodeitem); + } + + nodeitem.items.push({ + title: "" + unprefixed + " : missing " + missings, + }) + } + //nodes.push(this.convertNode(node, unprefixed)); + } + checked[unprefixed] = true; + } + } + + //scan content of media queries + if (node.type === "media") { + this.checkNodes(url, compatiblePrefixes, rulecheck, node.subStyles, nodes); + } + }); + } + } +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.documentmode.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.documentmode.ts new file mode 100644 index 00000000..49dd36dd --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.documentmode.ts @@ -0,0 +1,43 @@ +module VORLON.WebStandards.Rules.DOM { + export var modernDocType = { + id: "webstandards.documentmode", + title: "use modern doctype", + description: "Modern doctype like <!DOCTYPE html> are better for browser compatibility and enable using HTML5 features.", + nodeTypes: ["META"], + + prepare: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + }, + + check: function(node: HTMLElement, rulecheck: any, analyzeSummary: any, htmlString: string) { + var httpequiv = node.getAttribute("http-equiv"); + + if (httpequiv && httpequiv.toLowerCase() == "x-ua-compatible"){ + var content = node.getAttribute("content"); + if (!(content.toLowerCase().indexOf("edge") >= 0)){ + rulecheck.failed = true; + //current.content = doctype.html; + rulecheck.items.push({ + title : "your website use IE's document mode compatibility for an older version of IE ", + content : VORLON.Tools.htmlToString(node.outerHTML) + }); + } + } + }, + + endcheck: function(rulecheck: any, analyzeSummary: any, htmlString: string) { + //console.log("checking comment " + node.nodeValue); + var doctype = analyzeSummary.doctype || {}; + var current = { + title : "used doctype is
      " + VORLON.Tools.htmlToString(doctype.html) + } + + if (doctype.publicId || doctype.systemId){ + rulecheck.failed = true; + //current.content = doctype.html; + rulecheck.items.push(current); + } + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/rules/webstandards.jslibversions.ts b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.jslibversions.ts new file mode 100644 index 00000000..fe6197e6 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/rules/webstandards.jslibversions.ts @@ -0,0 +1,320 @@ +module VORLON.WebStandards.Rules.JavaScript { + var libraries = [ + { + name: 'Prototype', + minVersions: [ + { major: '1.7.', minor: '2' } + ], + check: function(checkVersion, scriptText) { + var version = scriptText.match(/Prototype JavaScript framework, version (\d+\.\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'Dojo', + minVersions: [ + { major: '1.5.', minor: '3' }, + { major: '1.6.', minor: '2' }, + { major: '1.7.', minor: '5' }, + { major: '1.8.', minor: '5' }, + { major: '1.9.', minor: '2' }, + { major: '1.10.', minor: '0' } + ], + check: function(checkVersion, scriptText) { + if (scriptText.indexOf('dojo') === -1) { + return false; + } + + var version = scriptText.match(/\.version\s*=\s*\{\s*major:\s*(\d+)\D+(\d+)\D+(\d+)/m); + if (version) { + return checkVersion(this, version[1] + '.' + version[2] + '.' + version[3]); + } + + version = scriptText.match(/\s*major:\s*(\d+),\s*minor:\s*(\d+),\s*patch:\s*(\d+),/mi); + return version && checkVersion(this, version[1] + '.' + version[2] + '.' + version[3]); + } + }, + { + name: 'Mootools', + minVersions: [ + { major: '1.2.', minor: '6' }, + { major: '1.4.', minor: '5' }, + { major: '1.5.', minor: '' } + ], + check: function(checkVersion, scriptText) { + var version = scriptText.match(/this.MooTools\s*=\s*\{version:\s*'(\d+\.\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'SWFObject', + minVersions: [ + { major: '2.', minor: '2' } + ], + check: function(checkVersion, scriptText) { + var version = scriptText.match(/\*\s+SWFObject v(\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery Form Plugin', + minVersions: [ + { major: '3.', minor: '22' } + ], + check: function(checkVersion, scriptText) { + var version = scriptText.match(/Form Plugin\s+\*\s+version: (\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'Modernizr', + minVersions: [ + { major: '2.5.', minor: '2' }, + { major: '2.6.', minor: '2' }, + { major: '2.7.', minor: '1' }, + { major: '2.8.', minor: '3' } + ], + check: function(checkVersion, scriptText) { + // Static analysis. :( The version is set as a local variable, far from + // where Modernizr._version is set. Just see if we have a comment header. + // ALT: look for /VAR="1.2.3"/ then for /._version=VAR/ ... ugh. + var version = scriptText.match(/\*\s*Modernizr\s+(\d+\.\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery cookie', + minVersions: [ + { major: '1.3.', minor: '1' }, + { major: '1.4.', minor: '1' } + ], + patchOptional: false, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/\*\s*jQuery Cookie Plugin v(\d+\.\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'hoverIntent', + minVersions: [ + { major: '1.8.', minor: '1' } + ], + patchOptional: false, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/\*\s*hoverIntent v(\d+\.\d+\.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery Easing', + minVersions: [ + { major: '1.3.', minor: '0' } + ], + patchOptional: true, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/\*\s*jQuery Easing v(\d+\.\d+)\s*/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'underscore', + minVersions: [ + { major: '1.8.', minor: '3' }, + { major: '1.7.', minor: '0' }, + { major: '1.6.', minor: '0' }, + { major: '1.5.', minor: '2' } + + ], + patchOptional: false, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/exports._(?:.*)?.VERSION="(\d+.\d+.\d+)"/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'hammer js', + minVersions: [ + { major: '2.0.', minor: '4' } + ], + patchOptional: false, + check: function(checkVersion, scriptText) { + if (scriptText.indexOf('hammer.input') !== -1) { + var version = scriptText.match(/.VERSION\s*=\s*['|"](\d+.\d+.\d+)['|"]/m); + return version && checkVersion(this, version[1]); + } + + return false; + } + }, + { + name: 'jQuery Superfish', + minVersions: [ + { major: '1.7.', minor: '4' } + ], + patchOptional: false, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/jQuery Superfish Menu Plugin - v(\d+.\d+.\d+)"/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery mousewheel', + minVersions: [ + { major: '3.1.', minor: '12' } + ], + patchOptional: true, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/.mousewheel={version:"(\d+.\d+.\d+)/); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery mobile', + minVersions: [ + { major: '1.4.', minor: '5' }, + { major: '1.3.', minor: '2' } + ], + patchOptional: true, + check: function(checkVersion, scriptText) { + var version = scriptText.match(/.mobile,{version:"(\d+.\d+.\d+)/); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery UI', + minVersions: [ + { major: '1.8.', minor: '24' }, + { major: '1.9.', minor: '2' }, + { major: '1.10.', minor: '4' }, + { major: '1.11.', minor: '4' } + ], + check: function(checkVersion, scriptText) { + var version = scriptText.match(/\.ui,[\s\r\n]*\{[\s\r\n]*version:\s*"(\d+.\d+.\d+)/m); + return version && checkVersion(this, version[1]); + } + }, + { + name: 'jQuery', + minVersions: [ + { major: '1.6.', minor: '4' }, + { major: '1.7.', minor: '2' }, + { major: '1.8.', minor: '2' }, + { major: '1.9.', minor: '1' }, + { major: '1.10.', minor: '2' }, + { major: '1.11.', minor: '3' }, + { major: '2.0.', minor: '3' }, + { major: '2.1.', minor: '4' } + ], + patchOptional: true, + check: function(checkVersion, scriptText) { + //We search the version in the header + //Explanation: Some libraries have things like: Requires: jQuery v1.7.1 (cycle, for example) + //We are matching regex that contain jQuery vx.y.z but do not have : right before jQuery + var regex = /(?:jQuery\s*v)(\d+.\d+.\d+)\s/g; + var regversion = regex.exec(scriptText); + if (regversion) { + var isPluginRegExp = new RegExp('(?::\\s*)' + regversion[0], 'g'); + + if (!isPluginRegExp.exec(scriptText)) { + return checkVersion(this, regversion[1]); + } + } + + var matchversion = scriptText.match(/jquery:\s*"([^"]+)/); + if (matchversion) { + return checkVersion(this, matchversion[1]); + } + + //If header fails, we look with another pattern + var regex = /(?:jquery[,\)].{0,200}=")(\d+\.\d+)(\..*?)"/gi; + var results = regex.exec(scriptText); + var version = results ? (results[1] + (results[2] || '')) : null; + + return version && checkVersion(this, version); + } + } + ]; + + export var librariesVersions = { + id: "webstandards.javascript-libraries-versions", + title: "update javascript libraries", + description: "Try being up to date with your JavaScript libraries like jQuery. Latest versions usually improves performances and browsers compatibility.", + + + prepare: function(rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + rulecheck.type = "blockitems"; + }, + + check: function(url: string, javascriptContent: string, rulecheck: any, analyzeSummary: any) { + rulecheck.items = rulecheck.items || []; + var filecheck = null; + + + if (!javascriptContent || url == "inline") + return; + + for (var i = 0; i < libraries.length; i++) { + var lib = libraries[i], result; + + result = lib.check.call(lib, this.checkVersion, javascriptContent); + if (result && result.needsUpdate) { + if (!filecheck){ + filecheck = { + title : url, + items : [] + } + rulecheck.items.push(filecheck); + } + + filecheck.items.push({ + title : "detected " + result.name + " version " + result.version, + }); + + rulecheck.failed = true; + + break; + } + + } + }, + + checkVersion: function(library, version) { + + var vinfo = { + name: library.name, + needsUpdate: true, + minVersion: library.minVersions[0].major + library.minVersions[0].minor, + version: version, + bannedVersion : null + }; + + if (library.patchOptional) { + // If lib can have an implied ".0", add it when needed + // match 1.17, 1.17b2, 1.17-beta2; not 1.17.0, 1.17.2, 1.17b2 + var parts = version.match(/^(\d+\.\d+)(.*)$/); + if (parts && !/^\.\d+/.test(parts[2])) { + version = parts[1] + '.0' + parts[2]; + } + } + + for (var i = 0; i < library.minVersions.length; i++) { + var gv = library.minVersions[i]; + if (version.indexOf(gv.major) === 0) { + vinfo.minVersion = gv.major + gv.minor; + vinfo.needsUpdate = +version.slice(gv.major.length) < +gv.minor; + break; + } + } + + if (library.bannedVersions) { + if (library.bannedVersions.indexOf(version) >= 0) { + vinfo.bannedVersion = version; + vinfo.needsUpdate = true; + } + } + + return vinfo; + } + } +} diff --git a/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.client.ts b/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.client.ts new file mode 100644 index 00000000..0ec04571 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.client.ts @@ -0,0 +1,263 @@ +declare var cssjs: any; + +module VORLON { + export class WebStandardsClient extends ClientPlugin { + public sendedHTML: string; + private _doctype: any; + private _currentAnalyze: any = {}; + public browserDetectionHook = { + userAgent: [], + appVersion: [], + appName: [], + product: [], + vendor: [], + }; + private exceptions = [ + "vorlon.max.js", + "vorlon.min.js", + "vorlon.js", + "google-analytics.com" + ]; + + constructor() { + super("webstandards"); + this._id = "WEBSTANDARDS"; + this._ready = true; + //this.debug = true; + } + + public refresh(): void { + //override this method with cleanup work that needs to happen + //as the user switches between clients on the dashboard + } + + // Start the clientside code + public startClientSide(): void { + this.hook(window.navigator, "userAgent"); + this.hook(window.navigator, "appVersion"); + this.hook(window.navigator, "appName"); + this.hook(window.navigator, "product"); + this.hook(window.navigator, "vendor"); + } + + public hook(root, prop) { + VORLON.Tools.HookProperty(root, prop, (stack) => { + //this.trace("browser detection " + stack.file); + //this.trace(stack.stack); + if (stack.file) { + if (this.exceptions.some((s) => { return stack.file.indexOf(s) >= 0 })) { + //this.trace("skip browser detection access " + stack.file) + + return; + } + } + this.browserDetectionHook[prop].push(stack); + }); + } + + private capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + public startNewAnalyze(data): void { + var allHTML = document.documentElement.outerHTML; + this.sendedHTML = allHTML; + + + var node = document.doctype; + + if (node) { + var doctypeHtml = "'; + this._doctype = { + html: doctypeHtml, + name: node.name, + publicId: node.publicId, + systemId: node.systemId + } + } + var inlineStylesheets = document.querySelectorAll("style"); + var stylesheetErrors = null; + if (data.analyzeCssFallback) { + stylesheetErrors = {} + if (inlineStylesheets.length) { + for (var x = 0; x < inlineStylesheets.length; x++) { + this.analyzeCssDocument("inline " + [x], (inlineStylesheets[x]).innerHTML, data.id, stylesheetErrors); + } + } + } + this.sendCommandToDashboard("htmlContent", { html: allHTML, doctype: this._doctype, url: window.location, browserDetection: this.browserDetectionHook, id: data.id, stylesheetErrors: stylesheetErrors }); + } + + checkIfNoPrefix(rules: Array, prefix: string) { + var present = false; + if (rules && rules.length) + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive.indexOf(prefix) === 0) { + present = true; + break; + } + } + if (!present) { + present = this.checkIfMsPrefix(rules, prefix); + } + + return present; + } + + checkIfMsPrefix(rules: Array, prefix: string) { + var present = false; + if (rules && rules.length) + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive.indexOf('-ms-' + prefix) === 0) { + present = true; + break; + } + } + + return present; + } + + unprefixedPropertyName(property: string) { + return property.replace("-webkit-", "").replace("-moz-", "").replace("-o-", "").replace("-ms-", ""); + } + + checkPrefix(rules: Array): Array { + var errorList = []; + if (rules && rules.length) + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive.indexOf('-webkit') === 0) { + var _unprefixedPropertyName = this.unprefixedPropertyName(rules[i].directive) + var good = this.checkIfNoPrefix(rules, _unprefixedPropertyName); + if (!good) { + var divTest = document.createElement('div'); + divTest.style['webkit' + this.capitalizeFirstLetter(_unprefixedPropertyName)] = rules[i].value; + if (divTest.style[_unprefixedPropertyName] == divTest.style['webkit' + this.capitalizeFirstLetter(_unprefixedPropertyName)]) { + good = true; + } + } + if (!good) { + errorList.push(rules[i].directive); + } + } + } + + return errorList; + } + + analyzeCssDocument(url, content, id, results) { + var parser = new cssjs(); + var parsed = parser.parseCSS(content); + // console.log("processing css " + url); + for (var i = 0; i < parsed.length; i++) { + var selector = parsed[i].selector; + var rules = parsed[i].rules; + + var resultsList = this.checkPrefix(rules); + if (resultsList.length > 0) { + if (!results[url]) + results[url] = {} + if (!results[url][selector]) + results[url][selector] = []; + for (var x = 0; x < resultsList.length; x++) { + results[url][selector].push(resultsList[x]); + } + } + } + } + + public fetchDocument(data: { id: string, url: string, type: string, analyzeCssFallback: boolean }, localFetch: boolean = false) { + var xhr = null; + var completed = false; + var timeoutRef = null; + if (!data || !data.url) { + this.trace("invalid fetch request"); + return; + } + + var documentUrl = data.url; + if (documentUrl.indexOf("//") === 0) { + documentUrl = window.location.protocol + documentUrl; + } + + documentUrl = this.getAbsolutePath(documentUrl); + if (documentUrl.indexOf("http") === 0) { + //external resources may not have Access Control headers, we make a proxied request to prevent CORS issues + var serverurl = (VORLON.Core._messenger)._serverUrl; + if (serverurl[serverurl.length - 1] !== '/') + serverurl = serverurl + "/"; + var target = this.getAbsolutePath(data.url); + documentUrl = serverurl + "httpproxy/fetch?fetchurl=" + encodeURIComponent(target); + } + this.trace("fetching " + documentUrl); + + try { + xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + completed = true; + clearTimeout(timeoutRef); + var encoding = xhr.getResponseHeader("X-VorlonProxyEncoding") || xhr.getResponseHeader("content-encoding"); + var contentLength = xhr.getResponseHeader("content-length"); + this.trace("encoding for " + data.url + " is " + encoding); + var stylesheetErrors = null; + if (data.type === "stylesheet" && data.analyzeCssFallback === true) { + stylesheetErrors = {}; + this.analyzeCssDocument(data.url, xhr.responseText, data.id, stylesheetErrors); + } + //TODO getting encoding is not working in IE (but do in Chrome), must try on other browsers because getting it may enable performance rules + this.sendCommandToDashboard("documentContent", { id: data.id, url: data.url, status: xhr.status, content: xhr.responseText, contentLength: contentLength, encoding: encoding, stylesheetErrors: stylesheetErrors }); + } + else { + completed = true; + clearTimeout(timeoutRef); + this.sendCommandToDashboard("documentContent", { id: data.id, url: data.url, status: xhr.status, content: null, error: xhr.statusText }); + } + } + }; + + xhr.open("GET", documentUrl, true); + xhr.send(null); + timeoutRef = setTimeout(() => { + if (!completed){ + completed = true; + this.trace("fetch timeout for " + data.url); + xhr.abort(); + this.sendCommandToDashboard("documentContent", { id: data.id, url: data.url, status: null, content: null, error: "timeout" }); + } + }, 20*1000); + } catch (e) { + console.error(e); + completed = true; + clearTimeout(timeoutRef); + this.sendCommandToDashboard("documentContent", { id: data.id, url: data.url, status: 0, content: null, error: e.message }); + } + } + + public getAbsolutePath(url) { + var a = document.createElement('a'); + a.href = url; + return a.href; + } + } + + WebStandardsClient.prototype.ClientCommands = { + startNewAnalyze: function (data: any) { + var plugin = this; + plugin.startNewAnalyze(data); + }, + + fetchDocument: function (data: any) { + var plugin = this; + plugin.fetchDocument(data); + } + }; + + //Register the plugin with vorlon core + Core.RegisterClientPlugin(new WebStandardsClient()); +} diff --git a/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.interfaces.ts b/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.interfaces.ts new file mode 100644 index 00000000..b3e85640 --- /dev/null +++ b/Plugins/Vorlon/plugins/webstandards/vorlon.webstandards.interfaces.ts @@ -0,0 +1,36 @@ +module VORLON { + export interface IDOMRule { + id: string; + title: string; + nodeTypes: string[]; + prepare?: (rulecheck, analyze, htmlcontent) => void; + check: (node, rulecheck, analyze, htmlcontent) => void; + endcheck?: (rulecheck, analyze, htmlcontent) => void; + generalRule?: boolean; + description?: string; + } + + export interface ICSSRule { + id: string; + title: string; + prepare?: (rulecheck, analyzeSummary) => void; + check: (url: string, ast, rulecheck, analyzeSummary) => void; + endcheck?: (rulecheck, analyzeSummary) => void; + description?: string; + } + + export interface IFileRule { + id: string; + title: string; + check: (rulecheck: any, analyzeSummary: any) => void; + description?: string; + } + export interface IScriptRule { + id: string; + title: string; + prepare?: (rulecheck: any, analyzeSummary: any) => void; + check: (url: string, javascriptContent: string, rulecheck: any, analyzeSummary: any) => void; + endcheck?: (rulecheck: any, analyzeSummary: any) => void; + description?: string; + } +} diff --git a/Plugins/Vorlon/plugins/xhrPanel/vorlon.xhrPanel.client.ts b/Plugins/Vorlon/plugins/xhrPanel/vorlon.xhrPanel.client.ts index 5e5cb4c9..1355351e 100644 --- a/Plugins/Vorlon/plugins/xhrPanel/vorlon.xhrPanel.client.ts +++ b/Plugins/Vorlon/plugins/xhrPanel/vorlon.xhrPanel.client.ts @@ -39,10 +39,10 @@ module VORLON { } public setupXMLHttpRequestHook(){ - + var that = this; var w = window; w.___XMLHttpRequest = w.XMLHttpRequest; - var XmlHttpRequestProxy = () => { + var XmlHttpRequestProxy = function() { var xhr = new w.___XMLHttpRequest(); var data = { id: VORLON.Tools.CreateGUID(), @@ -55,7 +55,7 @@ module VORLON { requestHeaders : [], readyState: 0, } - this.cache.push(data); + that.cache.push(data); xhr.__open = xhr.open; xhr.__send = xhr.send; xhr.__setRequestHeader = xhr.setRequestHeader; @@ -63,17 +63,17 @@ module VORLON { //todo catch send to get posted data //see https://msdn.microsoft.com/en-us/library/hh772834(v=vs.85).aspx - xhr.open = () => { + xhr.open = function() { data.method = arguments[0]; data.url = arguments[1]; - this.trace('request for ' + data.url); - this.sendCommandToDashboard('xhr', data); + that.trace('request for ' + data.url); + that.sendCommandToDashboard('xhr', data); xhr.__open.apply(xhr, arguments); return xhr.__open.apply(xhr, arguments); } - xhr.setRequestHeader = () => { + xhr.setRequestHeader = function() { var header = { name : arguments[0], value : arguments[1] @@ -84,7 +84,7 @@ module VORLON { xhr.addEventListener('readystatechange', () => { data.readyState = xhr.readyState; - this.trace('STATE CHANGED ' + data.readyState); + that.trace('STATE CHANGED ' + data.readyState); if (data.readyState === 4){ data.responseType = xhr.responseType; @@ -94,9 +94,9 @@ module VORLON { if (xhr.getAllResponseHeaders) data.responseHeaders = xhr.getAllResponseHeaders(); - this.trace('LOADED !!!'); + that.trace('LOADED !!!'); } - this.sendCommandToDashboard('xhr', data); + that.sendCommandToDashboard('xhr', data); }); return xhr; diff --git a/Plugins/Vorlon/vorlon.clientMessenger.ts b/Plugins/Vorlon/vorlon.clientMessenger.ts index 3c69497f..03273e5d 100644 --- a/Plugins/Vorlon/vorlon.clientMessenger.ts +++ b/Plugins/Vorlon/vorlon.clientMessenger.ts @@ -8,7 +8,6 @@ sessionId : string; clientId: string; listenClientId: string; - waitingEvents?: number; } export interface VorlonMessage { @@ -18,21 +17,21 @@ } export class ClientMessenger { - private _socket: Socket; + private _socket: any; private _isConnected = false; private _sessionId: string; private _clientId: string; private _listenClientId: string; private _serverUrl: string; - private _waitingEvents: number; public onRealtimeMessageReceived: (message: VorlonMessage) => void; public onHeloReceived: (id: string) => void; public onIdentifyReceived: (id: string) => void; - public onWaitingEventsReceived: (message: VorlonMessage) => void; + public onRemoveClient: (id: any) => void; + public onAddClient: (id: any) => void; public onStopListenReceived: () => void; public onRefreshClients: () => void; - + public onReload: (id: string) => void; public onError: (err: Error) => void; public get isConnected(): boolean { @@ -53,7 +52,6 @@ this._clientId = clientId; Core._listenClientId = listenClientId; this._serverUrl = serverUrl; - this._waitingEvents = 0; switch (side) { case RuntimeSide.Client: @@ -103,42 +101,44 @@ } }); - this._socket.on('waitingevents', message => { - //console.log('messenger waitingevents', message); - if (this.onWaitingEventsReceived) { - var receivedObject = JSON.parse(message); - this.onWaitingEventsReceived(receivedObject); - } - }); - this._socket.on('refreshclients',() => { //console.log('messenger refreshclients'); if (this.onRefreshClients) { this.onRefreshClients(); } }); - } - } + + this._socket.on('addclient', client => { + //console.log('messenger refreshclients'); + if (this.onAddClient) { + this.onAddClient(client); + } + }); - public sendWaitingEvents(pluginID: string, waitingevents: number): void { - var message: VorlonMessage = { - metadata: { - pluginID: pluginID, - side: RuntimeSide.Client, - sessionId: this._sessionId, - clientId: this._clientId, - listenClientId: Core._listenClientId, - waitingEvents: waitingevents - } - }; + this._socket.on('removeclient', client => { + //console.log('messenger refreshclients'); + if (this.onRemoveClient) { + this.onRemoveClient(client); + } + }); - if (this.isConnected) { - var messagestr = JSON.stringify(message); - this._socket.emit("waitingevents", messagestr); + this._socket.on('reload', message => { + //console.log('messenger reloadclient', message); + Core._listenClientId = message; + if (this.onReload) { + this.onReload(message); + } + }); + } + } + + public stopListening(): void{ + if(this._socket){ + this._socket.removeAllListeners(); } } - public sendRealtimeMessage(pluginID: string, objectToSend: any, side: RuntimeSide, messageType = "message", incrementVisualIndicator = false, command?:string): void { + public sendRealtimeMessage(pluginID: string, objectToSend: any, side: RuntimeSide, messageType = "message", command?:string): void { var message: VorlonMessage = { metadata: { pluginID: pluginID, @@ -160,17 +160,9 @@ } return; } else { - if (Core._listenClientId === "" && messageType === "message") { - if (incrementVisualIndicator) { - this._waitingEvents++; - this.sendWaitingEvents(pluginID, this._waitingEvents); - } - } else { + if (Core._listenClientId !== "" || messageType !== "message") { var strmessage = JSON.stringify(message); this._socket.emit(messageType, strmessage); - - this._waitingEvents = 0; - this.sendWaitingEvents(pluginID, 0); } } } diff --git a/Plugins/Vorlon/vorlon.clientPlugin.ts b/Plugins/Vorlon/vorlon.clientPlugin.ts index 3f0174bc..63fe9526 100644 --- a/Plugins/Vorlon/vorlon.clientPlugin.ts +++ b/Plugins/Vorlon/vorlon.clientPlugin.ts @@ -1,24 +1,27 @@ module VORLON { + declare var vorlonBaseURL: string; + export class ClientPlugin extends BasePlugin { public ClientCommands: any; - + constructor(name: string) { super(name); + } public startClientSide(): void { } public onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void { } - public sendToDashboard(data: any, incrementVisualIndicator: boolean = false){ + public sendToDashboard(data: any){ if (Core.Messenger) - Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Client, "message", incrementVisualIndicator); + Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Client, "message"); } - public sendCommandToDashboard(command: string, data: any = null, incrementVisualIndicator: boolean = false) { + public sendCommandToDashboard(command: string, data: any = null) { if (Core.Messenger) { this.trace(this.getID() + ' send command to dashboard ' + command); - Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Client, "message", incrementVisualIndicator, command); + Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Client, "message", command); } } @@ -26,19 +29,29 @@ console.error("Please override plugin.refresh()"); } - public _loadNewScriptAsync(scriptName: string, callback: () => void) { + public _loadNewScriptAsync(scriptName: string, callback: () => void, waitForDOMContentLoaded?: boolean) { var basedUrl = ""; if(this.loadingDirectory.indexOf('http') === 0){ basedUrl = this.loadingDirectory + "/" + this.name + "/"; } else{ - basedUrl = "/" + this.loadingDirectory + "/" + this.name + "/"; + basedUrl = vorlonBaseURL + "/" + this.loadingDirectory + "/" + this.name + "/"; + } + function loadScript() { + var scriptToLoad = document.createElement("script"); + scriptToLoad.setAttribute("src", basedUrl + scriptName); + scriptToLoad.onload = callback; + var first = document.getElementsByTagName('script')[0]; + first.parentNode.insertBefore(scriptToLoad, first); + } + if (!waitForDOMContentLoaded || document.body) { + loadScript(); + } + else { + document.addEventListener("DOMContentLoaded", () => { + this._loadNewScriptAsync(scriptName, callback, waitForDOMContentLoaded); + }); } - var scriptToLoad = document.createElement("script"); - scriptToLoad.setAttribute("src", basedUrl + scriptName); - scriptToLoad.onload = callback; - var first = document.getElementsByTagName('script')[0]; - first.parentNode.insertBefore(scriptToLoad, first); } } } diff --git a/Plugins/Vorlon/vorlon.core.ts b/Plugins/Vorlon/vorlon.core.ts index a6a74482..6c2a9571 100644 --- a/Plugins/Vorlon/vorlon.core.ts +++ b/Plugins/Vorlon/vorlon.core.ts @@ -20,7 +20,7 @@ public get ClientPlugins(): Array { return Core._clientPlugins; } - + public get DashboardPlugins(): Array { return Core._dashboardPlugins; } @@ -28,11 +28,18 @@ public RegisterClientPlugin(plugin: ClientPlugin): void { Core._clientPlugins.push(plugin); } - + public RegisterDashboardPlugin(plugin: DashboardPlugin): void { Core._dashboardPlugins.push(plugin); } - + + public StopListening(): void { + if (Core._messenger) { + Core._messenger.stopListening(); + delete Core._messenger; + } + } + public StartClientSide(serverUrl = "'http://localhost:1337/'", sessionId = "", listenClientId = ""): void { Core._side = RuntimeSide.Client; Core._sessionID = sessionId; @@ -44,7 +51,7 @@ if (this._socketIOWaitCount < 10) { this._socketIOWaitCount++; // Let's wait a bit just in case socket.io was loaded asynchronously - setTimeout(function () { + setTimeout(function() { console.log("Vorlon.js: waiting for socket.io to load..."); Core.StartClientSide(serverUrl, sessionId, listenClientId); }, 1000); @@ -64,6 +71,10 @@ } // Creating the messenger + if (Core._messenger) { + Core._messenger.stopListening(); + delete Core._messenger; + } Core._messenger = new ClientMessenger(Core._side, serverUrl, sessionId, clientId, listenClientId); // Connect messenger to dispatcher @@ -72,6 +83,7 @@ Core.Messenger.onIdentifyReceived = Core._OnIdentifyReceived; Core.Messenger.onStopListenReceived = Core._OnStopListenReceived; Core.Messenger.onError = Core._OnError; + Core.Messenger.onReload = Core._OnReloadClient; // Say 'helo' var heloMessage = { @@ -87,7 +99,7 @@ } // Handle client disconnect - window.addEventListener("beforeunload", function () { + window.addEventListener("beforeunload", function() { Core.Messenger.sendRealtimeMessage("", { socketid: Core.Messenger.socketId }, Core._side, "clientclosed"); }, false); @@ -99,6 +111,14 @@ } public startClientDirtyCheck() { + //sometimes refresh is called before document was loaded + if (!document.body) { + setTimeout(() => { + this.startClientDirtyCheck(); + }, 200); + return; + } + var mutationObserver = (window).MutationObserver || (window).WebKitMutationObserver || null; if (mutationObserver) { if (!document.body.__vorlon) @@ -107,13 +127,25 @@ var config = { attributes: true, childList: true, subtree: true, characterData: true }; document.body.__vorlon._observerMutationObserver = new mutationObserver((mutations) => { var sended = false; + var cancelSend = false; + var sendComandId = []; mutations.forEach((mutation) => { + if (cancelSend) { + for (var i = 0; i < sendComandId.length; i++) { + clearTimeout(sendComandId[i]); + } + cancelSend = false; + } if (mutation.target && mutation.target.__vorlon && mutation.target.__vorlon.ignore) { + cancelSend = true; + return; + } + if (mutation.previousSibling && mutation.previousSibling.__vorlon && mutation.previousSibling.__vorlon.ignore) { + cancelSend = true; return; } - if (mutation.target && !sended && mutation.target.__vorlon && mutation.target.parentNode && mutation.target.parentNode.__vorlon && mutation.target.parentNode.__vorlon.internalId) { - setTimeout(() => { + sendComandId.push(setTimeout(() => { var internalId = null; if (mutation && mutation.target && mutation.target.parentNode && mutation.target.parentNode.__vorlon && mutation.target.parentNode.__vorlon.internalId) internalId = mutation.target.parentNode.__vorlon.internalId; @@ -122,7 +154,7 @@ type: 'contentchanged', internalId: internalId }, Core._side, 'message'); - }, 300); + }, 300)); } sended = true; }); @@ -170,7 +202,7 @@ if (this._socketIOWaitCount < 10) { this._socketIOWaitCount++; // Let's wait a bit just in case socket.io was loaded asynchronously - setTimeout(function () { + setTimeout(function() { console.log("Vorlon.js: waiting for socket.io to load..."); Core.StartDashboardSide(serverUrl, sessionId, listenClientId, divMapper); }, 1000); @@ -191,6 +223,11 @@ } // Creating the messenger + if (Core._messenger) { + Core._messenger.stopListening(); + delete Core._messenger; + } + Core._messenger = new ClientMessenger(Core._side, serverUrl, sessionId, clientId, listenClientId); // Connect messenger to dispatcher @@ -226,6 +263,7 @@ } else { var div = document.createElement("div"); + div.className = "vorlonIdentifyNumber"; div.style.position = "absolute"; div.style.left = "0"; div.style.top = "50%"; @@ -287,7 +325,6 @@ } private _OnIdentificationReceived(id: string): void { - //console.log('helo received ' + id); Core._listenClientId = id; if (Core._side === RuntimeSide.Client) { @@ -296,12 +333,20 @@ var plugin = Core._clientPlugins[index]; plugin.refresh(); } - } else { + } + else { + //Stop bouncing and hide waiting page var elt = document.querySelector('.dashboard-plugins-overlay'); - Tools.AddClass(elt, 'hidden'); + VORLON.Tools.AddClass(elt, 'hidden'); + VORLON.Tools.RemoveClass(elt, 'bounce'); + document.getElementById('test').style.visibility = 'visible'; } } + private _OnReloadClient(id: string): void { + document.location.reload(); + } + private _RetrySendingRealtimeMessage(plugin: DashboardPlugin, message: VorlonMessage) { setTimeout(() => { if (plugin.isReady()) { diff --git a/Plugins/Vorlon/vorlon.dashboardPlugin.ts b/Plugins/Vorlon/vorlon.dashboardPlugin.ts index d0a59730..08626a1c 100644 --- a/Plugins/Vorlon/vorlon.dashboardPlugin.ts +++ b/Plugins/Vorlon/vorlon.dashboardPlugin.ts @@ -1,4 +1,6 @@ module VORLON { + declare var vorlonBaseURL: string; + export class DashboardPlugin extends BasePlugin { public htmlFragmentUrl; public cssStyleSheetUrl; @@ -20,29 +22,29 @@ Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Dashboard, "message"); } - public sendCommandToClient(command: string, data: any = null, incrementVisualIndicator: boolean = false) { + public sendCommandToClient(command: string, data: any = null) { if (Core.Messenger) { this.trace(this.getID() + ' send command to client ' + command); - Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Dashboard, "message", incrementVisualIndicator, command); + Core.Messenger.sendRealtimeMessage(this.getID(), data, RuntimeSide.Dashboard, "message", command); } } - public sendCommandToPluginClient(pluginId: string, command: string, data: any = null, incrementVisualIndicator: boolean = false) { + public sendCommandToPluginClient(pluginId: string, command: string, data: any = null) { if (Core.Messenger) { this.trace(this.getID() + ' send command to plugin client ' + command); - Core.Messenger.sendRealtimeMessage(pluginId, data, RuntimeSide.Dashboard, "protocol", incrementVisualIndicator, command); + Core.Messenger.sendRealtimeMessage(pluginId, data, RuntimeSide.Dashboard, "protocol", command); } } - public sendCommandToPluginDashboard(pluginId : string, command: string, data: any = null, incrementVisualIndicator: boolean = false) { + public sendCommandToPluginDashboard(pluginId : string, command: string, data: any = null) { if (Core.Messenger) { this.trace(this.getID() + ' send command to plugin dashboard ' + command); - Core.Messenger.sendRealtimeMessage(pluginId, data, RuntimeSide.Client, "protocol", incrementVisualIndicator, command); + Core.Messenger.sendRealtimeMessage(pluginId, data, RuntimeSide.Client, "protocol", command); } } public _insertHtmlContentAsync(divContainer: HTMLDivElement, callback: (filledDiv: HTMLDivElement) => void): void { - var basedUrl = "/" + this.loadingDirectory + "/" + this.name + "/"; + var basedUrl = vorlonBaseURL + "/" + this.loadingDirectory + "/" + this.name + "/"; var alone = false; if (!divContainer) { // Not emptyDiv provided, let's plug into the main DOM diff --git a/Plugins/Vorlon/vorlon.tools.ts b/Plugins/Vorlon/vorlon.tools.ts index 5069519d..2cfc6f37 100644 --- a/Plugins/Vorlon/vorlon.tools.ts +++ b/Plugins/Vorlon/vorlon.tools.ts @@ -37,6 +37,7 @@ } } } + public static Hook(rootObject: any, functionToHook: string, hookingFunction: (...optionalParams: any[]) => void): void { var previousFunction = rootObject[functionToHook]; @@ -46,6 +47,66 @@ } return previousFunction; } + + public static HookProperty(rootObject: any, propertyToHook: string, callback){ + var initialValue = rootObject[propertyToHook]; + Object.defineProperty(rootObject, propertyToHook, { + get: function() { + if (callback){ + callback(VORLON.Tools.getCallStack(1)); + } + return initialValue; + } + }); + } + + public static getCallStack(skipped){ + skipped = skipped || 0; + try { + //Throw an error to generate a stack trace + throw new Error(); + } + catch(e) { + //Split the stack trace into each line + var stackLines = e.stack.split('\n'); + var callerIndex = 0; + + //Now walk though each line until we find a path reference + for(var i=2 + skipped, l = stackLines.length; i= 0)) + continue; + //We skipped all the lines with out an http so we now have a script reference + //This one is the class constructor, the next is the getScriptPath() call + //The one after that is the user code requesting the path info (so offset by 2) + callerIndex = i; + break; + } + + var res = { + stack : e.stack, + // fullPath : pathParts ? pathParts[1] : null, + // path : pathParts ? pathParts[2] : null, + // file : pathParts ? pathParts[3] : null + }; + + var linetext = stackLines[callerIndex]; + //Now parse the string for each section we want to return + //var pathParts = linetext.match(/((http[s]?:\/\/.+\/)([^\/]+\.js))([\/]):/); + // if (pathParts){ + // + // } + var opening = linetext.indexOf("http://") || linetext.indexOf("https://"); + if (opening > 0){ + var closing = linetext.indexOf(")", opening); + if (closing < 0) + closing = linetext.length - 1; + var filename = linetext.substr(opening, closing - opening); + var linestart = filename.indexOf(":", filename.lastIndexOf("/")); + res.file = filename.substr(0, linestart); + } + return res; + } + } public static CreateCookie(name: string, value: string, days: number): void { var expires: string; @@ -192,13 +253,21 @@ } } - public static ToggleClass(e: HTMLElement, name: string) { + public static ToggleClass(e: HTMLElement, name: string, callback? : (hasClass:boolean) => void) { if (e.className.match(name)) { Tools.RemoveClass(e, name); + if (callback) + callback(false); } else { Tools.AddClass(e, name); + if (callback) + callback(true); } } + + public static htmlToString(text) { + return text.replace(//g, '>'); + } } export class FluentDOM { @@ -222,7 +291,7 @@ } } - public static for(element: HTMLElement) { + public static forElement(element: HTMLElement) { var res = new FluentDOM(null); res.element = element; return res; @@ -233,6 +302,11 @@ return this; } + toggleClass(classname: string) { + this.element.classList.toggle(classname); + return this; + } + className(classname: string) { this.element.className = classname; return this; @@ -288,7 +362,7 @@ return this; } - append(nodeType: string, className?: string, callback?: (FluentDOM) => void) { + append(nodeType: string, className?: string, callback?: (fdom: FluentDOM) => void) { var child = new FluentDOM(nodeType, className, this.element, this); if (callback) { callback(child); diff --git a/Plugins/gulpfile.js b/Plugins/gulpfile.js index c074c724..5f1e0659 100644 --- a/Plugins/gulpfile.js +++ b/Plugins/gulpfile.js @@ -15,19 +15,19 @@ gulp.task('typescript-to-js', function() { //Compile all ts file into their respective js file. var tsResult = gulp.src(['Vorlon/**/*.ts', 'libs/**.ts']) - .pipe(typescript({ + .pipe(typescript({ declarationFiles: true, noExternalResolve: true, target: 'ES5'} )); - + return merge([ tsResult.dts.pipe(gulp.dest('release')), tsResult.js.pipe(gulp.dest('release')) ]); }); -/** - * Compile less files to their css respective files + + /* Compile less files to their css respective files */ gulp.task('less-to-css', function() { return gulp.src(['Vorlon/**/*.less'], { base : '.' }) @@ -41,6 +41,7 @@ gulp.task('less-to-css', function() { */ gulp.task('scripts-noplugin', ['typescript-to-js'], function() { return gulp.src([ + 'libs/css.js', 'release/vorlon.tools.js', 'release/vorlon.enums.js', 'release/vorlon.basePlugin.js', @@ -57,6 +58,12 @@ gulp.task('scripts-noplugin', ['typescript-to-js'], function() { }); +gulp.task('concat-webstandards-rules', ['typescript-to-js'], function () { + return gulp.src(['./release/**/webstandards/rules/*.js', './release/**/webstandards/dashboard.js']) + .pipe(concat('vorlon.webstandards.dashboard.js')) + .pipe(gulp.dest('release/plugins/webstandards/')); +}); + /** * Specific task that need to be handled for specific plugins. * Do not hesitate to update it if you need to add your own files @@ -83,10 +90,10 @@ gulp.task('scripts-specific-plugins', function() { * Minify all plugins. * Do not hesitate to update it if you need to add your own files. */ -gulp.task('scripts', ['typescript-to-js'], function () { +gulp.task('scripts', ['concat-webstandards-rules'], function () { gulp.start('scripts-specific-plugins'); - + return gulp.src([ './**/vorlon.interactiveConsole.interfaces.js', './**/vorlon.interactiveConsole.client.js', @@ -112,8 +119,15 @@ gulp.task('scripts', ['typescript-to-js'], function () { './**/resourcesExplorer/vorlon.resourcesExplorer.interfaces.js', './**/resourcesExplorer/vorlon.resourcesExplorer.client.js', './**/resourcesExplorer/vorlon.resourcesExplorer.dashboard.js', + './**/unitTestRunner/vorlon.unitTestRunner.interfaces.js', + './**/unitTestRunner/vorlon.unitTestRunner.client.js', + './**/unitTestRunner/vorlon.unitTestRunner.dashboard.js', './**/sample/vorlon.sample.client.js', - './**/sample/vorlon.sample.dashboard.js' + './**/sample/vorlon.sample.dashboard.js', + './**/device/vorlon.device.client.js', + './**/device/vorlon.device.dashboard.js', + './**/webstandards/vorlon.webstandards.client.js', + './**/webstandards/vorlon.webstandards.dashboard.js' ]) .pipe(rename(function (path) { path.extname = ".min.js"; @@ -141,8 +155,8 @@ gulp.task('copyPlugins', function () { gulp.src([ 'Vorlon/plugins/**/*.js', 'Vorlon/plugins/**/*.css', - 'Vorlon/plugins/**/*.html', - 'Vorlon/plugins/**/*.png', + 'Vorlon/plugins/**/*.html', + 'Vorlon/plugins/**/*.png', 'release/plugins/**/*.js' ]) .pipe(gulp.dest('../Server/public/vorlon/plugins')); @@ -176,6 +190,7 @@ gulp.task('watch', function() { gulp.watch([ 'Vorlon/**/*.ts', 'Vorlon/**/*.less', + 'Vorlon/**/*.html' //'Vorlon/plugins/**/*.*', ], ['default']); }); diff --git a/Plugins/libs/css.js b/Plugins/libs/css.js new file mode 100644 index 00000000..e6d43251 --- /dev/null +++ b/Plugins/libs/css.js @@ -0,0 +1,672 @@ +/* jshint unused:false */ +/* global base64_decode, CSSWizardView, window, console, jQuery */ +(function(global) { + 'use strict'; + var fi = function() { + + this.cssImportStatements = []; + this.cssKeyframeStatements = []; + + this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi'); + this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together + this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)'; + this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi'); + }; + + /* + Strip outs css comments and returns cleaned css string + + @param css, the original css string to be stipped out of comments + + @return cleanedCSS contains no css comments + */ + fi.prototype.stripComments = function(cssString) { + var regex = new RegExp(this.cssCommentsRegex, 'gi'); + + return cssString.replace(regex, ''); + }; + + /* + Parses given css string, and returns css object + keys as selectors and values are css rules + eliminates all css comments before parsing + + @param source css string to be parsed + + @return object css + */ + fi.prototype.parseCSS = function(source) { + + if (source === undefined) { + return []; + } + + var css = []; + //strip out comments + //source = this.stripComments(source); + + //get import statements + + while (true) { + var imports = this.cssImportStatementRegex.exec(source); + if (imports !== null) { + this.cssImportStatements.push(imports[0]); + css.push({ + selector: '@imports', + type: 'imports', + styles: imports[0] + }); + } else { + break; + } + } + source = source.replace(this.cssImportStatementRegex, ''); + //get keyframe statements + var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi'); + var arr; + while (true) { + arr = keyframesRegex.exec(source); + if (arr === null) { + break; + } + css.push({ + selector: '@keyframes', + type: 'keyframes', + styles: arr[0] + }); + } + source = source.replace(keyframesRegex, ''); + + //unified regex + var unified = new RegExp(this.combinedCSSRegex, 'gi'); + + while (true) { + arr = unified.exec(source); + if (arr === null) { + break; + } + var selector = ''; + if (arr[2] === undefined) { + selector = arr[5].split('\r\n').join('\n').trim(); + } else { + selector = arr[2].split('\r\n').join('\n').trim(); + } + + /* + fetch comments and associate it with current selector + */ + var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi'); + var comments = commentsRegex.exec(selector); + if (comments !== null) { + selector = selector.replace(commentsRegex, '').trim(); + } + + //determine the type + if (selector.indexOf('@media') !== -1) { + //we have a media query + var cssObject = { + selector: selector, + type: 'media', + subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css + }; + if (comments !== null) { + cssObject.comments = comments[0]; + } + css.push(cssObject); + } else { + //we have standart css + var rules = this.parseRules(arr[6]); + var style = { + selector: selector, + rules: rules + }; + if (selector === '@font-face') { + style.type = 'font-face'; + } + if (comments !== null) { + style.comments = comments[0]; + } + css.push(style); + } + } + + return css; + }; + + /* + parses given string containing css directives + and returns an array of objects containing ruleName:ruleValue pairs + + @param rules, css directive string example + \n\ncolor:white;\n font-size:18px;\n + */ + fi.prototype.parseRules = function(rules) { + //convert all windows style line endings to unix style line endings + rules = rules.split('\r\n').join('\n'); + var ret = []; + + rules = rules.split(';'); + + //proccess rules line by line + for (var i = 0; i < rules.length; i++) { + var line = rules[i]; + + //determine if line is a valid css directive, ie color:white; + line = line.trim(); + if (line.indexOf(':') !== -1) { + //line contains : + line = line.split(':'); + var cssDirective = line[0].trim(); + var cssValue = line.slice(1).join(':').trim(); + + //more checks + if (cssDirective.length < 1 || cssValue.length < 1) { + continue; //there is no css directive or value that is of length 1 or 0 + // PLAIN WRONG WHAT ABOUT margin:0; ? + } + + //push rule + ret.push({ + directive: cssDirective, + value: cssValue + }); + } else { + //if there is no ':', but what if it was mis splitted value which starts with base64 + if (line.trim().substr(0, 7) == 'base64,') { //hack :) + ret[ret.length - 1].value += line.trim(); + } else { + //add rule, even if it is defective + if (line.length > 0) { + ret.push({ + directive: '', + value: line, + defective: true + }); + } + } + } + } + + return ret; //we are done! + }; + /* + just returns the rule having given directive + if not found returns false; + */ + fi.prototype.findCorrespondingRule = function(rules, directive, value) { + if (value === undefined) { + value = false; + } + var ret = false; + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive == directive) { + ret = rules[i]; + if (value === rules[i].value) { + break; + } + } + } + return ret; + }; + + /* + Finds styles that have given selector, compress them, + and returns them + */ + fi.prototype.findBySelector = function(cssObjectArray, selector, contains) { + if (contains === undefined) { + contains = false; + } + + var found = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (contains === false) { + if (cssObjectArray[i].selector === selector) { + found.push(cssObjectArray[i]); + } + } else { + if (cssObjectArray[i].selector.indexOf(selector) !== -1) { + found.push(cssObjectArray[i]); + } + } + + } + if (found.length < 2) { + return found; + } else { + var base = found[0]; + for (i = 1; i < found.length; i++) { + this.intelligentCSSPush([base], found[i]); + } + return [base]; //we are done!! all properties merged into base! + } + }; + + /* + deletes cssObjects having given selector, and returns new array + */ + fi.prototype.deleteBySelector = function(cssObjectArray, selector) { + var ret = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector !== selector) { + ret.push(cssObjectArray[i]); + } + } + return ret; + }; + + /* + Compresses given cssObjectArray and tries to minimize + selector redundence. + */ + fi.prototype.compressCSS = function(cssObjectArray) { + var compressed = []; + var done = {}; + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + if (done[obj.selector] === true) { + continue; + } + + var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed + if (found.length !== 0) { + compressed.push(found[0]); + done[obj.selector] = true; + } + } + return compressed; + }; + + /* + Received 2 css objects with following structure + { + rules : [{directive:"", value:""}, {directive:"", value:""}, ...] + selector : "SOMESELECTOR" + } + + returns the changed(new,removed,updated) values on css1 parameter, on same structure + + if two css objects are the same, then returns false + + if a css directive exists in css1 and css2, and its value is different, it is included in diff + if a css directive exists in css1 and not css2, it is then included in diff + if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED' + + @object css1 css object + @object css2 css object + + @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js + */ + fi.prototype.cssDiff = function(css1, css2) { + if (css1.selector !== css2.selector) { + return false; + } + + //if one of them is media query return false, because diff function can not operate on media queries + if ((css1.type === 'media' || css2.type === 'media')) { + return false; + } + + var diff = { + selector: css1.selector, + rules: [] + }; + var rule1, rule2; + for (var i = 0; i < css1.rules.length; i++) { + rule1 = css1.rules[i]; + //find rule2 which has the same directive as rule1 + rule2 = this.findCorrespondingRule(css2.rules, rule1.directive, rule1.value); + if (rule2 === false) { + //rule1 is a new rule in css1 + diff.rules.push(rule1); + } else { + //rule2 was found only push if its value is different too + if (rule1.value !== rule2.value) { + diff.rules.push(rule1); + } + } + } + + //now for rules exists in css2 but not in css1, which means deleted rules + for (var ii = 0; ii < css2.rules.length; ii++) { + rule2 = css2.rules[ii]; + //find rule2 which has the same directive as rule1 + rule1 = this.findCorrespondingRule(css1.rules, rule2.directive); + if (rule1 === false) { + //rule1 is a new rule + rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true + diff.rules.push(rule2); + } + } + + + if (diff.rules.length === 0) { + return false; + } + return diff; + }; + + /* + Merges 2 different css objects together + using intelligentCSSPush, + + @param cssObjectArray, target css object array + @param newArray, source array that will be pushed into cssObjectArray parameter + @param reverse, [optional], if given true, first parameter will be traversed on reversed order + effectively giving priority to the styles in newArray + */ + fi.prototype.intelligentMerge = function(cssObjectArray, newArray, reverse) { + if (reverse === undefined) { + reverse = false; + } + + + for (var i = 0; i < newArray.length; i++) { + this.intelligentCSSPush(cssObjectArray, newArray[i], reverse); + } + for (i = 0; i < cssObjectArray.length; i++) { + var cobj = cssObjectArray[i]; + if (cobj.type === 'media' || (cobj.type === 'keyframes')) { + continue; + } + cobj.rules = this.compactRules(cobj.rules); + } + }; + + /* + inserts new css objects into a bigger css object + with same selectors groupped together + + @param cssObjectArray, array of bigger css object to be pushed into + @param minimalObject, single css object + @param reverse [optional] default is false, if given, cssObjectArray will be reversly traversed + resulting more priority in minimalObject's styles + */ + fi.prototype.intelligentCSSPush = function(cssObjectArray, minimalObject, reverse) { + var pushSelector = minimalObject.selector; + //find correct selector if not found just push minimalObject into cssObject + var cssObject = false; + + if (reverse === undefined) { + reverse = false; + } + + if (reverse === false) { + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector === minimalObject.selector) { + cssObject = cssObjectArray[i]; + break; + } + } + } else { + for (var j = cssObjectArray.length - 1; j > -1; j--) { + if (cssObjectArray[j].selector === minimalObject.selector) { + cssObject = cssObjectArray[j]; + break; + } + } + } + + if (cssObject === false) { + cssObjectArray.push(minimalObject); //just push, because cssSelector is new + } else { + if (minimalObject.type !== 'media') { + for (var ii = 0; ii < minimalObject.rules.length; ii++) { + var rule = minimalObject.rules[ii]; + //find rule inside cssObject + var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive); + if (oldRule === false) { + cssObject.rules.push(rule); + } else if (rule.type == 'DELETED') { + oldRule.type = 'DELETED'; + } else { + //rule found just update value + + oldRule.value = rule.value; + } + } + } else { + cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too + } + + } + }; + + /* + filter outs rule objects whose type param equal to DELETED + + @param rules, array of rules + + @returns rules array, compacted by deleting all unneccessary rules + */ + fi.prototype.compactRules = function(rules) { + var newRules = []; + for (var i = 0; i < rules.length; i++) { + if (rules[i].type !== 'DELETED') { + newRules.push(rules[i]); + } + } + return newRules; + }; + /* + computes string for ace editor using this.css or given cssBase optional parameter + + @param [optional] cssBase, if given computes cssString from cssObject array + */ + fi.prototype.getCSSForEditor = function(cssBase, depth) { + if (depth === undefined) { + depth = 0; + } + var ret = ''; + if (cssBase === undefined) { + cssBase = this.css; + } + //append imports + for (var i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'imports') { + ret += cssBase[i].styles + '\n\n'; + } + } + for (i = 0; i < cssBase.length; i++) { + var tmp = cssBase[i]; + if (tmp.selector === undefined) { //temporarily omit media queries + continue; + } + var comments = ""; + if (tmp.comments !== undefined) { + comments = tmp.comments + '\n'; + } + + if (tmp.type == 'media') { //also put media queries to output + ret += comments + tmp.selector + '{\n'; + ret += this.getCSSForEditor(tmp.subStyles, depth + 1); + ret += '}\n\n'; + } else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') { + ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n'; + ret += this.getCSSOfRules(tmp.rules, depth + 1); + ret += this.getSpaces(depth) + '}\n\n'; + } + } + + //append keyFrames + for (i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'keyframes') { + ret += cssBase[i].styles + '\n\n'; + } + } + + return ret; + }; + + fi.prototype.getImports = function(cssObjectArray) { + var imps = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].type == 'imports') { + imps.push(cssObjectArray[i].styles); + } + } + return imps; + }; + /* + given rules array, returns visually formatted css string + to be used inside editor + */ + fi.prototype.getCSSOfRules = function(rules, depth) { + var ret = ''; + for (var i = 0; i < rules.length; i++) { + if (rules[i] === undefined) { + continue; + } + if (rules[i].defective === undefined) { + ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n'; + } else { + ret += this.getSpaces(depth) + rules[i].value + ';\n'; + } + + } + return ret || '\n'; + }; + + /* + A very simple helper function returns number of spaces appended in a single string, + the number depends input parameter, namely input*2 + */ + fi.prototype.getSpaces = function(num) { + var ret = ''; + for (var i = 0; i < num * 4; i++) { + ret += ' '; + } + return ret; + }; + + /* + Given css string or objectArray, parses it and then for every selector, + prepends this.cssPreviewNamespace to prevent css collision issues + + @returns css string in which this.cssPreviewNamespace prepended + */ + fi.prototype.applyNamespacing = function(css, forcedNamespace) { + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if(forcedNamespace !== undefined){ + namespaceClass = forcedNamespace; + } + + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + //bypass namespacing for @font-face @keyframes @import + if(obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1){ + continue; + } + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova + newSelector.push(namespaceClass + ' ' + selector[j]); + } else { + newSelector.push(selector[j]); + } + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well + } + } + + return cssObjectArray; + }; + + /* + given css string or object array, clears possible namespacing from + all of the selectors inside the css + */ + fi.prototype.clearNamespacing = function(css, returnObj) { + if (returnObj === undefined) { + returnObj = false; + } + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + newSelector.push(selector[j].split(namespaceClass + ' ').join('')); + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well + } + } + if (returnObj === false) { + return this.getCSSForEditor(cssObjectArray); + } else { + return cssObjectArray; + } + + }; + + /* + creates a new style tag (also destroys the previous one) + and injects given css string into that css tag + */ + fi.prototype.createStyleElement = function(id, css, format) { + if (format === undefined) { + format = false; + } + + if (this.testMode === false && format!=='nonamespace') { + //apply namespacing classes + css = this.applyNamespacing(css); + } + + if (typeof css != 'string') { + css = this.getCSSForEditor(css); + } + //apply formatting for css + if (format === true) { + css = this.getCSSForEditor(this.parseCSS(css)); + } + + if (this.testMode !== false) { + return this.testMode('create style #' + id, css); //if test mode, just pass result to callback + } + + var __el = document.getElementById( id ); + if(__el){ + __el.parentNode.removeChild( __el ); + } + + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'); + + style.id = id; + style.type = 'text/css'; + + head.appendChild(style); + + if (style.styleSheet && !style.sheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + }; + + global.cssjs = fi; + +})(this); diff --git a/Plugins/package.json b/Plugins/package.json deleted file mode 100644 index 9f3a5c9d..00000000 --- a/Plugins/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "VorlonJS", - "version": "1.0.0", - "description": "", - "main": "", - "devDependencies": { - "gulp": "^3.6.2", - "gulp-concat": "^2.2.0", - "gulp-filter": "^0.4.1", - "gulp-rename": "^1.2.0", - "gulp-typescript" : "2.4.0", - "gulp-sourcemaps" : "1.3.0", - "gulp-uglify": "^0.3.0", - "gulp-util": "^2.2.14", - "through": "^2.3.4" - } -} diff --git a/Plugins/samples/bestpractices.css b/Plugins/samples/bestpractices.css new file mode 100644 index 00000000..2a4a8ab0 --- /dev/null +++ b/Plugins/samples/bestpractices.css @@ -0,0 +1,49 @@ +body { + margin: 0; + padding: 50px; + padding-bottom: 100px; + padding-top: 10px; + font-family: Helvetica, Arial, sans-serif; + color: #666; + } + + h1, h2 { + margin-bottom: 0px; + margin-top: 40px; + } + + h1, h2, a, a:visited { + color: #6C2983; + } + + button{ + display: inline-block; + margin-right : 5px; + margin-bottom: 5px; + background-color: #6C2983; + color : white; + border : none; + padding : 0.5em 1em; + } + + #test { + font-size: 18px; + } + + .class1 { + color: coral; + -webkit-transform : scale(1); + } + + #footer { + background: rgba(220, 220, 220, 0.9); + position: fixed; + bottom: 0; + left: 0; + width: 100%; + text-align: center; + padding: 20px; + -webkit-transform : scale(1); + transform : scale(1); + transition : opacity 200ms ease-out; + } diff --git a/Plugins/samples/bestpractices.html b/Plugins/samples/bestpractices.html new file mode 100644 index 00000000..35c56a43 --- /dev/null +++ b/Plugins/samples/bestpractices.html @@ -0,0 +1,51 @@ + + + + Vorlon.js - Test page + + + + + + + + + +

      Vorlon.JS Best practices Test Page

      + +

      + This test page includes the vorlon client script. Ensure you are running the Vorlon.JS server, as explained in the Vorlon.JS docs. Then open the Vorlon.JS dashboard and refresh this page. +

      +
      + +
      +

      + This page is intended to showcase various best practices checks for the "Best Practices" plugin in Vorlon. +

      +

      + +

      + + + +

      You are not using Internet Explorer 5-9.

      + + + + + + diff --git a/Plugins/samples/index.css b/Plugins/samples/index.css new file mode 100644 index 00000000..1e013681 --- /dev/null +++ b/Plugins/samples/index.css @@ -0,0 +1,75 @@ +body { + margin: 0; + padding: 50px; + padding-bottom: 100px; + padding-top: 10px; + font-family: Helvetica, Arial, sans-serif; + color: #666; + -webkit-transition: ease; + -webkit-padding-start: 10px; + } + + h1, h2 { + margin-bottom: 0px; + margin-top: 40px; + } + + h1, h2, a, a:visited { + color: #6C2983; + } + + button{ + display: inline-block; + margin-right : 5px; + margin-bottom: 5px; + background-color: #6C2983; + color : white; + border : none; + padding : 0.5em 1em; + } + + #test { + font-size: 18px; + } + + .class1 { + color: coral; + -webkit-transform : scale(1); + } + + #footer { + background: rgba(220, 220, 220, 0.9); + position: fixed; + bottom: 0; + left: 0; + width: 100%; + text-align: center; + padding: 20px; + -webkit-transform : scale(1); + transform : scale(1); + transition : opacity 200ms ease-out; + } + +@media screen and (min-width :1280px){ + body { + padding : 20px 80px 80px 80px; + } +} + +@media screen and (max-width : 800px){ + body { + padding : 0 20px 70px 20px; + } + + #footer{ + transform : translate(0,0); + } +} + +@media screen and (max-width : 500px){ + button{ + display: block; + margin-right: 0; + width : 100%; + } +} \ No newline at end of file diff --git a/Plugins/samples/index.html b/Plugins/samples/index.html index 461c4e88..262022ff 100644 --- a/Plugins/samples/index.html +++ b/Plugins/samples/index.html @@ -1,44 +1,15 @@  - + Vorlon.js - Test page - + + + @@ -65,7 +36,8 @@

      Console Logging

      Dom Explorer

      You can inspect the DOM of this page from the Vorlon.JS dashboard. Here is some html with inline styles, and css styles applied.

      - + +

      This text is styled by css

      @@ -91,11 +63,23 @@

      Network

      } +

      unitTest

      +

      You can run this QUnit test in dashboard

      +
      +

      + QUnit.test( "Sample test", function( assert ) { + assert.ok( document.getElementById("sampleInput").value == "42", "Passed!" ); + }); +

      +
      + +

      web standards and best practices

      +

      This block is for testing several aspects of web standards and best practice checks. Open the best practices sample to check more rules

      - + diff --git a/Plugins/samples/unitTestRunner.html b/Plugins/samples/unitTestRunner.html new file mode 100644 index 00000000..1fbba3ae --- /dev/null +++ b/Plugins/samples/unitTestRunner.html @@ -0,0 +1,31 @@ + + + + Vorlon.js - Test page + + + + + +

      Vorlon.JS Test Page

      + +

      + This test page includes the vorlon client script. Ensure you are running the Vorlon.JS server, as explained in the Vorlon.JS docs. Then open the Vorlon.JS dashboard and refresh this page. +

      + + +

      Unit Test

      +

      You can run this QUnit test against this page: +
      +

      +	QUnit.test( "Sample test", function( assert ) {
      +		assert.ok( document.getElementById("sampleInput").value == "42", "Passed!" );
      +	});
      +	
      +

      +
      + Is this value equals to 42? + +
      + + diff --git a/README.md b/README.md index 3ad9b398..21248193 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,25 @@ If you want to run the server with SSL support proceed as follows: 5. If you want to replace our localhost certificate should only change the path of the files with the private key and certificate 6. Exit and save JSON file +## SSL Support on Azure +1. Navigate to the installation folder +2. Modify JSON file for activation SSLAzure support +3. In JSON file set to true +4. Exit and save JSON file +5. Navigate with https protole on your Azure WebSite + +## Custom log file +By default Vorlon.JS application logs with debug level and files are stored in the installation folder. +If you want to customize logs, proceed as follows : +1. Navigate to the installation folder +2. Modify JSON file, add or edit the "logs" section : + - enableConsole : enabled logging to the console, + - level : allowed values : info, warn, error + - filePath : folder where log files should be store + - vorlonLogFileName : name of Vorlon.JS log file, + - exceptionsLogFileName : name of the log files for exceptions +3. Exit and save JSON file + ```console [Windows] C:\>cd %HOMEPATH%\node_modules\vorlon @@ -68,7 +87,14 @@ C:\Users\Username\node_modules\vorlon>notepad Server/config.json { "id" : "OBJEXPLORER", "name" : "Obj. Explorer","panel": "top", "foldername" : "objectExplorer", "enabled": true }, { "id" : "XHRPANEL", "name" : "XHR","panel": "top", "foldername" : "xhrPanel", "enabled": true }, { "id" : "NGINSPECTOR", "name" : "ngInspector","panel": "top", "foldername" : "ngInspector", "enabled": false } - ] + ], + "logs": { + "level" : "info", + "enableConsole" : true, + "filePath" : "E:\\temp", + "vorlonLogFileName": "vorlonjs.log", + "exceptionsLogFileName": "exceptions.log" + } } C:\Users\Username\node_modules\vorlon>vorlon diff --git a/Server/.gitignore b/Server/.gitignore index 16c238c3..8737d242 100644 --- a/Server/.gitignore +++ b/Server/.gitignore @@ -1,4 +1,5 @@ .log +.vscode/ Scripts/**/*.js Scripts/**/*.js.map config/**/*.js @@ -9,3 +10,7 @@ public/vorlon.dashboardManager.js public/vorlon.dashboardManager.js.map public/vorlon/**/* public/stylesheets/**/*.css + + +Scripts/typings/vorlon/**/*.d.ts +public/stylesheets/style.css \ No newline at end of file diff --git a/Server/Scripts/typings/socket.io/socket.io.d.ts b/Server/Scripts/typings/socket.io/socket.io.d.ts index b2db0f4c..e22af5bf 100644 --- a/Server/Scripts/typings/socket.io/socket.io.d.ts +++ b/Server/Scripts/typings/socket.io/socket.io.d.ts @@ -1,84 +1,824 @@ -// Type definitions for socket.io 1.2.0 +// Type definitions for socket.io 1.3.5 // Project: http://socket.io/ -// Definitions by: PROGRE +// Definitions by: PROGRE , Damian Connolly // Definitions: https://github.com/borisyankov/DefinitelyTyped /// declare module 'socket.io' { - var server: SocketIOStatic; + var server: SocketIOStatic; - export = server; + export = server; } interface SocketIOStatic { - (): SocketIO.Server; - (srv: any, opts?: any): SocketIO.Server; - (port: number, opts?: any): SocketIO.Server; - (opts: any): SocketIO.Server; + /** + * Default Server constructor + */ + (): SocketIO.Server; + + /** + * Creates a new Server + * @param srv The HTTP server that we're going to bind to + * @param opts An optional parameters object + */ + (srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server; + + /** + * Creates a new Server + * @param port A port to bind to, as a number, or a string + * @param An optional parameters object + */ + (port: string|number, opts?: SocketIO.ServerOptions): SocketIO.Server; + + /** + * Creates a new Server + * @param A parameters object + */ + (opts: SocketIO.ServerOptions): SocketIO.Server; + /** + * Backwards compatibility + * @see io().listen() + */ listen: SocketIOStatic; } declare module SocketIO { - interface Server { - serveClient(v: boolean): Server; - path(v: string): Server; - adapter(v: any): Server; - origins(v: string): Server; - sockets: Namespace; - attach(srv: any, opts?: any): Server; - attach(port: number, opts?: any): Server; - listen(srv: any, opts?: any): Server; - listen(port: number, opts?: any): Server; - bind(srv: any): Server; - onconnection(socket: any): Server; - of(nsp: string): Namespace; - emit(name: string, ...args: any[]): Socket; - use(fn: Function): Namespace; + + interface Server { + + /** + * A dictionary of all the namespaces currently on this Server + */ + nsps: {[namespace: string]: Namespace}; + + /** + * The default '/' Namespace + */ + sockets: Namespace; + + /** + * Sets the 'json' flag when emitting an event + */ + json: Server; + + /** + * Server request verification function, that checks for allowed origins + * @param req The http.IncomingMessage request + * @param fn The callback to be called. It should take one parameter, err, + * which will be null if there was no problem, and one parameter, success, + * of type boolean + */ + checkRequest( req:any, fn:( err: any, success: boolean ) => void ):void; + + /** + * Gets whether we're serving the client.js file or not + * @default true + */ + serveClient(): boolean; + + /** + * Sets whether we're serving the client.js file or not + * @param v True if we want to serve the file, false otherwise + * @default true + * @return This Server + */ + serveClient( v: boolean ): Server; + + /** + * Gets the client serving path + * @default '/socket.io' + */ + path(): string; + + /** + * Sets the client serving path + * @param v The path to serve the client file on + * @default '/socket.io' + * @return This Server + */ + path( v: string ): Server; + + /** + * Gets the adapter that we're going to use for handling rooms + * @default typeof Adapter + */ + adapter(): any; + + /** + * Sets the adapter (class) that we're going to use for handling rooms + * @param v The class for the adapter to create + * @default typeof Adapter + * @return This Server + */ + adapter( v: any ): Server; + + /** + * Gets the allowed origins for requests + * @default "*:*" + */ + origins(): string; + + /** + * Sets the allowed origins for requests + * @param v The allowed origins, in host:port form + * @default "*:*" + * return This Server + */ + origins( v: string ): Server; + + /** + * Attaches socket.io to a server + * @param srv The http.Server that we want to attach to + * @param opts An optional parameters object + * @return This Server + */ + attach( srv: any, opts?: ServerOptions ): Server; + + /** + * Attaches socket.io to a port + * @param port The port that we want to attach to + * @param opts An optional parameters object + * @return This Server + */ + attach( port: number, opts?: ServerOptions ): Server; + + /** + * @see attach( srv, opts ) + */ + listen( srv: any, opts?: ServerOptions ): Server; + + /** + * @see attach( port, opts ) + */ + listen( port: number, opts?: ServerOptions ): Server; + + /** + * Binds socket.io to an engine.io intsance + * @param src The Engine.io (or compatible) server to bind to + * @return This Server + */ + bind( srv: any ): Server; + + /** + * Called with each incoming connection + * @param socket The Engine.io Socket + * @return This Server + */ + onconnection( socket: any ): Server; + + /** + * Looks up/creates a Namespace + * @param nsp The name of the NameSpace to look up/create. Should start + * with a '/' + * @return The Namespace + */ + of( nsp: string ): Namespace; + + /** + * Closes the server connection + */ + close():void; - on(event: 'connection', listener: (socket: Socket) => void): Namespace; - on(event: 'connect', listener: (socket: Socket) => void): Namespace; - on(event: string, listener: Function): Namespace; - } + /** + * The event fired when we get a new connection + * @param event The event being fired: 'connection' + * @param listener A listener that should take one parameter of type Socket + * @return The default '/' Namespace + */ + on( event: 'connection', listener: ( socket: Socket ) => void ): Namespace; + + /** + * @see on( 'connection', listener ) + */ + on( event: 'connect', listener: ( socket: Socket ) => void ): Namespace; + + /** + * Base 'on' method to add a listener for an event + * @param event The event that we want to add a listener for + * @param listener The callback to call when we get the event. The parameters + * for the callback depend on the event + * @return The default '/' Namespace + */ + on( event: string, listener: Function ): Namespace; + + /** + * Targets a room when emitting to the default '/' Namespace + * @param room The name of the room that we're targeting + * @return The default '/' Namespace + */ + to( room: string ): Namespace; + + /** + * @see to( room ) + */ + in( room: string ): Namespace; + + /** + * Registers a middleware function, which is a function that gets executed + * for every incoming Socket, on the default '/' Namespace + * @param fn The function to call when we get a new incoming socket. It should + * take one parameter of type Socket, and one callback function to call to + * execute the next middleware function. The callback can take one optional + * parameter, err, if there was an error. Errors passed to middleware callbacks + * are sent as special 'error' packets to clients + * @return The default '/' Namespace + */ + use( fn: ( socket:Socket, fn: ( err?: any ) => void ) =>void ): Namespace; + + /** + * Emits an event to the default Namespace + * @param event The event that we want to emit + * @param args Any number of optional arguments to pass with the event. If the + * last argument is a function, it will be called as an ack. The ack should + * take whatever data was sent with the packet + * @return The default '/' Namespace + */ + emit( event: string, ...args: any[]): Namespace; + + /** + * Sends a 'message' event + * @see emit( event, ...args ) + * @return The default '/' Namespace + */ + send( ...args: any[] ): Namespace; + + /** + * @see send( ...args ) + */ + write( ...args: any[] ): Namespace; + } + + /** + * Options to pass to our server when creating it + */ + interface ServerOptions { + + /** + * The path to server the client file to + * @default '/socket.io' + */ + path?: string; + + /** + * Should we serve the client file? + * @default true + */ + serveClient?: boolean; + + /** + * The adapter to use for handling rooms. NOTE: this should be a class, + * not an object + * @default typeof Adapter + */ + adapter?: Adapter; + + /** + * Accepted origins + * @default '*:*' + */ + origins?: string; + + /** + * How many milliseconds without a pong packed to consider the connection closed (engine.io) + * @default 60000 + */ + pingTimeout?: number; + + /** + * How many milliseconds before sending a new ping packet (keep-alive) (engine.io) + * @default 25000 + */ + pingInterval?: number; + + /** + * How many bytes or characters a message can be when polling, before closing the session + * (to avoid Dos) (engine.io) + * @default 10E7 + */ + maxHttpBufferSize?: number; + + /** + * A function that receives a given handshake or upgrade request as its first parameter, + * and can decide whether to continue or not. The second argument is a function that needs + * to be called with the decided information: fn( err, success ), where success is a boolean + * value where false means that the request is rejected, and err is an error code (engine.io) + * @default null + */ + allowRequest?: (request:any, callback: (err: number, success: boolean) => void) => void; + + /** + * Transports to allow connections to (engine.io) + * @default ['polling','websocket'] + */ + transports?: string[]; + + /** + * Whether to allow transport upgrades (engine.io) + * @default true + */ + allowUpgrades?: boolean; + + /** + * parameters of the WebSocket permessage-deflate extension (see ws module). + * Set to false to disable (engine.io) + * @default true + */ + perMessageDeflate?: Object|boolean; + + /** + * Parameters of the http compression for the polling transports (see zlib). + * Set to false to disable, or set an object with parameter "threshold:number" + * to only compress data if the byte size is above this value (1024) (engine.io) + * @default true|1024 + */ + httpCompression?: Object|boolean; + + /** + * Name of the HTTP cookie that contains the client sid to send as part of + * handshake response headers. Set to false to not send one (engine.io) + * @default "io" + */ + cookie?: string|boolean; + } - interface Namespace extends NodeJS.EventEmitter { - name: string; - connected: { [id: string]: Socket }; - use(fn: Function): Namespace; + /** + * The Namespace, sandboxed environments for sockets, each connection + * to a Namespace requires a new Socket + */ + interface Namespace extends NodeJS.EventEmitter { + + /** + * The name of the NameSpace + */ + name: string; + + /** + * The controller Server for this Namespace + */ + server: Server; + + /** + * A list of all the Sockets connected to this Namespace + */ + sockets: Socket[]; + + /** + * A dictionary of all the Sockets connected to this Namespace, where + * the Socket ID is the key + */ + connected: { [id: string]: Socket }; + + /** + * The Adapter that we're using to handle dealing with rooms etc + */ + adapter: Adapter; + + /** + * Sets the 'json' flag when emitting an event + */ + json: Namespace; + + /** + * Registers a middleware function, which is a function that gets executed + * for every incoming Socket + * @param fn The function to call when we get a new incoming socket. It should + * take one parameter of type Socket, and one callback function to call to + * execute the next middleware function. The callback can take one optional + * parameter, err, if there was an error. Errors passed to middleware callbacks + * are sent as special 'error' packets to clients + * @return This Namespace + */ + use( fn: ( socket:Socket, fn: ( err?: any ) => void ) =>void ): Namespace; + + /** + * Targets a room when emitting + * @param room The name of the room that we're targeting + * @return This Namespace + */ + to( room: string ): Namespace; + + /** + * @see to( room ) + */ + in( room: string ): Namespace; + + /** + * Sends a 'message' event + * @see emit( event, ...args ) + * @return This Namespace + */ + send( ...args: any[] ): Namespace; + + /** + * @see send( ...args ) + */ + write( ...args: any[] ): Namespace; - on(event: 'connection', listener: (socket: Socket) => void): Namespace; - on(event: 'connect', listener: (socket: Socket) => void): Namespace; - on(event: string, listener: Function): Namespace; - } + /** + * The event fired when we get a new connection + * @param event The event being fired: 'connection' + * @param listener A listener that should take one parameter of type Socket + * @return This Namespace + */ + on( event: 'connection', listener: ( socket: Socket ) => void ): Namespace; + + /** + * @see on( 'connection', listener ) + */ + on( event: 'connect', listener: ( socket: Socket ) => void ): Namespace; + + /** + * Base 'on' method to add a listener for an event + * @param event The event that we want to add a listener for + * @param listener The callback to call when we get the event. The parameters + * for the callback depend on the event + * @ This Namespace + */ + on( event: string, listener: Function ): Namespace; + } - interface Socket { - rooms: string[]; - client: Client; - conn: Socket; - request: any; - id: string; - emit(name: string, ...args: any[]): Socket; - join(name: string, fn?: Function): Socket; - leave(name: string, fn?: Function): Socket; - to(room: string): Socket; - in(room: string): Socket; - send(...args: any[]): Socket; - write(...args: any[]): Socket; + /** + * The socket, which handles our connection for a namespace. NOTE: while + * we technically extend NodeJS.EventEmitter, we're not putting it here + * as we have a problem with the emit() event (as it's overridden with a + * different return) + */ + interface Socket { + + /** + * The namespace that this socket is for + */ + nsp: Namespace; + + /** + * The Server that our namespace is in + */ + server: Server; + + /** + * The Adapter that we use to handle our rooms + */ + adapter: Adapter; + + /** + * The unique ID for this Socket. Regenerated at every connection. This is + * also the name of the room that the Socket automatically joins on connection + */ + id: string; + + /** + * The http.IncomingMessage request sent with the connection. Useful + * for recovering headers etc + */ + request: any; + + /** + * The Client associated with this Socket + */ + client: Client; + + /** + * The underlying Engine.io Socket instance + */ + conn: { + + /** + * The ID for this socket - matches Client.id + */ + id: string; + + /** + * The Engine.io Server for this socket + */ + server: any; + + /** + * The ready state for the client. Either 'opening', 'open', 'closing', or 'closed' + */ + readyState: string; + + /** + * The remote IP for this connection + */ + remoteAddress: string; + }; + + /** + * The list of rooms that this Socket is currently in + */ + rooms: string[]; + + /** + * Is the Socket currently connected? + */ + connected: boolean; + + /** + * Is the Socket currently disconnected? + */ + disconnected: boolean; + + /** + * The object used when negociating the handshake + */ + handshake: { + /** + * The headers passed along with the request. e.g. 'host', + * 'connection', 'accept', 'referer', 'cookie' + */ + headers: any; + + /** + * The current time, as a string + */ + time: string; + + /** + * The remote address of the connection request + */ + address: string; + + /** + * Is this a cross-domain request? + */ + xdomain: boolean; + + /** + * Is this a secure request? + */ + secure: boolean; + + /** + * The timestamp for when this was issued + */ + issued: number; + + /** + * The request url + */ + url: string; + + /** + * Any query string parameters in the request url + */ + query: any; + }; + + /** + * Sets the 'json' flag when emitting an event + */ + json: Socket; + + /** + * Sets the 'volatile' flag when emitting an event. Volatile messages are + * messages that can be dropped because of network issues and the like. Use + * for high-volume/real-time messages where you don't need to receive *all* + * of them + */ + volatile: Socket; + + /** + * Sets the 'broadcast' flag when emitting an event. Broadcasting an event + * will send it to all the other sockets in the namespace except for yourself + */ + broadcast: Socket; + + /** + * Emits an event to this client. If the 'broadcast' flag was set, this will + * emit to all other clients, except for this one + * @param event The event that we want to emit + * @param args Any number of optional arguments to pass with the event. If the + * last argument is a function, it will be called as an ack. The ack should + * take whatever data was sent with the packet + * @return This Socket + */ + emit( event: string, ...args: any[]): Socket; + + /** + * Targets a room when broadcasting + * @param room The name of the room that we're targeting + * @return This Socket + */ + to( room: string ): Socket; + + /** + * @see to( room ) + */ + in( room: string ): Socket; + + /** + * Sends a 'message' event + * @see emit( event, ...args ) + */ + send( ...args: any[] ): Socket; + + /** + * @see send( ...args ) + */ + write( ...args: any[] ): Socket; + + /** + * Joins a room. You can join multiple rooms, and by default, on connection, + * you join a room with the same name as your ID + * @param name The name of the room that we want to join + * @param fn An optional callback to call when we've joined the room. It should + * take an optional parameter, err, of a possible error + * @return This Socket + */ + join( name: string, fn?: ( err?: any ) => void ): Socket; + + /** + * Leaves a room + * @param name The name of the room to leave + * @param fn An optional callback to call when we've left the room. It should + * take on optional parameter, err, of a possible error + */ + leave( name: string, fn?: Function ): Socket; + + /** + * Leaves all the rooms that we've joined + */ + leaveAll(): void; + + /** + * Disconnects this Socket + * @param close If true, also closes the underlying connection + * @return This Socket + */ + disconnect( close: boolean ): Socket; + + /** + * Adds a listener for a particular event. Calling multiple times will add + * multiple listeners + * @param event The event that we're listening for + * @param fn The function to call when we get the event. Parameters depend on the + * event in question + * @return This Socket + */ + on( event: string, fn: Function ): Socket; + + /** + * @see on( event, fn ) + */ + addListener( event: string, fn: Function ): Socket; + + /** + * Adds a listener for a particular event that will be invoked + * a single time before being automatically removed + * @param event The event that we're listening for + * @param fn The function to call when we get the event. Parameters depend on + * the event in question + * @return This Socket + */ + once( event: string, fn: Function ): Socket; + + /** + * Removes a listener for a particular type of event. This will either + * remove a specific listener, or all listeners for this type of event + * @param event The event that we want to remove the listener of + * @param fn The function to remove, or null if we want to remove all functions + * @return This Socket + */ + removeListener( event: string, fn?: Function ): Socket; + + /** + * Removes all event listeners on this object + * @return This Socket + */ + removeAllListeners( event?: string ): Socket; + + /** + * Sets the maximum number of listeners this instance can have + * @param n The max number of listeners we can add to this emitter + * @return This Socket + */ + setMaxListeners( n: number ): Socket; + + /** + * Returns all the callbacks for a particular event + * @param event The event that we're looking for the callbacks of + * @return An array of callback Functions, or an empty array if we don't have any + */ + listeners( event: string ):Function[]; + } + + /** + * The interface used when dealing with rooms etc + */ + interface Adapter extends NodeJS.EventEmitter { + + /** + * The namespace that this adapter is for + */ + nsp: Namespace; + + /** + * A dictionary of all the rooms that we have in this namespace, each room + * a dictionary of all the sockets currently in that room + */ + rooms: {[room: string]: {[id: string]: boolean }}; + + /** + * A dictionary of all the socket ids that we're dealing with, and all + * the rooms that the socket is currently in + */ + sids: {[id: string]: {[room: string]: boolean}}; + + /** + * Adds a socket to a room. If the room doesn't exist, it's created + * @param id The ID of the socket to add + * @param room The name of the room to add the socket to + * @param callback An optional callback to call when the socket has been + * added. It should take an optional parameter, error, if there was a problem + */ + add( id: string, room: string, callback?: ( err?: any ) => void ): void; + + /** + * Removes a socket from a room. If there are no more sockets in the room, + * the room is deleted + * @param id The ID of the socket that we're removing + * @param room The name of the room to remove the socket from + * @param callback An optional callback to call when the socket has been + * removed. It should take on optional parameter, error, if there was a problem + */ + del( id: string, room: string, callback?: ( err?: any ) => void ): void; + + /** + * Removes a socket from all the rooms that it's joined + * @param id The ID of the socket that we're removing + */ + delAll( id: string ):void; + + /** + * Broadcasts a packet + * @param packet The packet to broadcast + * @param opts Any options to send along: + * - rooms: An optional list of rooms to broadcast to. If empty, the packet is broadcast to all sockets + * - except: A list of Socket IDs to exclude + * - flags: Any flags that we want to send along ('json', 'volatile', 'broadcast') + */ + broadcast( packet: any, opts: { rooms?: string[]; except?: string[]; flags?: {[flag: string]: boolean} } ):void; + } - on(event: string, listener: Function): Socket; - once(event: string, listener: Function): Socket; - removeListener(event: string, listener: Function): Socket; - removeAllListeners(event: string): Socket; - broadcast: Socket; - volatile: Socket; - connected: boolean; - disconnect(close?: boolean): Socket; - } - - interface Client { - conn: any; - request: any; - } + /** + * The client behind each socket (can have multiple sockets) + */ + interface Client { + /** + * The Server that this client belongs to + */ + server: Server; + + /** + * The underlying Engine.io Socket instance + */ + conn: { + + /** + * The ID for this socket - matches Client.id + */ + id: string; + + /** + * The Engine.io Server for this socket + */ + server: any; + + /** + * The ready state for the client. Either 'opening', 'open', 'closing', or 'closed' + */ + readyState: string; + + /** + * The remote IP for this connection + */ + remoteAddress: string; + }; + + /** + * The ID for this client. Regenerated at every connection + */ + id: string; + + /** + * The http.IncomingMessage request sent with the connection. Useful + * for recovering headers etc + */ + request: any; + + /** + * The list of sockets currently connect via this client (i.e. to different + * namespaces) + */ + sockets: Socket[]; + + /** + * A dictionary of all the namespaces for this client, with the Socket that + * deals with that namespace + */ + nsps: {[nsp: string]: Socket}; + } } diff --git a/Server/Scripts/typings/vorlon/plugins/domExplorer/vorlon.domExplorer.d.ts b/Server/Scripts/typings/vorlon/plugins/domExplorer/vorlon.domExplorer.d.ts deleted file mode 100644 index 61eb8e20..00000000 --- a/Server/Scripts/typings/vorlon/plugins/domExplorer/vorlon.domExplorer.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -declare module VORLON { - class DOMExplorer extends Plugin { - private _previousSelectedNode; - private _internalId; - private _lastElementSelectedClientSide; - private _newAppliedStyles; - private _lastContentState; - private _lastReceivedObject; - private _clikedNodeID; - constructor(); - getID(): string; - private _getAppliedStyles(node); - private _packageNode(node); - private _packageDOM(root, packagedObject, withChildsNodes?); - private _packageAndSendDOM(element?); - private _markForRefresh(); - startClientSide(): void; - private _getElementByInternalId(internalId, node); - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - refresh(): void; - refreshbyId(internaID: any): void; - private _containerDiv; - private _treeDiv; - private _styleView; - private _dashboardDiv; - private _refreshButton; - startDashboardSide(div?: HTMLDivElement): void; - private _makeEditable(element); - private _generateClickableValue(label, value, internalId); - private _generateStyle(property, value, internalId, editableLabel?); - private _generateStyles(styles, internalId); - private _appendSpan(parent, className, value); - private _generateColorfullLink(link, receivedObject); - private _generateColorfullClosingLink(link, receivedObject); - private _generateButton(parentNode, text, className, attribute?); - private _spaceCheck; - private _generateTreeNode(parentNode, receivedObject, first?); - private _insertReceivedObject(receivedObject, root); - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - } -} diff --git a/Server/Scripts/typings/vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.d.ts b/Server/Scripts/typings/vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.d.ts deleted file mode 100644 index b23e0298..00000000 --- a/Server/Scripts/typings/vorlon/plugins/interactiveConsole/vorlon.interactiveConsole.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module VORLON { - class InteractiveConsole extends Plugin { - _cache: any[]; - constructor(); - startClientSide(): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - refresh(): void; - private _containerDiv; - private _interactiveInput; - private _commandIndex; - private _commandHistory; - startDashboardSide(div?: HTMLDivElement): void; - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - } -} diff --git a/Server/Scripts/typings/vorlon/plugins/modernizrReport/vorlon.modernizrReport.d.ts b/Server/Scripts/typings/vorlon/plugins/modernizrReport/vorlon.modernizrReport.d.ts deleted file mode 100644 index 10e4a480..00000000 --- a/Server/Scripts/typings/vorlon/plugins/modernizrReport/vorlon.modernizrReport.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -declare module VORLON { - class FeatureSupported { - featureName: string; - isSupported: boolean; - type: string; - } - class ModernizrReport extends Plugin { - supportedFeatures: FeatureSupported[]; - constructor(); - getID(): string; - startClientSide(): void; - refresh(): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - private _filterList; - private _cssFeaturesListTable; - private _htmlFeaturesListTable; - private _miscFeaturesListTable; - private _nonCoreFeaturesListTable; - startDashboardSide(div?: HTMLDivElement): void; - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - } -} diff --git a/Server/Scripts/typings/vorlon/plugins/ngInspector/vorlon.ngInspector.d.ts b/Server/Scripts/typings/vorlon/plugins/ngInspector/vorlon.ngInspector.d.ts deleted file mode 100644 index af2b8904..00000000 --- a/Server/Scripts/typings/vorlon/plugins/ngInspector/vorlon.ngInspector.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module VORLON { - class NgInspector extends Plugin { - _cache: any[]; - constructor(); - getID(): string; - startClientSide(): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - startDashboardSide(div?: HTMLDivElement): void; - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - } -} diff --git a/Server/Scripts/typings/vorlon/plugins/objectExplorer/vorlon.objectExplorer.d.ts b/Server/Scripts/typings/vorlon/plugins/objectExplorer/vorlon.objectExplorer.d.ts deleted file mode 100644 index 6c373f1f..00000000 --- a/Server/Scripts/typings/vorlon/plugins/objectExplorer/vorlon.objectExplorer.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -declare module VORLON { - interface ObjPropertyDescriptor { - type: string; - name: string; - fullpath: string; - contentFetched: boolean; - value?: any; - content: Array; - } - class ObjectExplorerPlugin extends Plugin { - private _selectedObjProperty; - private _previousSelectedNode; - private _currentPropertyPath; - private _timeoutId; - constructor(); - getID(): string; - private STRIP_COMMENTS; - private ARGUMENT_NAMES; - private rootProperty; - private getFunctionArgumentNames(func); - private _getProperty(propertyPath); - private getObjDescriptor(object, pathTokens, scanChild?); - private _packageAndSendObjectProperty(type, path?); - private _markForRefresh(); - startClientSide(): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - refresh(): void; - private _containerDiv; - private _searchBoxInput; - private _searchBtn; - private _searchUpBtn; - private _treeDiv; - private _dashboardDiv; - private _contentCallbacks; - startDashboardSide(div?: HTMLDivElement): void; - private setCurrent(path); - private _queryObjectContent(objectPath); - private _appendSpan(parent, className, value); - private _generateColorfullLink(link, receivedObject); - private _generateButton(parentNode, text, className, onClick); - private _generateTreeNode(parentNode, receivedObject, first?); - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.basePlugin.d.ts b/Server/Scripts/typings/vorlon/vorlon.basePlugin.d.ts deleted file mode 100644 index 4f49c028..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.basePlugin.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -declare module VORLON { - class BasePlugin { - name: string; - _ready: boolean; - protected _id: string; - protected _debug: boolean; - _type: PluginType; - trace: (msg) => void; - protected traceLog: (msg: any) => void; - protected traceNoop: (msg: any) => void; - loadingDirectory: string; - constructor(name: string); - Type: PluginType; - debug: boolean; - getID(): string; - isReady(): boolean; - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.clientMessenger.d.ts b/Server/Scripts/typings/vorlon/vorlon.clientMessenger.d.ts deleted file mode 100644 index 67045ae1..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.clientMessenger.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -declare module VORLON { - interface VorlonMessageMetadata { - pluginID: string; - side: RuntimeSide; - sessionId: string; - clientId: string; - listenClientId: string; - waitingEvents?: number; - } - interface VorlonMessage { - metadata: VorlonMessageMetadata; - command?: string; - data?: any; - } - class ClientMessenger { - private _socket; - private _isConnected; - private _sessionId; - private _clientId; - private _listenClientId; - private _serverUrl; - private _waitingEvents; - onRealtimeMessageReceived: (message: VorlonMessage) => void; - onHeloReceived: (id: string) => void; - onIdentifyReceived: (id: string) => void; - onWaitingEventsReceived: (message: VorlonMessage) => void; - onStopListenReceived: () => void; - onRefreshClients: () => void; - onError: (err: Error) => void; - isConnected: boolean; - clientId: string; - socketId: string; - constructor(side: RuntimeSide, serverUrl: string, sessionId: string, clientId: string, listenClientId: string); - sendWaitingEvents(pluginID: string, waitingevents: number): void; - sendRealtimeMessage(pluginID: string, objectToSend: any, side: RuntimeSide, messageType?: string, incrementVisualIndicator?: boolean, command?: string): void; - sendMonitoringMessage(pluginID: string, message: string): void; - getMonitoringMessage(pluginID: string, onMonitoringMessage: (messages: string[]) => any, from?: string, to?: string): any; - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.clientPlugin.d.ts b/Server/Scripts/typings/vorlon/vorlon.clientPlugin.d.ts deleted file mode 100644 index 08b0f817..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.clientPlugin.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module VORLON { - class ClientPlugin extends BasePlugin { - ClientCommands: any; - constructor(name: string); - startClientSide(): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - sendToDashboard(data: any, incrementVisualIndicator?: boolean): void; - sendCommandToDashboard(command: string, data?: any, incrementVisualIndicator?: boolean): void; - refresh(): void; - _loadNewScriptAsync(scriptName: string, callback: () => void): void; - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.core.d.ts b/Server/Scripts/typings/vorlon/vorlon.core.d.ts deleted file mode 100644 index 3ce4cf2a..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.core.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -declare module VORLON { - class _Core { - _clientPlugins: ClientPlugin[]; - _dashboardPlugins: DashboardPlugin[]; - _messenger: ClientMessenger; - _sessionID: string; - _listenClientId: string; - _side: RuntimeSide; - _errorNotifier: any; - _messageNotifier: any; - _socketIOWaitCount: number; - debug: boolean; - _RetryTimeout: number; - Messenger: ClientMessenger; - ClientPlugins: Array; - DashboardPlugins: Array; - RegisterClientPlugin(plugin: ClientPlugin): void; - RegisterDashboardPlugin(plugin: DashboardPlugin): void; - StartClientSide(serverUrl?: string, sessionId?: string, listenClientId?: string): void; - startClientDirtyCheck(): void; - StartDashboardSide(serverUrl?: string, sessionId?: string, listenClientId?: string, divMapper?: (string) => HTMLDivElement): void; - private _OnStopListenReceived(); - private _OnIdentifyReceived(message); - private ShowError(message, timeout?); - private _OnError(err); - private _OnIdentificationReceived(id); - private _RetrySendingRealtimeMessage(plugin, message); - private _Dispatch(message); - private _DispatchPluginMessage(plugin, message); - private _DispatchFromClientPluginMessage(plugin, message); - private _DispatchFromDashboardPluginMessage(plugin, message); - } - var Core: _Core; -} diff --git a/Server/Scripts/typings/vorlon/vorlon.dashboardPlugin.d.ts b/Server/Scripts/typings/vorlon/vorlon.dashboardPlugin.d.ts deleted file mode 100644 index c0ac2038..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.dashboardPlugin.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -declare module VORLON { - class DashboardPlugin extends BasePlugin { - htmlFragmentUrl: any; - cssStyleSheetUrl: any; - DashboardCommands: any; - constructor(name: string, htmlFragmentUrl: string, cssStyleSheetUrl: string); - startDashboardSide(div: HTMLDivElement): void; - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - sendToClient(data: any): void; - sendCommandToClient(command: string, data?: any, incrementVisualIndicator?: boolean): void; - sendCommandToPluginClient(pluginId: string, command: string, data?: any, incrementVisualIndicator?: boolean): void; - sendCommandToPluginDashboard(pluginId: string, command: string, data?: any, incrementVisualIndicator?: boolean): void; - _insertHtmlContentAsync(divContainer: HTMLDivElement, callback: (filledDiv: HTMLDivElement) => void): void; - private _stripContent(content); - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.enums.d.ts b/Server/Scripts/typings/vorlon/vorlon.enums.d.ts deleted file mode 100644 index 96b062b9..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.enums.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module VORLON { - enum RuntimeSide { - Client = 0, - Dashboard = 1, - Both = 2, - } - enum PluginType { - OneOne = 0, - MulticastReceiveOnly = 1, - Multicast = 2, - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.plugin.d.ts b/Server/Scripts/typings/vorlon/vorlon.plugin.d.ts deleted file mode 100644 index 72212fdc..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.plugin.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -declare module VORLON { - class Plugin { - htmlFragmentUrl: any; - cssStyleSheetUrl: any; - loadingDirectory: string; - name: any; - _ready: boolean; - protected _id: string; - protected _debug: boolean; - _type: PluginType; - trace: (msg) => void; - protected traceLog: (msg: any) => void; - private traceNoop; - ClientCommands: any; - DashboardCommands: any; - constructor(name: string, htmlFragmentUrl: string, cssStyleSheetUrl: string); - Type: PluginType; - debug: boolean; - getID(): string; - isReady(): boolean; - startClientSide(): void; - startDashboardSide(div: HTMLDivElement): void; - onRealtimeMessageReceivedFromClientSide(receivedObject: any): void; - onRealtimeMessageReceivedFromDashboardSide(receivedObject: any): void; - sendToClient(data: any): void; - sendCommandToClient(command: string, data?: any, incrementVisualIndicator?: boolean): void; - sendCommandToPluginClient(pluginId: string, command: string, data?: any, incrementVisualIndicator?: boolean): void; - sendToDashboard(data: any, incrementVisualIndicator?: boolean): void; - sendCommandToDashboard(command: string, data?: any, incrementVisualIndicator?: boolean): void; - sendCommandToPluginDashboard(pluginId: string, command: string, data?: any, incrementVisualIndicator?: boolean): void; - refresh(): void; - _insertHtmlContentAsync(divContainer: HTMLDivElement, callback: (filledDiv: HTMLDivElement) => void): void; - _loadNewScriptAsync(scriptName: string, callback: () => void): void; - private _stripContent(content); - } -} diff --git a/Server/Scripts/typings/vorlon/vorlon.tools.d.ts b/Server/Scripts/typings/vorlon/vorlon.tools.d.ts deleted file mode 100644 index ae04d061..00000000 --- a/Server/Scripts/typings/vorlon/vorlon.tools.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -declare module VORLON { - class Tools { - static QuerySelectorById(root: HTMLElement, id: string): HTMLElement; - static SetImmediate(func: () => void): void; - static setLocalStorageValue(key: string, data: string): void; - static getLocalStorageValue(key: string): any; - static Hook(rootObject: any, functionToHook: string, hookingFunction: (...optionalParams: any[]) => void): void; - static CreateCookie(name: string, value: string, days: number): void; - static ReadCookie(name: string): string; - static CreateGUID(): string; - static RemoveEmpties(arr: string[]): number; - static AddClass(e: HTMLElement, name: string): HTMLElement; - static RemoveClass(e: HTMLElement, name: string): HTMLElement; - static ToggleClass(e: HTMLElement, name: string): void; - } - class FluentDOM { - element: HTMLElement; - childs: Array; - parent: FluentDOM; - constructor(nodeType: string, className?: string, parentElt?: Element, parent?: FluentDOM); - static for(element: HTMLElement): FluentDOM; - addClass(classname: string): FluentDOM; - className(classname: string): FluentDOM; - opacity(opacity: string): FluentDOM; - display(display: string): FluentDOM; - hide(): FluentDOM; - visibility(visibility: string): FluentDOM; - text(text: string): FluentDOM; - html(text: string): FluentDOM; - attr(name: string, val: string): FluentDOM; - editable(editable: boolean): FluentDOM; - style(name: string, val: string): FluentDOM; - appendTo(elt: Element): FluentDOM; - append(nodeType: string, className?: string, callback?: (FluentDOM) => void): FluentDOM; - createChild(nodeType: string, className?: string): FluentDOM; - click(callback: (EventTarget) => void): FluentDOM; - blur(callback: (EventTarget) => void): FluentDOM; - keydown(callback: (EventTarget) => void): FluentDOM; - } -} diff --git a/Server/Scripts/vorlon.IWebServerComponent.ts b/Server/Scripts/vorlon.IWebServerComponent.ts index 107e952c..3e37acd7 100644 --- a/Server/Scripts/vorlon.IWebServerComponent.ts +++ b/Server/Scripts/vorlon.IWebServerComponent.ts @@ -3,7 +3,7 @@ import http = require("http"); export module VORLON { export interface IWebServerComponent { - addRoutes: (app: express.Express) => void; + addRoutes: (app: express.Express, passport: any) => void; start: (httpServer: http.Server) => void; } } \ No newline at end of file diff --git a/Server/Scripts/vorlon.authentication.ts b/Server/Scripts/vorlon.authentication.ts index 89e63f6b..9a08ed3c 100644 --- a/Server/Scripts/vorlon.authentication.ts +++ b/Server/Scripts/vorlon.authentication.ts @@ -1,36 +1,35 @@ +import fs = require("fs"); +import path = require("path"); export module VORLON { export class Authentication { - public static Passport = require("passport"); - public static LocalStrategy = require("passport-local").Strategy; public static ActivateAuth: boolean = false; - - public static initAuthentication(){ - Authentication.Passport.use(new Authentication.LocalStrategy(function(username, password, done) { - // insert your MongoDB check here. For now, just a simple hardcoded check. - if (username === 'vorlon' && password === 'vorlon') - { - done(null, { user: username }); - } - else - { - done(null, false); - } - }) - ); - - Authentication.Passport.serializeUser(function(user, done) { - done(null, user.id); - }); - - Authentication.Passport.deserializeUser(function(id, done) { - done(null, { 'id' : '1', 'login' : 'vorlon'}); - }); - } + public static UserName: string; + public static Password: string; public static ensureAuthenticated(req, res, next) { if (!Authentication.ActivateAuth || req.isAuthenticated()) { return next(); } - res.redirect('/login') + res.redirect('/login'); } + + public static loadAuthConfig(): void { + fs.readFile(path.join(__dirname, "../config.json"), "utf8",(err, catalogdata) => { + if (err) { + return; + } + + var catalog = JSON.parse(catalogdata.replace(/^\uFEFF/, '')); + + if(catalog.activateAuth){ + Authentication.ActivateAuth = catalog.activateAuth; + } + if(catalog.username){ + Authentication.UserName = catalog.username; + } + if(catalog.password){ + Authentication.Password = catalog.password; + } + }); + } } }; diff --git a/Server/Scripts/vorlon.dashboard.ts b/Server/Scripts/vorlon.dashboard.ts index cf03a70f..d0d2a54e 100644 --- a/Server/Scripts/vorlon.dashboard.ts +++ b/Server/Scripts/vorlon.dashboard.ts @@ -1,36 +1,44 @@ import express = require("express"); import http = require("http"); var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; +import fs = require("fs"); +import path = require("path"); //Vorlon import iwsc = require("./vorlon.IWebServerComponent"); import ws = require("./vorlon.webServer"); import vauth = require("./vorlon.authentication"); +import baseURLConfig = require("../config/vorlon.baseurlconfig"); export module VORLON { export class Dashboard implements iwsc.VORLON.IWebServerComponent { + + private baseURLConfig: baseURLConfig.VORLON.BaseURLConfig; + constructor() { - //Nothing for now + this.baseURLConfig = new baseURLConfig.VORLON.BaseURLConfig(); } - public addRoutes(app: express.Express): void { - app.route('/').get(vauth.VORLON.Authentication.ensureAuthenticated, this.defaultDashboard); - app.route('/dashboard').get(vauth.VORLON.Authentication.ensureAuthenticated,this.defaultDashboard); - app.route('/dashboard/').get(vauth.VORLON.Authentication.ensureAuthenticated,this.defaultDashboard); + public addRoutes(app: express.Express, passport: any): void { + app.route(this.baseURLConfig.baseURL + '/').get(vauth.VORLON.Authentication.ensureAuthenticated, this.defaultDashboard()); + app.route(this.baseURLConfig.baseURL + '/dashboard').get(vauth.VORLON.Authentication.ensureAuthenticated,this.defaultDashboard()); + app.route(this.baseURLConfig.baseURL + '/dashboard/').get(vauth.VORLON.Authentication.ensureAuthenticated,this.defaultDashboard()); - app.route('/dashboard/:sessionid').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboard); - app.route('/dashboard/:sessionid/reset').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboardServerReset); - app.route('/dashboard/:sessionid/:clientid').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboardWithClient); + app.route(this.baseURLConfig.baseURL + '/dashboard/:sessionid').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboard()); + app.route(this.baseURLConfig.baseURL + '/dashboard/:sessionid/reset').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboardServerReset()); + app.route(this.baseURLConfig.baseURL + '/dashboard/:sessionid/:clientid').get(vauth.VORLON.Authentication.ensureAuthenticated,this.dashboardWithClient()); //login - app.post('/login', - vauth.VORLON.Authentication.Passport.authenticate('local', + app.post(this.baseURLConfig.baseURL + '/login', + passport.authenticate('local', { failureRedirect: '/login', successRedirect: '/', failureFlash: false }) ); - app.route('/login').get(this.login); + app.route(this.baseURLConfig.baseURL + '/login').get(this.login); + + app.get(this.baseURLConfig.baseURL + '/logout', this.logout); } public start(httpServer: http.Server): void { @@ -38,16 +46,32 @@ export module VORLON { } //Routes - private defaultDashboard(req: express.Request, res: express.Response) { - res.redirect('/dashboard/default'); + private defaultDashboard() { + return (req: express.Request, res: express.Response) => { + res.redirect(this.baseURLConfig.baseURL + '/dashboard/default'); + }; } - private dashboard(req: express.Request, res: express.Response) { - res.render('dashboard', { title: 'Dashboard', sessionid: req.params.sessionid, clientid: "" }); + private dashboard() { + return (req: express.Request, res: express.Response) => { + var authent = false; + var configastext = fs.readFileSync(path.join(__dirname, "../config.json")); + var catalog = JSON.parse(configastext.toString().replace(/^\uFEFF/, '')); + + if(catalog.activateAuth){ + authent = catalog.activateAuth; + } + + console.log(authent); + res.render('dashboard', { baseURL: this.baseURLConfig.baseURL, title: 'Dashboard', sessionid: req.params.sessionid, clientid: "", authenticated: authent }); + } } - private dashboardWithClient(req: express.Request, res: express.Response) { - res.render('dashboard', { title: 'Dashboard', sessionid: req.params.sessionid, clientid: req.params.clientid }); + + private dashboardWithClient() { + return (req: express.Request, res: express.Response) => { + res.render('dashboard', { baseURL: this.baseURLConfig.baseURL, title: 'Dashboard', sessionid: req.params.sessionid, clientid: req.params.clientid }); + } } private getsession(req: express.Request, res: express.Response) { @@ -55,22 +79,30 @@ export module VORLON { } private login(req: express.Request, res: express.Response) { - res.render('login', { message: 'Please login' }); + this.baseURLConfig = new baseURLConfig.VORLON.BaseURLConfig(); + res.render('login', { baseURL: this.baseURLConfig.baseURL, message: 'Please login' }); + } + + private logout(req: any, res: any) { + req.logout(); + res.redirect('/'); } - private dashboardServerReset(req: any, res: any) { - var sessionid = req.params.sessionid; - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - res.send("Done."); + private dashboardServerReset() { + return (req: any, res: any) => { + var sessionid = req.params.sessionid; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + res.send("Done."); + } } } + + xhr.open("GET", "http://" + req.headers.host + this.baseURLConfig.baseURL + "/api/reset/" + sessionid); + xhr.send(); } - - xhr.open("GET", "http://" + req.headers.host + "/api/reset/" + sessionid); - xhr.send(); } } }; diff --git a/Server/Scripts/vorlon.httpproxy.server.ts b/Server/Scripts/vorlon.httpproxy.server.ts new file mode 100644 index 00000000..952db973 --- /dev/null +++ b/Server/Scripts/vorlon.httpproxy.server.ts @@ -0,0 +1,510 @@ +import express = require("express"); +import http = require("http"); +import https = require('https'); +import path = require("path"); +import fs = require("fs"); +import util = require("util"); +import url = require("url"); +import zlib = require("zlib"); +var cookieParser = require('cookie-parser') +var colors = require("colors"); +var httpProxy = require("http-proxy"); + +import iwsc = require("./vorlon.IWebServerComponent"); +import baseURLConfig = require("../config/vorlon.baseurlconfig"); +import httpConfig = require("../config/vorlon.httpconfig"); + +export module VORLON { + export class HttpProxy implements iwsc.VORLON.IWebServerComponent { + private _proxy = null; + private _fetchproxy = null; + private _server = null; + private _proxyCookieName = "vorlonProxyTarget"; + private _proxySessionCookieName = "vorlonProxySession"; + private _vorlonScript = "vorlon.max.js"; + private baseURLConfig: baseURLConfig.VORLON.BaseURLConfig; + private httpConfig: httpConfig.VORLON.HttpConfig; + private _passport = require("passport"); + private _startProxyOnly =false; + + constructor(startProxyOnly:boolean=false) { + this._startProxyOnly=startProxyOnly; + this.baseURLConfig = new baseURLConfig.VORLON.BaseURLConfig(); + this.httpConfig = new httpConfig.VORLON.HttpConfig(); + this._proxy = httpProxy.createProxyServer({}); + this._fetchproxy = httpProxy.createProxyServer({}); + } + + private insertVorlonScript(str: string, uri, _script: string, vorlonsessionid: string) { + var position = str.indexOf(" 0) { + var closing = str.indexOf(">", position) + 1; + + console.log("PROXY Injert vorlon script in website with SESSIONID " + vorlonsessionid); + var beforehead = str.substr(0, closing); + var afterhead = str.substr(closing); + str = beforehead + " " + _script + afterhead; + } + return str; + } + + public start(): void { + if(this._startProxyOnly){ + this.addRoutes(express(),this._passport); + } + } + + public addRoutes(app: express.Express, passport: any): void { + if(!this._startProxyOnly){ + app.get(this.baseURLConfig.baseURL + "/httpproxy/fetch", this.fetchFile()); + app.get(this.baseURLConfig.baseURL + "/browserspecificcontent", this.browserSpecificContent()); + + if (!this.httpConfig.enableWebproxy) { + //the proxy is disabled, look at config.json to enable webproxy + return; + } + app.get(this.baseURLConfig.baseURL + "/httpproxy", this.home()); + app.get(this.baseURLConfig.baseURL + "/httpproxy/inject", this.inject()); + } + + this.startProxyServer(); + } + + public startProxyServer() { + this._server = express(); + if(this.httpConfig.proxyEnvPort) + this._server.set('port', process.env.PORT); + else + this._server.set('port', this.httpConfig.proxyPort); + this._server.use(cookieParser()); + this._server.use(this.baseURLConfig.baseProxyURL + "/vorlonproxy/root.html", this.proxyForTarget()); + this._server.use(this.baseURLConfig.baseProxyURL + "/vorlonproxy/*", this.proxyForRelativePath()); + this._server.use(this.baseURLConfig.baseProxyURL + "/", this.proxyForRootDomain()); + + // http.createServer(this._server).listen(this.httpConfig.proxyPort, () => { + // console.log("Vorlon.js proxy started on port " + this.httpConfig.proxyPort); + // }); + + + if (this.httpConfig.useSSL) { + https.createServer(this.httpConfig.options, this._server).listen(this._server.get('port'), () => { + console.log('Vorlon.js PROXY with SSL listening on port ' + this._server.get('port')); + }); + } else { + http.createServer(this._server).listen(this._server.get('port'), () => { + console.log('Vorlon.js PROXY listening on port ' + this._server.get('port')); + }); + } + + this._proxy.on("error", this.proxyError); + this._proxy.on("proxyRes", this.proxyResult.bind(this)); + this._proxy.on("proxyReq", this.proxyRequest.bind(this)); + this._fetchproxy.on("proxyReq", this.proxyFetchRequest.bind(this)); + this._fetchproxy.on("proxyRes", this.proxyFetchResult.bind(this)); + } + + private vorlonClientFileUrl() { + var scriptUrl = "http://localhost:" + this.httpConfig.port + "/" + this._vorlonScript; + if (this.httpConfig.vorlonServerURL) { + scriptUrl = this.httpConfig.vorlonServerURL + "/" + this._vorlonScript; + } + + return scriptUrl; + } + + private browserSpecificContent() { + return (req: express.Request, res: express.Response) => { + var userAgent = req.headers["user-agent"]; + var reply = (browsername) => { + res.write('Vorlon.js - Test page
      This is content for ' + browsername + '
      ' + userAgent + '
      '); + res.end(); + } + if (userAgent == "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36") { + reply("Google Chrome") + } else if (userAgent == "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240") { + reply("Microsoft Edge") + } else if (userAgent == "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko") { + reply("IE 11") + } else { + reply("Others") + } + } + } + + private fetchFile() { + return (req: express.Request, res: express.Response) => { + var targetProxyUrl = req.query.fetchurl; + + console.log("FETCH DOCUMENT " + targetProxyUrl); + var opt = { + target: targetProxyUrl, + changeOrigin: true + }; + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + this._fetchproxy.web(req, res, opt); + }; + } + + private proxyFetchRequest(proxyReq, req: express.Request, res: express.Response, opt) { + var e = proxyReq; + proxyReq.path = req.query.fetchurl; + + if (req.query.fetchuseragent) { + proxyReq._headers["user-agent"] = req.query.fetchuseragent; + console.log("FETCH ISSUING UA REQUEST TO " + proxyReq.path); + } else { + console.log("FETCH ISSUING REQUEST TO " + proxyReq.path); + } + } + + private proxyFetchResult(proxyRes, req: express.Request, res: express.Response) { + var writeHead = res.writeHead; + var encoding = proxyRes.headers["content-encoding"] || "none"; + res.writeHead = function() { + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range, X-VorlonProxyEncoding'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + + var encoding = proxyRes.headers["content-encoding"] || "none"; + + res.header('X-VorlonProxyEncoding', encoding); + + writeHead.apply(res, arguments); + }; + } + + //Routes + private proxyForRelativePath() { + return (req: express.Request, res: express.Response) => { + //disable accept-encoding + //req.headers["accept-encoding"] = ""; + + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + + var cookieUrl = req.cookies[this._proxyCookieName]; + var targetProxyUrl = (req).baseUrl.substr("/vorlonproxy/".length); + // var idx = targetProxyUrl.lastIndexOf('/'); + // if (idx > -1){ + // targetProxyUrl = targetProxyUrl.substr(0, idx+1); + // }else{ + // targetProxyUrl = ""; + // } + + if (cookieUrl) { + var uri = url.parse(cookieUrl); + var target = uri.href; + + if (targetProxyUrl) { + if (target[target.length - 1] != '/') + target = target + '/'; + + target = target + targetProxyUrl; + } + console.log("PROXY RELATIVE REQUEST from target " + target + " for " + (req).baseUrl); + var opt = { + target: target, + changeOrigin: true + }; + if (target.indexOf("https:") === 0) { + opt.secure = true; + } + this._proxy.web(req, res, opt); + } else { + console.warn("PROXY RELATIVE REQUEST but no target for " + (req).baseUrl); + } + }; + } + + private proxyForTarget() { + return (req: express.Request, res: express.Response) => { + //disable accept-encoding + //req.headers["accept-encoding"] = ""; + + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + + var targetProxyUrl = req.query.vorlonproxytarget; + if (!targetProxyUrl) { + targetProxyUrl = req.cookies[this._proxyCookieName]; + } + + if (targetProxyUrl) { + console.log("PROXY REQUEST from target " + targetProxyUrl + " for " + (req).baseUrl); + var opt = { + target: targetProxyUrl, + changeOrigin: true + }; + + if (targetProxyUrl.indexOf("https:") === 0) { + opt.secure = true; + } + this._proxy.web(req, res, opt); + } else { + console.warn("PROXY REQUEST but no target" + " for " + (req).baseUrl); + } + }; + } + + private proxyForRootDomain() { + return (req: express.Request, res: express.Response) => { + //disable accept-encoding + //req.headers["accept-encoding"] = ""; + + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + + var cookieUrl = req.cookies[this._proxyCookieName]; + + if (cookieUrl) { + var uri = url.parse(cookieUrl); + var target = uri.protocol + "//" + uri.hostname; + console.log("PROXY REQUEST for root http domain " + target) + var opt = { + target: target, + changeOrigin: true + }; + if (target.indexOf("https:") === 0) { + opt.secure = true; + } + this._proxy.web(req, res, opt); + } else { + console.warn("PROXY REQUEST from root but no cookie..."); + } + }; + } + + private home() { + return (req: express.Request, res: express.Response) => { + res.render('httpproxy', { baseURL: this.baseURLConfig.baseURL }); + }; + } + + private inject() { + return (req: express.Request, res: express.Response) => { + var uri = url.parse(req.query.url); + //res.cookie(this._proxyCookieName, uri.protocol + "//" + uri.hostname); + console.log("PROXY request for " + uri.hostname + " to port " + this.httpConfig.proxyPort); + var rootUrl = "http://localhost:" + this.httpConfig.proxyPort; + if (this.httpConfig.vorlonProxyURL) { + rootUrl = this.httpConfig.vorlonProxyURL; + } + var sessionid = this.vorlonSessionIdFor(uri.protocol + "//" + uri.hostname); + res.end(JSON.stringify({ url : rootUrl + "/vorlonproxy/root.html?vorlonproxytarget=" + encodeURIComponent(req.query.url)+"&vorlonsessionid=" + sessionid, session : sessionid })); + }; + } + + private vorlonSessionIdFor(targeturl, req?) { + if (req && req.query.vorlonsessionid) { + return req.query.vorlonsessionid; + }else if (req && req.cookies[this._proxySessionCookieName]){ + return req.cookies[this._proxySessionCookieName]; + } + var uri = url.parse(targeturl); + var pat = /^(https?:\/\/)?(?:www\.)?([^\/]+)/; + var match = uri.href.match(pat); + var vorlonsessionid = match[2].replace(".",""); + return vorlonsessionid; + } + + //Events HttpProxy + private proxyError(error, req: express.Request, res: express.Response) { + var json; + console.log("proxy error", error); + if (!res.headersSent) { + res.writeHead(500, { "content-type": "application/json" }); + } + + json = { error: "proxy_error", reason: error.message }; + res.end(JSON.stringify(json)); + } + + + private proxyRequest(proxyReq, req: express.Request, res: express.Response, opt) { + var e = proxyReq; + if (proxyReq.path[proxyReq.path.length - 1] == "/") { + proxyReq.path = proxyReq.path.substr(0, proxyReq.path.length - 1); + } + console.log("PROXY ISSUING REQUEST TO " + proxyReq.path); + } + + private proxyResult(proxyRes, req: express.Request, res: express.Response) { + var _proxy = this; + var chunks, end = res.end, writeHead = res.writeHead, write = res.write; + + var targetProxyUrl = req.query.vorlonproxytarget; + if (!targetProxyUrl) { + targetProxyUrl = req.cookies[this._proxyCookieName]; + } + var cspHeader = proxyRes.headers["content-security-policy"]; + //TODO : manage content-security-policy header for script, ... + + if (proxyRes.statusCode >= 300) { + console.warn("PROXY received status " + proxyRes.statusCode + " " + proxyRes.statusMessage); + console.warn(proxyRes.req._header); + } + + if (req.query.vorlonproxytarget && proxyRes.statusCode >= 300 && proxyRes.statusCode < 400) { + return this.proxyResultForRedirection(targetProxyUrl, proxyRes, req, res); + } + + console.log("PROXY content type " + proxyRes.headers["content-type"]); + if (targetProxyUrl && proxyRes.headers && proxyRes.headers["content-type"] && proxyRes.headers["content-type"].match("text/html")) { + return this.proxyResultForPageContent(targetProxyUrl, proxyRes, req, res); + } else { + var encoding = proxyRes.headers["content-encoding"]; + res.writeHead = function() { + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + res.header("content-security-policy", ""); + res.header("content-security-policy", ""); + res.header('X-VorlonProxyEncoding', encoding || "none"); + + writeHead.apply(res, arguments); + }; + } + } + + private proxyResultForRedirection(targetProxyUrl: string, proxyRes, req: express.Request, res: express.Response) { + var _proxy = this; + var chunks, end = res.end, writeHead = res.writeHead, write = res.write; + res.writeHead = function() { + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + res.header("content-security-policy", ""); + + var location = (res)._headers["location"]; + location = location.substr(0, location.indexOf("?vorlonproxytarget=")); + var vorlonsessionid = _proxy.vorlonSessionIdFor(targetProxyUrl, req); + res.header("location", "?vorlonproxytarget=" + encodeURIComponent(location) + "&vorlonsessionid=" + encodeURIComponent(vorlonsessionid)); + + writeHead.apply(res, arguments); + }; + + return; + } + + private proxyResultForPageContent(targetProxyUrl: string, proxyRes, req: express.Request, res: express.Response) { + var _proxy = this; + var chunks, end = res.end, writeHead = res.writeHead, write = res.write; + var encoding = proxyRes.headers["content-encoding"]; + + var uri = url.parse(targetProxyUrl); + var vorlonsessionid = _proxy.vorlonSessionIdFor(targetProxyUrl, req); + //var scriptUrl = "http://localhost:" + this.httpConfig.port + "/" + this._vorlonScript + "/" + vorlonsessionid; + var _script = "" + + if (encoding == "gzip" || encoding == "deflate") { + console.warn("PROXY content is encoded to " + encoding); + var uncompress = (zlib).Gunzip(); + if (encoding == "deflate") + uncompress = (zlib).Inflate(); + + res.writeHead = function() { + if (proxyRes.headers && proxyRes.headers["content-length"]) { + res.setHeader("content-length", parseInt(proxyRes.headers["content-length"], 10) + _script.length); + } + res.setHeader("transfer-encoding", ""); + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + res.header('Expires', '-1'); + res.header("content-security-policy", ""); + res.header('Pragma', 'no-cache'); + res.header('Content-Encoding', ''); + + //res.header('Content-Type', 'text/html; charset=utf-8'); + res.removeHeader('Content-Encoding'); + res.removeHeader('Content-Length'); + + res.header('X-VorlonProxyEncoding', encoding || "none"); + + //we must set cookie only if url was requested through Vorlon + if (req.query.vorlonproxytarget) { + console.log("set cookie " + req.query.vorlonproxytarget); + res.cookie(_proxy._proxyCookieName, req.query.vorlonproxytarget); + res.cookie(_proxy._proxySessionCookieName, vorlonsessionid); + } + + writeHead.apply(res, arguments); + }; + + (res).write = (data) => { + uncompress.write(data); + }; + + uncompress.on('data', (data) => { + if (chunks) { + chunks += data; + } else { + chunks = data; + } + return chunks; + }); + + uncompress.on('end', (data) => { + if (chunks && chunks.toString) { + var contentstring = chunks.toString(); + var tmp = _proxy.insertVorlonScript(contentstring, uri, _script, vorlonsessionid); + + write.apply(res, [tmp]); + } + end.apply(res); + }); + + res.end = function(): void { + uncompress.end(arguments[0]); + }; + + } else { + res.writeHead = function() { + if (proxyRes.headers && proxyRes.headers["content-length"]) { + res.setHeader("content-length", parseInt(proxyRes.headers["content-length"], 10) + _script.length); + } + res.setHeader("transfer-encoding", ""); + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Encoding, Content-Length, Content-Range'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + res.header("content-security-policy", ""); + res.header('X-VorlonProxyEncoding', encoding || "none"); + //we must set cookie only if url was requested through Vorlon + if (req.query.vorlonproxytarget) { + console.log("set cookie " + req.query.vorlonproxytarget); + res.cookie(_proxy._proxyCookieName, req.query.vorlonproxytarget); + } + + writeHead.apply(res, arguments); + }; + + res.write = (data) => { + if (chunks) { + chunks += data; + } else { + chunks = data; + } + return chunks; + }; + + res.end = function() { + if (chunks && chunks.toString) { + var tmp = _proxy.insertVorlonScript(chunks.toString(), uri, _script, vorlonsessionid); + + write.apply(res, [tmp]); + } + end.apply(res, arguments); + }; + } + } + + } +} \ No newline at end of file diff --git a/Server/Scripts/vorlon.server.ts b/Server/Scripts/vorlon.server.ts index f3abfcb8..dcb5b06f 100644 --- a/Server/Scripts/vorlon.server.ts +++ b/Server/Scripts/vorlon.server.ts @@ -10,7 +10,9 @@ var fakeredis = require("fakeredis"); var winstonDisplay = require("winston-logs-display"); import redisConfigImport = require("../config/vorlon.redisconfig"); var redisConfig = redisConfigImport.VORLON.RedisConfig; -import httpConfig = require("../config/vorlon.httpconfig"); +import httpConfig = require("../config/vorlon.httpconfig"); +import logConfig = require("../config/vorlon.logconfig"); +import baseURLConfig = require("../config/vorlon.baseurlconfig"); //Vorlon import iwsc = require("./vorlon.IWebServerComponent"); @@ -25,11 +27,16 @@ export module VORLON { private _redisApi: any; private _log: winston.LoggerInstance; private http: httpConfig.VORLON.HttpConfig; + private logConfig: logConfig.VORLON.LogConfig; + private baseURLConfig: baseURLConfig.VORLON.BaseURLConfig; constructor() { + this.logConfig = new logConfig.VORLON.LogConfig(); + this.baseURLConfig = new baseURLConfig.VORLON.BaseURLConfig(); + //LOGS winston.cli(); - this._log = new winston.Logger({ + this._log = new winston.Logger({ levels: { info: 0, warn: 1, @@ -40,21 +47,32 @@ export module VORLON { plugin: 6 }, transports: [ - new winston.transports.Console({ - level: 'debug', - handleExceptions: true, - json: false, - timestamp: true, - colorize: true - }), - new winston.transports.File({ filename: 'vorlonjs.log' }) + new winston.transports.File({ filename: this.logConfig.vorlonLogFile, level: this.logConfig.level}) ], exceptionHandlers: [ - new winston.transports.File({ filename: 'exceptions.log', timestamp: true, maxsize: 1000000 }) + new winston.transports.File({ filename: this.logConfig.exceptionsLogFile, timestamp: true, maxsize: 1000000 }) ], exitOnError: false }); + if (this.logConfig.enableConsole) { + this._log.add(winston.transports.Console, { + level: this.logConfig.level, + handleExceptions: true, + json: false, + timestamp: function() { + var date:Date = new Date(); + return date.getFullYear() + "-" + + date.getMonth() + "-" + + date.getDate() + " " + + date.getHours() + ":" + + date.getMinutes() + ":" + + date.getSeconds(); + }, + colorize: true + }); + } + winston.addColors({ info: 'green', warn: 'cyan', @@ -81,12 +99,12 @@ export module VORLON { this.http = new httpConfig.VORLON.HttpConfig(); } - public addRoutes(app: express.Express): void { - app.get("/api/createsession",(req: any, res: any) => { + public addRoutes(app: express.Express, passport: any): void { + app.get(this.baseURLConfig.baseURL + "/api/createsession",(req: any, res: any) => { this.json(res, this.guid()); }); - app.get("/api/reset/:idSession",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/api/reset/:idSession",(req: any, res: any) => { var session = this.sessions[req.params.idSession]; if (session && session.connectedClients) { for (var client in session.connectedClients) { @@ -98,7 +116,7 @@ export module VORLON { res.end(); }); - app.get("/api/getclients/:idSession",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/api/getclients/:idSession",(req: any, res: any) => { var session = this.sessions[req.params.idSession]; var clients = new Array(); if (session != null) { @@ -107,7 +125,7 @@ export module VORLON { var currentclient = session.connectedClients[client]; if (currentclient.opened) { var name = tools.VORLON.Tools.GetOperatingSystem(currentclient.ua); - clients.push({ "clientid": currentclient.clientId, "displayid": currentclient.displayId, "waitingevents": currentclient.waitingevents, "name": name }); + clients.push(currentclient.data); nbClients++; } } @@ -123,14 +141,14 @@ export module VORLON { this.json(res, clients); }); - app.get("/api/range/:idsession/:idplugin/:from/:to",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/api/range/:idsession/:idplugin/:from/:to",(req: any, res: any) => { this._redisApi.lrange(req.params.idsession + req.params.idplugin, req.params.from, req.params.to,(err: any, reply: any) => { this._log.info("API : Get Range data from : " + req.params.from + " to " + req.params.to + " = " + reply, { type: "API", session: req.params.idsession }); this.json(res, reply); }); }); - app.post("/api/push",(req: any, res: any) => { + app.post(this.baseURLConfig.baseURL + "/api/push",(req: any, res: any) => { var receiveMessage = req.body; this._log.info("API : Receve data to log : " + JSON.stringify(req.body), { type: "API", session: receiveMessage._idsession }); this._redisApi.rpush([receiveMessage._idsession + receiveMessage.id, receiveMessage.message], err => { @@ -143,31 +161,31 @@ export module VORLON { this.json(res, {}); }); - app.get("/vorlon.max.js/",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/vorlon.max.js/",(req: any, res: any) => { res.redirect("/vorlon.max.js/default"); }); - app.get("/vorlon.max.js/:idsession",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/vorlon.max.js/:idsession",(req: any, res: any) => { this._sendVorlonJSFile(false, req, res); }); - app.get("/vorlon.js/",(req: any, res: any) => { - res.redirect("/vorlon.js/default"); + app.get(this.baseURLConfig.baseURL + "/vorlon.js",(req: any, res: any) => { + res.redirect(this.baseURLConfig.baseURL + "/vorlon.js/default"); }); - app.get("/vorlon.js/:idsession",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/vorlon.js/:idsession",(req: any, res: any) => { this._sendVorlonJSFile(true, req, res); }); - app.get("/vorlon.max.autostartdisabled.js/",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/vorlon.max.autostartdisabled.js",(req: any, res: any) => { this._sendVorlonJSFile(false, req, res, false); }); - app.get("/vorlon.autostartdisabled.js/",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/vorlon.autostartdisabled.js",(req: any, res: any) => { this._sendVorlonJSFile(true, req, res, false); }); - app.get("/config.json",(req: any, res: any) => { + app.get(this.baseURLConfig.baseURL + "/config.json",(req: any, res: any) => { this._sendConfigJson(req, res); }); @@ -182,11 +200,23 @@ export module VORLON { this._log.error("ROUTE : Error reading config.json file"); return; } - - var configstring = catalogdata.toString().replace(/^\uFEFF/, ''); + var catalog = JSON.parse(catalogdata.replace(/^\uFEFF/, '')); + + //remove auth data to not send username and password outside ;) + if(catalog.activateAuth){ + delete catalog.activateAuth; + } + if(catalog.username){ + delete catalog.username; + } + if(catalog.password){ + delete catalog.password; + } + + catalogdata = JSON.stringify(catalog); res.header('Content-Type', 'application/json'); - res.send(configstring); + res.send(catalogdata); }); } @@ -196,19 +226,21 @@ export module VORLON { fs.readFile(path.join(__dirname, "../config.json"), "utf8",(err, catalogdata) => { if (err) { - this._log.error("ROUTE : Error reading config.json file"); + this._log.error("ROUTE : Error reading config.json"); return; } var configstring = catalogdata.toString().replace(/^\uFEFF/, ''); - console.log(configstring); + var baseUrl = this.baseURLConfig.baseURL; var catalog = JSON.parse(configstring); var vorlonpluginfiles: string = ""; var javascriptFile: string = ""; + javascriptFile += 'var vorlonBaseURL="' + baseUrl + '";\n'; + //read the socket.io file if needed if (catalog.includeSocketIO) { - javascriptFile += fs.readFileSync(path.join(__dirname, "../public/javascripts/socket.io-1.3.5.js")); + javascriptFile += fs.readFileSync(path.join(__dirname, "../public/javascripts/socket.io-1.3.6.js")); } if (ismin) { @@ -231,7 +263,7 @@ export module VORLON { } } - vorlonpluginfiles = vorlonpluginfiles.replace('"vorlon/plugins"', '"' + this.http.protocol + '://' + req.headers.host + '/vorlon/plugins"'); + vorlonpluginfiles = vorlonpluginfiles.replace('"vorlon/plugins"', '"' + this.http.protocol + '://' + req.headers.host + baseUrl + '/vorlon/plugins"'); javascriptFile += "\r" + vorlonpluginfiles; if (autostart) { @@ -298,7 +330,6 @@ export module VORLON { public addClient(socket: SocketIO.Socket): void { socket.on("helo",(message: string) => { - //this._log.warn("CLIENT helo " + message); var receiveMessage = JSON.parse(message); var metadata = receiveMessage.metadata; var data = receiveMessage.data; @@ -314,9 +345,9 @@ export module VORLON { if (client == undefined) { var client = new Client(metadata.clientId, data.ua, socket, ++session.nbClients); session.connectedClients[metadata.clientId] = client; - this._log.info(formatLog("PLUGIN", "Send Refresh clientlist to dashboard (" + client.displayId + ")[" + data.ua + "] socketid = " + socket.id, receiveMessage)); + this._log.info(formatLog("PLUGIN", "Send Add Client to dashboard (" + client.displayId + ")[" + data.ua + "] socketid = " + socket.id, receiveMessage)); if (dashboard != undefined) { - dashboard.emit("refreshclients"); + dashboard.emit("addclient", client.data); } this._log.info(formatLog("PLUGIN", "New client (" + client.displayId + ")[" + data.ua + "] socketid = " + socket.id, receiveMessage)); @@ -325,7 +356,7 @@ export module VORLON { client.socket = socket; client.opened = true; if (dashboard != undefined) { - dashboard.emit("refreshclients"); + dashboard.emit("addclient", client.data); } this._log.info(formatLog("PLUGIN", "Client Reconnect (" + client.displayId + ")[" + data.ua + "] socketid=" + socket.id, receiveMessage)); } @@ -335,7 +366,7 @@ export module VORLON { //If dashboard already connected to this socket send "helo" else wait if ((metadata.clientId != "") && (metadata.clientId == session.currentClientId)) { this._log.info(formatLog("PLUGIN", "Send helo to client to open socket : " + metadata.clientId, receiveMessage)); - socket.emit("helo", metadata.clientId); + //socket.emit("helo", metadata.clientId); } else { this._log.info(formatLog("PLUGIN", "New client (" + client.displayId + ") wait...", receiveMessage)); @@ -370,34 +401,6 @@ export module VORLON { } }); - socket.on("waitingevents",(message: string) => { - //this._log.warn("CLIENT waitingevents " + message); - var receiveMessage = JSON.parse(message); - var dashboard = this.dashboards[receiveMessage.metadata.sessionId]; - if (dashboard != null) { - dashboard.emit("waitingevents", message); - var session = this.sessions[receiveMessage.metadata.sessionId]; - if (session && session.connectedClients) { - var client = session.connectedClients[receiveMessage.metadata.clientId]; - client.waitingevents = receiveMessage.metadata.waitingEvents; - } - } - }); - - socket.on("disconnect",(message: string) => { - //this._log.warn("CLIENT disconnect " + message); - for (var sessionId in this.sessions) { - var session = this.sessions[sessionId] - for (var clientId in session.connectedClients) { - var client = session.connectedClients[clientId]; - if (client.socket.id === socket.id) { - client.opened = false; - this._log.info(formatLog("PLUGIN", "Delete client socket " + socket.id)); - } - } - } - }); - socket.on("clientclosed",(message: string) => { //this._log.warn("CLIENT clientclosed " + message); var receiveMessage = JSON.parse(message); @@ -406,8 +409,8 @@ export module VORLON { if (receiveMessage.data.socketid === this.sessions[session].connectedClients[client].socket.id) { this.sessions[session].connectedClients[client].opened = false; if (this.dashboards[session]) { - this._log.info(formatLog("PLUGIN", "Send RefreshClients to Dashboard " + socket.id, receiveMessage)); - this.dashboards[session].emit("refreshclients"); + this._log.info(formatLog("PLUGIN", "Send RemoveClient to Dashboard " + socket.id, receiveMessage)); + this.dashboards[session].emit("removeclient", this.sessions[session].connectedClients[client].data); } else { this._log.info(formatLog("PLUGIN", "NOT sending RefreshClients, no Dashboard " + socket.id, receiveMessage)); } @@ -449,7 +452,6 @@ export module VORLON { if (client.socket != null) { this._log.info(formatLog("DASHBOARD", "Send helo to socketid :" + client.socket.id, receiveMessage)); client.socket.emit("helo", metadata.listenClientId); - } } else { @@ -467,6 +469,39 @@ export module VORLON { } }); + socket.on("reload",(message: string) => { + //this._log.warn("DASHBOARD reload " + message); + var receiveMessage = JSON.parse(message); + var metadata = receiveMessage.metadata; + + //if client listen by dashboard send reload to selected client + if (metadata.listenClientId !== "") { + this._log.info(formatLog("DASHBOARD", "Client selected for :" + metadata.listenClientId, receiveMessage)); + var session = this.sessions[metadata.sessionId]; + if (session != undefined) { + this._log.info(formatLog("DASHBOARD", "Change currentClient " + metadata.clientId, receiveMessage)); + session.currentClientId = metadata.listenClientId; + + for (var clientId in session.connectedClients) { + var client = session.connectedClients[clientId] + if (client.clientId === metadata.listenClientId) { + if (client.socket != null) { + this._log.info(formatLog("DASHBOARD", "Send reload to socketid :" + client.socket.id, receiveMessage)); + client.socket.emit("reload", metadata.listenClientId); + + } + } + else { + this._log.info(formatLog("DASHBOARD", "Wait for socketid (" + client.socket.id + ")", receiveMessage)); + } + } + } + } + else { + this._log.info(formatLog("DASHBOARD", "No client selected for this dashboard")); + } + }); + socket.on("protocol",(message: string) => { //this._log.warn("DASHBOARD protocol " + message); var receiveMessage = JSON.parse(message); @@ -558,8 +593,11 @@ export module VORLON { public displayId: number; public socket: SocketIO.Socket; public opened: boolean; - public waitingevents: number; public ua: string; + + public get data(): any { + return { "clientid": this.clientId, "displayid": this.displayId, "ua": this.ua, "name": tools.VORLON.Tools.GetOperatingSystem(this.ua) }; + } constructor(clientId: string, ua: string, socket: SocketIO.Socket, displayId: number, opened: boolean = true) { this.clientId = clientId; @@ -567,7 +605,6 @@ export module VORLON { this.socket = socket; this.displayId = displayId; this.opened = opened; - this.waitingevents = 0; } } @@ -577,7 +614,6 @@ export module VORLON { sessionId: string; clientId: string; listenClientId: string; - waitingEvents?: number; } export interface VorlonMessage { diff --git a/Server/Scripts/vorlon.webServer.ts b/Server/Scripts/vorlon.webServer.ts index ac4cf77e..874b6774 100644 --- a/Server/Scripts/vorlon.webServer.ts +++ b/Server/Scripts/vorlon.webServer.ts @@ -1,37 +1,43 @@ import express = require("express"); import path = require("path"); import stylus = require("stylus"); +import fs = require("fs"); //Vorlon import iwsc = require("./vorlon.IWebServerComponent"); import vauth = require("./vorlon.authentication"); import httpConfig = require("../config/vorlon.httpconfig"); - +import baseURLConfig = require("../config/vorlon.baseurlconfig"); export module VORLON { export class WebServer { private _bodyParser = require("body-parser"); - private _cookieParser = require("cookieparser"); - private _favicon = require("favicon"); + private _cookieParser = require("cookie-parser"); + private _methodOverride = require("method-override"); private _session = require("express-session"); private _json = require("json"); private _multer = require("multer"); + private _passport = require("passport"); + private _localStrategy = require("passport-local"); + private _twitterStrategy = require("passport-twitter"); // private _flash = require('connect-flash'); private _components: Array; private http: httpConfig.VORLON.HttpConfig; private _app: express.Express; + private baseURLConfig: baseURLConfig.VORLON.BaseURLConfig; constructor() { this._app = express(); this._components = new Array(); this.http = new httpConfig.VORLON.HttpConfig(); + this.baseURLConfig = new baseURLConfig.VORLON.BaseURLConfig(); } public init(): void { for (var id in this._components) { var component = this._components[id]; - component.addRoutes(this._app); + component.addRoutes(this._app, this._passport); } } @@ -45,47 +51,89 @@ export module VORLON { public start(): void { var app = this._app; - this.init(); + + //Command line + var stopExecution = false; + process.argv.forEach(function (val, index, array) { + switch (val) { + case "--version": + fs.readFile(path.join(__dirname, "../../package.json"), "utf8",(err, packageData) => { + if (err) { + this._log.error("Error reading package.json file"); + return; + } + + var _package = JSON.parse(packageData.replace(/^\uFEFF/, '')); + console.log('Vorlon.js v' + _package.version); + }); + stopExecution = true; + break; + } + }); + + if (stopExecution) { + return; + } //Sets - app.set('port', process.env.PORT || 1337); + app.set('port', this.http.port); app.set('views', path.join(__dirname, '../views')); app.set('view engine', 'jade'); //Uses + this._passport.use(new this._localStrategy(function(username, password, done) { + // insert your MongoDB check here. For now, just a simple hardcoded check. + if (username === vauth.VORLON.Authentication.UserName && password === vauth.VORLON.Authentication.Password) + { + done(null, { user: username }); + } + else + { + done(null, false); + } + }) + ); + + this._passport.serializeUser(function(user, done) { + done(null, user); + }); + + this._passport.deserializeUser(function(user, done) { + done(null, user); + }); + app.use(stylus.middleware(path.join(__dirname, '../public'))); - app.use(express.static(path.join(__dirname, '../public'))); - app.use(this._cookieParser); - app.use(this._favicon); + app.use(this.baseURLConfig.baseURL, express.static(path.join(__dirname, '../public'))); + app.use(this._bodyParser.urlencoded({ extended: false })); + app.use(this._bodyParser.json()); + app.use(this._cookieParser()); + app.use(this._multer()); + app.use(this._methodOverride()); app.use(this._session({ - // secret: '1th3is4is3as2e5cr6ec7t7keyf23or1or5lon5', + secret: '1th3is4is3as2e5cr6ec7t7keyf23or1or5lon5', expires: false, saveUninitialized: true, resave: true })); - app.use(this._bodyParser.json()); - app.use(this._bodyParser.urlencoded({ extended: true })); - app.use(this._multer()); - app.use(vauth.VORLON.Authentication.Passport.initialize()); - app.use(vauth.VORLON.Authentication.Passport.session()); - // app.use(this._flash()); + app.use(this._passport.initialize()); + app.use(this._passport.session()); + + vauth.VORLON.Authentication.loadAuthConfig(); - //Authorization CORS - //Ressource : http://enable-cors.org + this.init(); + app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); - - vauth.VORLON.Authentication.initAuthentication(); if (this.http.useSSL) { this.http.httpModule = this.http.httpModule.createServer(this.http.options, app).listen(app.get('port'), () => { - console.log('Vorlon with SSL listening on port ' + app.get('port')); + console.log('Vorlon.js SERVER with SSL listening on port ' + app.get('port')); }); } else { this.http.httpModule = this.http.httpModule.createServer(app).listen(app.get('port'), () => { - console.log('Vorlon listening on port ' + app.get('port')); + console.log('Vorlon.js SERVER listening on port ' + app.get('port')); }); } diff --git a/Server/Server.njsproj b/Server/Server.njsproj index 9a861b71..3e014700 100644 --- a/Server/Server.njsproj +++ b/Server/Server.njsproj @@ -201,7 +201,7 @@ - + diff --git a/Server/config.json b/Server/config.json index 1d4c923c..1249f2ba 100644 --- a/Server/config.json +++ b/Server/config.json @@ -1,8 +1,13 @@ -{ +{ + "baseURL": "", + "useSSLAzure":false, "useSSL": false, "SSLkey": "cert/server.key", "SSLcert": "cert/server.crt", "includeSocketIO": true, + "activateAuth": false, + "username": "", + "password": "", "plugins": [ { "id": "CONSOLE", "name": "Interactive Console", "panel": "bottom", "foldername": "interactiveConsole", "enabled": true }, { "id": "DOM", "name": "Dom Explorer", "panel": "top", "foldername": "domExplorer", "enabled": true }, @@ -12,6 +17,16 @@ { "id": "NGINSPECTOR", "name": "Ng. Inspector", "panel": "top", "foldername": "ngInspector", "enabled": false }, { "id": "NETWORK", "name": "Network Monitor", "panel": "top", "foldername": "networkMonitor", "enabled": true }, { "id": "RESOURCES", "name": "Resources Explorer", "panel": "top", "foldername": "resourcesExplorer", "enabled": true }, - { "id": "BABYLONINSPECTOR", "name": "Babylon Inspector", "panel": "top", "foldername": "babylonInspector", "enabled": false } - ] + { "id": "DEVICE", "name": "My Device", "panel": "top", "foldername": "device", "enabled": true }, + { "id": "UNITTEST", "name": "Unit Test", "panel": "top", "foldername": "unitTestRunner", "enabled": true }, + { "id": "BABYLONINSPECTOR", "name": "Babylon Inspector", "panel": "top", "foldername": "babylonInspector", "enabled": false }, + { "id": "WEBSTANDARDS", "name": "Best practices", "panel": "top", "foldername": "webstandards", "enabled": true } + ], + "port": 1337, + "enableWebproxy" : true, + "baseProxyURL": "", + "proxyPort" : 5050, + "proxyEnvPort": false, + "vorlonServerURL": "", + "vorlonProxyURL": "" } diff --git a/Server/config/vorlon.baseurlconfig.ts b/Server/config/vorlon.baseurlconfig.ts new file mode 100644 index 00000000..3d52b125 --- /dev/null +++ b/Server/config/vorlon.baseurlconfig.ts @@ -0,0 +1,29 @@ +import fs = require("fs"); +import path = require("path"); + +export module VORLON { + export class BaseURLConfig { + + public baseURL: string; + public baseProxyURL : string; + + constructor() { + var catalogdata: string = fs.readFileSync(path.join(__dirname, "../config.json"), "utf8"); + var catalogstring = catalogdata.toString().replace(/^\uFEFF/, ''); + var catalog = JSON.parse(catalogstring); + if (catalog.baseURL != undefined) { + this.baseURL = catalog.baseURL; + } + else { + this.baseURL = ""; + } + + if (catalog.baseProxyURL != undefined) { + this.baseProxyURL = catalog.baseProxyURL; + } + else { + this.baseProxyURL = ""; + } + } + } +} \ No newline at end of file diff --git a/Server/config/vorlon.httpconfig.ts b/Server/config/vorlon.httpconfig.ts index 673b578a..2a57b17e 100644 --- a/Server/config/vorlon.httpconfig.ts +++ b/Server/config/vorlon.httpconfig.ts @@ -9,7 +9,13 @@ export module VORLON { public protocol: String; public httpModule; public options; - + public port; + public proxyPort; + public enableWebproxy : boolean; + public vorlonServerURL : string; + public vorlonProxyURL : string; + public proxyEnvPort:boolean; + public constructor() { var catalogdata: string = fs.readFileSync(path.join(__dirname, "../config.json"), "utf8"); var catalogstring = catalogdata.toString().replace(/^\uFEFF/, ''); @@ -25,10 +31,26 @@ export module VORLON { } } else { - this.useSSL = false; - this.protocol = "http"; - this.httpModule = http; + this.useSSL = false; + if (catalog.useSSLAzure){ + this.protocol = "https"; + this.httpModule = http; + } + else{ + this.protocol = "http"; + this.httpModule = http; + } } + this.proxyEnvPort=catalog.proxyEnvPort; + if(catalog.proxyEnvPort) + this.proxyPort = process.env.PORT; + else + this.proxyPort = catalog.proxyPort || 5050; + this.port = process.env.PORT || catalog.port || 1337; + this.proxyPort = catalog.proxyPort || 5050; + this.enableWebproxy = catalog.enableWebproxy || false; + this.vorlonServerURL = catalog.vorlonServerURL || ""; + this.vorlonProxyURL = catalog.vorlonProxyURL || ""; } } -} \ No newline at end of file +} diff --git a/Server/config/vorlon.logconfig.ts b/Server/config/vorlon.logconfig.ts new file mode 100644 index 00000000..34c71829 --- /dev/null +++ b/Server/config/vorlon.logconfig.ts @@ -0,0 +1,34 @@ +import fs = require("fs"); +import path = require("path"); + +export module VORLON { + export class LogConfig { + public vorlonLogFile: string; + public exceptionsLogFile: string; + public enableConsole: boolean; + public level: string; + + public constructor() { + var configurationFile: string = fs.readFileSync(path.join(__dirname, "../config.json"), "utf8"); + var configurationString = configurationFile.toString().replace(/^\uFEFF/, ''); + var configuration = JSON.parse(configurationString); + if (configuration.logs) { + var logConfig = configuration.logs; + var filePath = logConfig.filePath ? logConfig.filePath : path.join(__dirname, "../"); + var vorlonjsFile = logConfig.vorlonLogFileName ? logConfig.vorlonLogFileName : "vorlonjs.log"; + var exceptionFile = logConfig.exceptionsLogFileName ? logConfig.exceptionsLogFileName : "exceptions.log"; + + this.vorlonLogFile = path.join(filePath, vorlonjsFile); + this.exceptionsLogFile = path.join(filePath, exceptionFile); + this.enableConsole = logConfig.enableConsole; + this.level = logConfig.level ? logConfig.level : "warn"; + } + else { + this.vorlonLogFile = path.join(__dirname, "../vorlonjs.log"); + this.exceptionsLogFile = path.join(__dirname, "../exceptions.log"); + this.enableConsole = true; + this.level = "warn"; + } + } + } +} \ No newline at end of file diff --git a/Server/public/javascripts/css.js b/Server/public/javascripts/css.js new file mode 100644 index 00000000..e6d43251 --- /dev/null +++ b/Server/public/javascripts/css.js @@ -0,0 +1,672 @@ +/* jshint unused:false */ +/* global base64_decode, CSSWizardView, window, console, jQuery */ +(function(global) { + 'use strict'; + var fi = function() { + + this.cssImportStatements = []; + this.cssKeyframeStatements = []; + + this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi'); + this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together + this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)'; + this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi'); + }; + + /* + Strip outs css comments and returns cleaned css string + + @param css, the original css string to be stipped out of comments + + @return cleanedCSS contains no css comments + */ + fi.prototype.stripComments = function(cssString) { + var regex = new RegExp(this.cssCommentsRegex, 'gi'); + + return cssString.replace(regex, ''); + }; + + /* + Parses given css string, and returns css object + keys as selectors and values are css rules + eliminates all css comments before parsing + + @param source css string to be parsed + + @return object css + */ + fi.prototype.parseCSS = function(source) { + + if (source === undefined) { + return []; + } + + var css = []; + //strip out comments + //source = this.stripComments(source); + + //get import statements + + while (true) { + var imports = this.cssImportStatementRegex.exec(source); + if (imports !== null) { + this.cssImportStatements.push(imports[0]); + css.push({ + selector: '@imports', + type: 'imports', + styles: imports[0] + }); + } else { + break; + } + } + source = source.replace(this.cssImportStatementRegex, ''); + //get keyframe statements + var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi'); + var arr; + while (true) { + arr = keyframesRegex.exec(source); + if (arr === null) { + break; + } + css.push({ + selector: '@keyframes', + type: 'keyframes', + styles: arr[0] + }); + } + source = source.replace(keyframesRegex, ''); + + //unified regex + var unified = new RegExp(this.combinedCSSRegex, 'gi'); + + while (true) { + arr = unified.exec(source); + if (arr === null) { + break; + } + var selector = ''; + if (arr[2] === undefined) { + selector = arr[5].split('\r\n').join('\n').trim(); + } else { + selector = arr[2].split('\r\n').join('\n').trim(); + } + + /* + fetch comments and associate it with current selector + */ + var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi'); + var comments = commentsRegex.exec(selector); + if (comments !== null) { + selector = selector.replace(commentsRegex, '').trim(); + } + + //determine the type + if (selector.indexOf('@media') !== -1) { + //we have a media query + var cssObject = { + selector: selector, + type: 'media', + subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css + }; + if (comments !== null) { + cssObject.comments = comments[0]; + } + css.push(cssObject); + } else { + //we have standart css + var rules = this.parseRules(arr[6]); + var style = { + selector: selector, + rules: rules + }; + if (selector === '@font-face') { + style.type = 'font-face'; + } + if (comments !== null) { + style.comments = comments[0]; + } + css.push(style); + } + } + + return css; + }; + + /* + parses given string containing css directives + and returns an array of objects containing ruleName:ruleValue pairs + + @param rules, css directive string example + \n\ncolor:white;\n font-size:18px;\n + */ + fi.prototype.parseRules = function(rules) { + //convert all windows style line endings to unix style line endings + rules = rules.split('\r\n').join('\n'); + var ret = []; + + rules = rules.split(';'); + + //proccess rules line by line + for (var i = 0; i < rules.length; i++) { + var line = rules[i]; + + //determine if line is a valid css directive, ie color:white; + line = line.trim(); + if (line.indexOf(':') !== -1) { + //line contains : + line = line.split(':'); + var cssDirective = line[0].trim(); + var cssValue = line.slice(1).join(':').trim(); + + //more checks + if (cssDirective.length < 1 || cssValue.length < 1) { + continue; //there is no css directive or value that is of length 1 or 0 + // PLAIN WRONG WHAT ABOUT margin:0; ? + } + + //push rule + ret.push({ + directive: cssDirective, + value: cssValue + }); + } else { + //if there is no ':', but what if it was mis splitted value which starts with base64 + if (line.trim().substr(0, 7) == 'base64,') { //hack :) + ret[ret.length - 1].value += line.trim(); + } else { + //add rule, even if it is defective + if (line.length > 0) { + ret.push({ + directive: '', + value: line, + defective: true + }); + } + } + } + } + + return ret; //we are done! + }; + /* + just returns the rule having given directive + if not found returns false; + */ + fi.prototype.findCorrespondingRule = function(rules, directive, value) { + if (value === undefined) { + value = false; + } + var ret = false; + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive == directive) { + ret = rules[i]; + if (value === rules[i].value) { + break; + } + } + } + return ret; + }; + + /* + Finds styles that have given selector, compress them, + and returns them + */ + fi.prototype.findBySelector = function(cssObjectArray, selector, contains) { + if (contains === undefined) { + contains = false; + } + + var found = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (contains === false) { + if (cssObjectArray[i].selector === selector) { + found.push(cssObjectArray[i]); + } + } else { + if (cssObjectArray[i].selector.indexOf(selector) !== -1) { + found.push(cssObjectArray[i]); + } + } + + } + if (found.length < 2) { + return found; + } else { + var base = found[0]; + for (i = 1; i < found.length; i++) { + this.intelligentCSSPush([base], found[i]); + } + return [base]; //we are done!! all properties merged into base! + } + }; + + /* + deletes cssObjects having given selector, and returns new array + */ + fi.prototype.deleteBySelector = function(cssObjectArray, selector) { + var ret = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector !== selector) { + ret.push(cssObjectArray[i]); + } + } + return ret; + }; + + /* + Compresses given cssObjectArray and tries to minimize + selector redundence. + */ + fi.prototype.compressCSS = function(cssObjectArray) { + var compressed = []; + var done = {}; + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + if (done[obj.selector] === true) { + continue; + } + + var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed + if (found.length !== 0) { + compressed.push(found[0]); + done[obj.selector] = true; + } + } + return compressed; + }; + + /* + Received 2 css objects with following structure + { + rules : [{directive:"", value:""}, {directive:"", value:""}, ...] + selector : "SOMESELECTOR" + } + + returns the changed(new,removed,updated) values on css1 parameter, on same structure + + if two css objects are the same, then returns false + + if a css directive exists in css1 and css2, and its value is different, it is included in diff + if a css directive exists in css1 and not css2, it is then included in diff + if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED' + + @object css1 css object + @object css2 css object + + @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js + */ + fi.prototype.cssDiff = function(css1, css2) { + if (css1.selector !== css2.selector) { + return false; + } + + //if one of them is media query return false, because diff function can not operate on media queries + if ((css1.type === 'media' || css2.type === 'media')) { + return false; + } + + var diff = { + selector: css1.selector, + rules: [] + }; + var rule1, rule2; + for (var i = 0; i < css1.rules.length; i++) { + rule1 = css1.rules[i]; + //find rule2 which has the same directive as rule1 + rule2 = this.findCorrespondingRule(css2.rules, rule1.directive, rule1.value); + if (rule2 === false) { + //rule1 is a new rule in css1 + diff.rules.push(rule1); + } else { + //rule2 was found only push if its value is different too + if (rule1.value !== rule2.value) { + diff.rules.push(rule1); + } + } + } + + //now for rules exists in css2 but not in css1, which means deleted rules + for (var ii = 0; ii < css2.rules.length; ii++) { + rule2 = css2.rules[ii]; + //find rule2 which has the same directive as rule1 + rule1 = this.findCorrespondingRule(css1.rules, rule2.directive); + if (rule1 === false) { + //rule1 is a new rule + rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true + diff.rules.push(rule2); + } + } + + + if (diff.rules.length === 0) { + return false; + } + return diff; + }; + + /* + Merges 2 different css objects together + using intelligentCSSPush, + + @param cssObjectArray, target css object array + @param newArray, source array that will be pushed into cssObjectArray parameter + @param reverse, [optional], if given true, first parameter will be traversed on reversed order + effectively giving priority to the styles in newArray + */ + fi.prototype.intelligentMerge = function(cssObjectArray, newArray, reverse) { + if (reverse === undefined) { + reverse = false; + } + + + for (var i = 0; i < newArray.length; i++) { + this.intelligentCSSPush(cssObjectArray, newArray[i], reverse); + } + for (i = 0; i < cssObjectArray.length; i++) { + var cobj = cssObjectArray[i]; + if (cobj.type === 'media' || (cobj.type === 'keyframes')) { + continue; + } + cobj.rules = this.compactRules(cobj.rules); + } + }; + + /* + inserts new css objects into a bigger css object + with same selectors groupped together + + @param cssObjectArray, array of bigger css object to be pushed into + @param minimalObject, single css object + @param reverse [optional] default is false, if given, cssObjectArray will be reversly traversed + resulting more priority in minimalObject's styles + */ + fi.prototype.intelligentCSSPush = function(cssObjectArray, minimalObject, reverse) { + var pushSelector = minimalObject.selector; + //find correct selector if not found just push minimalObject into cssObject + var cssObject = false; + + if (reverse === undefined) { + reverse = false; + } + + if (reverse === false) { + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector === minimalObject.selector) { + cssObject = cssObjectArray[i]; + break; + } + } + } else { + for (var j = cssObjectArray.length - 1; j > -1; j--) { + if (cssObjectArray[j].selector === minimalObject.selector) { + cssObject = cssObjectArray[j]; + break; + } + } + } + + if (cssObject === false) { + cssObjectArray.push(minimalObject); //just push, because cssSelector is new + } else { + if (minimalObject.type !== 'media') { + for (var ii = 0; ii < minimalObject.rules.length; ii++) { + var rule = minimalObject.rules[ii]; + //find rule inside cssObject + var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive); + if (oldRule === false) { + cssObject.rules.push(rule); + } else if (rule.type == 'DELETED') { + oldRule.type = 'DELETED'; + } else { + //rule found just update value + + oldRule.value = rule.value; + } + } + } else { + cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too + } + + } + }; + + /* + filter outs rule objects whose type param equal to DELETED + + @param rules, array of rules + + @returns rules array, compacted by deleting all unneccessary rules + */ + fi.prototype.compactRules = function(rules) { + var newRules = []; + for (var i = 0; i < rules.length; i++) { + if (rules[i].type !== 'DELETED') { + newRules.push(rules[i]); + } + } + return newRules; + }; + /* + computes string for ace editor using this.css or given cssBase optional parameter + + @param [optional] cssBase, if given computes cssString from cssObject array + */ + fi.prototype.getCSSForEditor = function(cssBase, depth) { + if (depth === undefined) { + depth = 0; + } + var ret = ''; + if (cssBase === undefined) { + cssBase = this.css; + } + //append imports + for (var i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'imports') { + ret += cssBase[i].styles + '\n\n'; + } + } + for (i = 0; i < cssBase.length; i++) { + var tmp = cssBase[i]; + if (tmp.selector === undefined) { //temporarily omit media queries + continue; + } + var comments = ""; + if (tmp.comments !== undefined) { + comments = tmp.comments + '\n'; + } + + if (tmp.type == 'media') { //also put media queries to output + ret += comments + tmp.selector + '{\n'; + ret += this.getCSSForEditor(tmp.subStyles, depth + 1); + ret += '}\n\n'; + } else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') { + ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n'; + ret += this.getCSSOfRules(tmp.rules, depth + 1); + ret += this.getSpaces(depth) + '}\n\n'; + } + } + + //append keyFrames + for (i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'keyframes') { + ret += cssBase[i].styles + '\n\n'; + } + } + + return ret; + }; + + fi.prototype.getImports = function(cssObjectArray) { + var imps = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].type == 'imports') { + imps.push(cssObjectArray[i].styles); + } + } + return imps; + }; + /* + given rules array, returns visually formatted css string + to be used inside editor + */ + fi.prototype.getCSSOfRules = function(rules, depth) { + var ret = ''; + for (var i = 0; i < rules.length; i++) { + if (rules[i] === undefined) { + continue; + } + if (rules[i].defective === undefined) { + ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n'; + } else { + ret += this.getSpaces(depth) + rules[i].value + ';\n'; + } + + } + return ret || '\n'; + }; + + /* + A very simple helper function returns number of spaces appended in a single string, + the number depends input parameter, namely input*2 + */ + fi.prototype.getSpaces = function(num) { + var ret = ''; + for (var i = 0; i < num * 4; i++) { + ret += ' '; + } + return ret; + }; + + /* + Given css string or objectArray, parses it and then for every selector, + prepends this.cssPreviewNamespace to prevent css collision issues + + @returns css string in which this.cssPreviewNamespace prepended + */ + fi.prototype.applyNamespacing = function(css, forcedNamespace) { + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if(forcedNamespace !== undefined){ + namespaceClass = forcedNamespace; + } + + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + //bypass namespacing for @font-face @keyframes @import + if(obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1){ + continue; + } + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova + newSelector.push(namespaceClass + ' ' + selector[j]); + } else { + newSelector.push(selector[j]); + } + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well + } + } + + return cssObjectArray; + }; + + /* + given css string or object array, clears possible namespacing from + all of the selectors inside the css + */ + fi.prototype.clearNamespacing = function(css, returnObj) { + if (returnObj === undefined) { + returnObj = false; + } + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + newSelector.push(selector[j].split(namespaceClass + ' ').join('')); + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well + } + } + if (returnObj === false) { + return this.getCSSForEditor(cssObjectArray); + } else { + return cssObjectArray; + } + + }; + + /* + creates a new style tag (also destroys the previous one) + and injects given css string into that css tag + */ + fi.prototype.createStyleElement = function(id, css, format) { + if (format === undefined) { + format = false; + } + + if (this.testMode === false && format!=='nonamespace') { + //apply namespacing classes + css = this.applyNamespacing(css); + } + + if (typeof css != 'string') { + css = this.getCSSForEditor(css); + } + //apply formatting for css + if (format === true) { + css = this.getCSSForEditor(this.parseCSS(css)); + } + + if (this.testMode !== false) { + return this.testMode('create style #' + id, css); //if test mode, just pass result to callback + } + + var __el = document.getElementById( id ); + if(__el){ + __el.parentNode.removeChild( __el ); + } + + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'); + + style.id = id; + style.type = 'text/css'; + + head.appendChild(style); + + if (style.styleSheet && !style.sheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + }; + + global.cssjs = fi; + +})(this); diff --git a/Server/public/javascripts/dashboard.js b/Server/public/javascripts/dashboard.js index a4265d21..a7f33d3e 100644 --- a/Server/public/javascripts/dashboard.js +++ b/Server/public/javascripts/dashboard.js @@ -4,26 +4,29 @@ jQuery(function ($) { orientation: 'horizontal', limit: 38 }); - + + // Replace default url with the current one (running vorlon) + $('#scriptSrc').text(''.replace('{{location}}', location.origin).replace('{{baseURL}}', vorlonBaseURL)); + //Plugin tab navigation $('#pluginsPaneTop').on('click', '[data-plugin-target]', function (e) { var $this = $(this); var target = $this.data('plugin-target'); - + $('#pluginsPaneTop [data-plugin-target]').removeClass('active'); $this.addClass('active'); $('#pluginsPaneTop [data-plugin]') .hide() .trigger("vorlon.plugins.inactive"); - + $('#pluginsPaneTop [data-plugin~=' + target + ']') .show() .trigger("vorlon.plugins.active"); }); - + $('#pluginsPaneBottom').on('click', '[data-plugin-target]', function (e) { var target = $(this).data('plugin-target'); - + $('#pluginsPaneBottom [data-plugin-target]').removeClass('active'); $(this).addClass('active'); $('#pluginsPaneBottom [data-plugin]') diff --git a/Server/public/javascripts/socket.io-1.3.5.js b/Server/public/javascripts/socket.io-1.3.6.js similarity index 100% rename from Server/public/javascripts/socket.io-1.3.5.js rename to Server/public/javascripts/socket.io-1.3.6.js diff --git a/Server/public/stylesheets/components/base.styl b/Server/public/stylesheets/components/base.styl index 639697d6..516dcbee 100644 --- a/Server/public/stylesheets/components/base.styl +++ b/Server/public/stylesheets/components/base.styl @@ -37,6 +37,12 @@ h3 a color: $blue text-decoration: none + +.waitLoader + transform: scale(1.5) + +.waitLoader.hidden + display: none // Clearfix .cf diff --git a/Server/public/stylesheets/components/buttons.styl b/Server/public/stylesheets/components/buttons.styl index c9308d5a..aea17192 100644 --- a/Server/public/stylesheets/components/buttons.styl +++ b/Server/public/stylesheets/components/buttons.styl @@ -23,6 +23,14 @@ &:hover background: darken($grey-med, 10%) + &.btn-secondary1 + background: $grey-med + color: $grey-drk + visibility: hidden + + &:hover + background: darken($grey-med, 10%) + &.btn-small padding: 2px 5px font-size: 90% diff --git a/Server/public/stylesheets/components/dashboard.styl b/Server/public/stylesheets/components/dashboard.styl index 59989612..19484614 100644 --- a/Server/public/stylesheets/components/dashboard.styl +++ b/Server/public/stylesheets/components/dashboard.styl @@ -59,10 +59,7 @@ max-width: 100% max-height: 120px margin-bottom: 1em - transition: transform - transition-duration: 700ms - transition-timing: ease-out - transform: scale(1) + h1 font-size: 36px color: #6b2d81 @@ -81,6 +78,31 @@ .logo transform: scale(1.3) +@keyframes bounce { + 0% { + transform:translateY(0%); + } + 80% { + transform:translateY(-15%); + } + 90% { + transform:translateY(0%); + } + 95% { + transform:translateY(-4%); + } + 100% { + transform:translateY(0); + opacity: 1; + } +} + +.dashboard-plugins-overlay.bounce + pointer-events: none + .message + .logo + animation: bounce 1.2s ease infinite + .dashboard-plugins height: 100% // Panel positions and default sizing diff --git a/Server/public/stylesheets/components/login.styl b/Server/public/stylesheets/components/login.styl new file mode 100644 index 00000000..af7ef09a --- /dev/null +++ b/Server/public/stylesheets/components/login.styl @@ -0,0 +1,50 @@ + +.login-container + position: absolute + left:0 + top: 0 + right: 0 + bottom: 0 + background-color: #3C1942 + z-index: 1000 + opacity : 1 + transition: opacity + transition-duration: 700ms + transition-delay: 200ms + transition-timing: ease-out + .message + background-color: #DDD + text-align:center + max-width:300px + padding: 30px + padding-bottom: 40px + margin: auto + margin-top: calc(25% - 100px) + .logo + max-width: 100% + max-height: 80px + margin-bottom: 1em + transition: transform + transition-duration: 700ms + transition-timing: ease-out + transform: scale(1) + h1 + font-size: 1em + color: #6b2d81 + form + width: 200px + margin-left:auto + margin-right: auto + text-align: left + input + width: 100% + margin-bottom: 10px + border: 0 + padding: 10px + button + margin-top: 10px + width: 200px + background-color: #6b2d81 + color: white + border:0 + padding: 10px \ No newline at end of file diff --git a/Server/public/stylesheets/httpproxy.css b/Server/public/stylesheets/httpproxy.css new file mode 100644 index 00000000..ad75df34 --- /dev/null +++ b/Server/public/stylesheets/httpproxy.css @@ -0,0 +1,76 @@ +body { + background-color: #eee; + font-family: Roboto; +} +.home .top { + margin: calc(15% - 100px) auto auto; + text-align: center; +} +.home .top .logo { + max-width: 100%; + max-height: 120px; + margin-bottom: 1em; +} +.home .form { + margin: auto; + text-align: center; + width: 400px; +} + +.home .form .input input { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.home .message { + color: #777; + text-align: center; + margin-bottom: 10px; + text-align: justify; +} + +.home .message.error { + color: red; +} + +.home .form .input input { + padding: 6px 12px; + border-color: #411748; + width: 100%; +} +.home .form .button { + margin: auto; +} +.spacebutton { + width: 100%; +} +.btn { + border: none; + cursor: pointer; + display: inline-block; + font-weight: 400; + margin: 10px 0 0 0; + padding: 8px 10px; + text-align: center; + width: 100%; + background-color: #411748 !important; + color: white !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.btn.left{ + float: left; +} +.btn.right{ + float: right; +} +.btn-default { + background: #ccc none repeat scroll 0 0; + color: #444; +} + +.hide { + display: none; +} \ No newline at end of file diff --git a/Server/public/stylesheets/style.css b/Server/public/stylesheets/style.css deleted file mode 100644 index 33f2ea74..00000000 --- a/Server/public/stylesheets/style.css +++ /dev/null @@ -1,517 +0,0 @@ -html, -body, -div, -span, -applet, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section { - display: block; -} -body { - line-height: 1; -} -ol, -ul { - list-style: none; -} -blockquote, -q { - quotes: none; -} -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} -*, -*:after, -*:before { - box-sizing: border-box; -} -html { - font-size: 100%; - text-size-adjust: none; -} -body { - background: white; - text-rendering: optimizeLegibility; - font-smoothing: antialiased; - font-family: 'Roboto', Helvetica, Arial, sans-serif; - font-weight: 300; - font-size: 16px; - line-height: 1.4; - color: #444; -} -h1:not(.logo), -h2, -h3 { - margin-bottom: 15px; - font-family: 'Oswald', Helvetica, Arial, sans-serif; - font-weight: 400; - line-height: 1.2; - text-transform: uppercase; - letter-spacing: 0.5px; -} -h1:not(.logo) { - font-size: 18px; -} -h2 { - font-size: 16px; -} -h3 { - font-size: 14px; -} -a { - color: #007ca7; - text-decoration: none; -} -.cf:before, -.cf:after { - content: " "; - display: table; -} -.cf:after { - clear: both; -} -.btn { - margin: 10px 0; - display: inline-block; - border-radius: 3px; - font-weight: 400; - text-align: center; - cursor: pointer; - padding: 8px 10px; -} -.btn.btn-primary { - background: #00b7c7; - color: white; -} -.btn.btn-primary:hover { - background: #00a5b3; -} -.btn.btn-secondary { - background: #ccc; - color: #444; -} -.btn.btn-secondary:hover { - background: #b8b8b8; -} -.btn.btn-small { - padding: 2px 5px; - font-size: 90%; -} -/* - * StyleSheet for JQuery splitter Plugin - * Copyright (C) 2010 Jakub Jankiewicz - * - * Same license as plugin - */ -.splitter_panel { - position: relative; -} -.splitter_panel .vsplitter { - cursor: col-resize; - z-index: 900; - width: 5px; - background-color: white; - border-right: 1px solid #ddd; - border-left: 1px solid #ddd; -} -.splitter_panel .hsplitter { - cursor: row-resize; - z-index: 800; - height: 8px; - background: white url("../images/icon-resize-horiz.svg") no-repeat center center; - background-size: auto 3px; - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; -} -.splitter_panel .vsplitter.splitter-invisible, -.splitter_panel .hsplitter.splitter-invisible { - background: none; -} -.splitter_panel .vsplitter, -.splitter_panel .left_panel, -.splitter_panel .right_panel, -.splitter_panel .hsplitter, -.splitter_panel .top_panel, -.splitter_panel .bottom_panel { - position: absolute; - overflow: auto; -} -.splitter_panel .vsplitter, -.splitter_panel .left_panel, -.splitter_panel .right_panel { - height: 100%; -} -.splitter_panel .hsplitter, -.splitter_panel .top_panel, -.splitter_panel .bottom_panel { - width: 100%; -} -.splitter_panel .top_panel, -.splitter_panel .left_panel, -.splitter_panel .right_panel, -.splitter_panel .vsplitter { - top: 0; -} -.splitter_panel .top_panel, -.splitter_panel .bottom_panel, -.splitter_panel .left_panel, -.splitter_panel .hsplitter { - left: 0; -} -.splitter_panel .bottom_panel { - bottom: 0; -} -.splitter_panel .right_panel { - right: 0; -} -.splitterMask { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - z-index: 1000; - cursor: default; -} -.sidebar { - width: 200px; - height: 100vh; - padding-bottom: 20px; - position: absolute; - top: 0; - left: 0; - overflow: auto; - background: #eee; - border-right: 1px solid #ddd; -} -.sidebar .sidebar-header { - background: #1d0c3d; - color: white; -} -.sidebar .logo { - height: 40px; - background: url("../images/vorlon-logo-white.svg") no-repeat center center; - background-size: auto 20px; - text-indent: -9999em; -} -.sidebar .session-info { - padding: 15px; - background: #6b2d81; - font-weight: 400; -} -.sidebar .session-info p span { - font-weight: 300; -} -.sidebar .client-pane { - padding: 15px; -} -.sidebar .client-pane h2 { - margin-bottom: 5px; -} -.sidebar .client-pane .btn { - width: 100%; - margin: 0 0 15px; -} -.sidebar .client-pane .client { - margin: 0 -15px; - display: block; - font-size: 15px; - color: #444; -} -.sidebar .client-pane .client.active { - background-color: white; - font-weight: 400; -} -.sidebar .client-pane .client a { - display: block; - padding: 2px 15px; -} -.sidebar .client-pane .btn-reset { - margin-top: 10px; -} -.dashboard { - min-width: 700px; - min-height: 350px; - padding-left: 200px; - overflow-y: hidden; -} -.dashboard-initial { - height: 100vh; - position: relative; -} -.dashboard-initial .get-started { - max-width: 300px; - padding: 30px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border: 1px solid #ddd; - border-radius: 10px; - text-align: center; -} -.dashboard-plugins-container { - height: 100vh; - position: relative; -} -.dashboard-plugins-container .expandBtn { - height: 24px; - width: 30px; - float: right; - text-align: center; - line-height: 24px; - border: 1px solid #ccc; - margin-right: 5px; - margin-top: 3px; - border-radius: 3px; - cursor: pointer; - color: #999; -} -.dashboard-plugins-overlay { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - background-color: #ddd; - z-index: 1000; - opacity: 1; - transition: opacity; - transition-duration: 700ms; - transition-delay: 200ms; - transition-timing: ease-out; - font-size: 16px; -} -.dashboard-plugins-overlay .message { - text-align: center; - max-width: 80%; - margin: auto; - margin-top: calc(25% - 100px); -h4 -} -.dashboard-plugins-overlay .message .logo { - max-width: 100%; - max-height: 120px; - margin-bottom: 1em; - transition: transform; - transition-duration: 700ms; - transition-timing: ease-out; - transform: scale(1); -} -.dashboard-plugins-overlay .message h1 { - font-size: 36px; - color: #6b2d81; -} -.dashboard-plugins-overlay .message h3 { - font-size: 16px; - color: #1d0c3d; - margin-top: 0.5em; - margin-bottom: 0.5em; -} -.dashboard-plugins-overlay.hidden { - opacity: 0; - pointer-events: none; -} -.dashboard-plugins-overlay.hidden .message .logo { - transform: scale(1.3); -} -.dashboard-plugins { - height: 100%; -} -.dashboard-plugins .panel-top, -.dashboard-plugins .panel-bottom { - position: relative; - overflow: hidden !important; - height: 400px; -} -.dashboard-plugins .panel-bottom { - margin-top: 8px; -} -.dashboard-plugins .tab-bar { - height: 30px; - background: #eee; - border-bottom: 1px solid #ddd; - font-weight: 400; -} -.dashboard-plugins .code-text { - font-family: 'Menlo', 'Consolas', 'Lucida Console', Courier, monospace; - font-size: 12px; - line-height: 1.5; -} -.dashboard-plugins .dashboard-button { - border: none; - background-color: #ddd; - border-radius: 3px; - color: #444; -} -.dashboard-plugins .dashboard-button:hover { - background-color: #bbb; -} -.dashboard-plugins .dashboard-input { - padding: 0 0.3em; - border: 1px solid #ddd; - border-radius: 3px; -} -:focus { - outline: none; - border-color: #00b7c7; -} -.tab { - height: 100%; - padding: 4px 10px; - position: relative; - display: block; - float: left; -} -.tab:not(.active) { - cursor: pointer; - opacity: 0.5; -} -.tab:not(.active):hover { - opacity: 0.9; -} -.tab:not(.add) { - background: white; - border-right: 1px solid #ddd; -} -.plugin { - width: 100%; - height: calc(100% - 30px); - overflow: auto; - display: none; -} -.active { - display: block; -} -x-notify { - z-index: 1001; -} -x-controlbar > *, -x-controlbar input { - border-radius: 0.25em; -} -.no-anim { - -webkit-animation: none; - animation: none; -} -input[type=text]::-webkit-input-placeholder { - color: #aaa; - font-style: italic; -} -input[type=text]:-moz-placeholder { - color: #aaa; - font-style: italic; -} -input[type=text]::-moz-placeholder { - color: #aaa; - font-style: italic; -} -input[type=text]:-ms-input-placeholder { - color: #aaa; - font-style: italic; -} -input[type=text]::placeholder { - color: #aaa; - font-style: italic; -} diff --git a/Server/public/stylesheets/style.styl b/Server/public/stylesheets/style.styl index cbb34243..cba67955 100644 --- a/Server/public/stylesheets/style.styl +++ b/Server/public/stylesheets/style.styl @@ -6,3 +6,4 @@ @import 'components/resize' @import 'components/sidebar' @import 'components/dashboard' +@import 'components/login' diff --git a/Server/public/vorlon.dashboardManager.ts b/Server/public/vorlon.dashboardManager.ts index aa4d191f..3b72bc71 100644 --- a/Server/public/vorlon.dashboardManager.ts +++ b/Server/public/vorlon.dashboardManager.ts @@ -1,39 +1,208 @@ /// /// -/// +/// + + module VORLON { declare var $: any; + declare var vorlonBaseURL: string; + export class DashboardManager { - private _catalogUrl: string = "/config.json"; + static CatalogUrl: string; static ListenClientid: string; + static DisplayingClient: boolean; static ListenClientDisplayid: string; static SessionId: string; - static ClientList: Array; + static ClientList: any; + static PluginsLoaded: boolean; + constructor(sessionid: string, listenClientid: string) { + //Dashboard session id DashboardManager.SessionId = sessionid; + DashboardManager.PluginsLoaded = false; + DashboardManager.DisplayingClient = false; + //Client ID DashboardManager.ListenClientid = listenClientid; - DashboardManager.ClientList = new Array(); - DashboardManager.RefreshClients(); - this.loadPlugins(); + DashboardManager.ClientList = {}; + DashboardManager.StartListeningServer() + DashboardManager.GetClients(); + DashboardManager.CatalogUrl = vorlonBaseURL + "/config.json"; } + + public static StartListeningServer(clientid: string = ""): void{ + var getUrl = window.location; + var baseUrl = getUrl.protocol + "//" + getUrl.host; + Core.StopListening(); + Core.StartDashboardSide(baseUrl, DashboardManager.SessionId, clientid, DashboardManager.divMapper); + if(!Core.Messenger.onAddClient && !Core.Messenger.onAddClient){ + Core.Messenger.onAddClient = DashboardManager.addClient; + Core.Messenger.onRemoveClient = DashboardManager.removeClient; + } + + if(clientid !== ""){ + DashboardManager.DisplayingClient = true; + } + else { + DashboardManager.DisplayingClient = false; + } + } + + public static GetClients(): void { + let xhr = new XMLHttpRequest(); - static UpdateClientInfo() { - document.querySelector('[data-hook~=session-id]').textContent = DashboardManager.SessionId; - for (var i = 0; i < DashboardManager.ClientList.length; i++) { - if (DashboardManager.ClientList[i].clientid === DashboardManager.ListenClientid) { - DashboardManager.ListenClientDisplayid = DashboardManager.ClientList[i].displayid; + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + //Init ClientList Object + DashboardManager.ClientList = {}; + document.getElementById('test').style.visibility='hidden'; + + //Loading client list + var clients = JSON.parse(xhr.responseText); + + //Test if the client to display is in the list + var contains = false; + if (clients && clients.length) { + for (var j = 0; j < clients.length; j++) { + if (clients[j].clientid === DashboardManager.ListenClientid) { + contains = true; + break; + } + } + } + + //Get the client list placeholder + var divClientsListPane = document.getElementById("clientsListPaneContent"); + + //Create the new empty list + var clientlist = document.createElement("ul"); + clientlist.setAttribute("id", "clientsListPaneContentList") + divClientsListPane.appendChild(clientlist); + + //Show waiting logo + if(!contains || clients.length === 0){ + var elt = document.querySelector('.dashboard-plugins-overlay'); + VORLON.Tools.RemoveClass(elt, 'hidden'); + } + + for (var i = 0; i < clients.length; i++) { + var client = clients[i]; + DashboardManager.AddClientToList(client); + } + + if (contains) { + DashboardManager.loadPlugins(); + } + } + } + } + + xhr.open("GET", vorlonBaseURL + "/api/getclients/" + DashboardManager.SessionId); + xhr.send(); + } + + public static AddClientToList(client: any){ + var clientlist = document.getElementById("clientsListPaneContentList"); + + if (DashboardManager.ListenClientid === "") { + DashboardManager.ListenClientid = client.clientid; + } + + var pluginlistelement = document.createElement("li"); + pluginlistelement.classList.add('client'); + pluginlistelement.id = client.clientid; + if (client.clientid === DashboardManager.ListenClientid) { + pluginlistelement.classList.add('active'); + } + + var clients = clientlist.children; + + //remove ghosts ones + for (var i = 0; i < clients.length; i++) { + var currentClient = (clients[i]); + if(DashboardManager.ClientList[currentClient.id].displayid === client.displayid){ + clientlist.removeChild(currentClient); + i--; + } + } + + if(clients.length === 0 || DashboardManager.ClientList[(clients[clients.length - 1]).id].displayid < client.displayid){ + clientlist.appendChild(pluginlistelement); + } + else if(clients.length === 1){ + var firstClient = clients[clients.length - 1]; + clientlist.insertBefore(pluginlistelement, firstClient); + } + else{ + for (var i = 0; i < clients.length - 1; i++) { + var currentClient = (clients[i]); + var nextClient = (clients[i+1]); + if(DashboardManager.ClientList[currentClient.id].displayid < client.displayid + && DashboardManager.ClientList[nextClient.id].displayid >= client.displayid){ + clientlist.insertBefore(pluginlistelement, nextClient); + break; + } + else if(i === 0){ + clientlist.insertBefore(pluginlistelement, currentClient); + } } } + + var pluginlistelementa = document.createElement("a"); + pluginlistelementa.textContent = " " + client.name + " - " + client.displayid; + pluginlistelementa.setAttribute("href", vorlonBaseURL + "/dashboard/" + DashboardManager.SessionId + "/" + client.clientid); + pluginlistelement.appendChild(pluginlistelementa); + + DashboardManager.ClientList[client.clientid] = client; + } + + static ClientCount(): number{ + return Object.keys(DashboardManager.ClientList).length; + } + + static UpdateClientInfo(): void { + document.querySelector('[data-hook~=session-id]').textContent = DashboardManager.SessionId; + + if(DashboardManager.ClientList[DashboardManager.ListenClientid] != null){ + DashboardManager.ListenClientDisplayid = DashboardManager.ClientList[DashboardManager.ListenClientid].displayid; + } + document.querySelector('[data-hook~=client-id]').textContent = DashboardManager.ListenClientDisplayid; } + + static DisplayWaitingLogo(): void{ + //Hide waiting page and let's not bounce the logo ! + var elt = document.querySelector('.dashboard-plugins-overlay'); + VORLON.Tools.RemoveClass(elt, 'hidden'); + } + + static DisplayBouncingLogo(): void{ + //Hide waiting page and let's not bounce the logo ! + var elt = document.querySelector('.dashboard-plugins-overlay'); + VORLON.Tools.RemoveClass(elt, 'hidden'); + elt = document.querySelector('.waitLoader'); + VORLON.Tools.RemoveClass(elt, 'hidden'); + } - public loadPlugins(): void { - var xhr = new XMLHttpRequest(); - var divPluginsBottom = document.getElementById("pluginsPaneBottom"); - var divPluginsTop = document.getElementById("pluginsPaneTop"); - var divPluginBottomTabs = document.getElementById("pluginsListPaneBottom"); - var divPluginTopTabs = document.getElementById("pluginsListPaneTop"); - var coreLoaded = false; + public static loadPlugins(): void { + if(DashboardManager.ListenClientid === ""){ + return; + } + + if(this.PluginsLoaded){ + DashboardManager.StartListeningServer(DashboardManager.ListenClientid); + return; + } + + let xhr = new XMLHttpRequest(); + let divPluginsBottom = document.getElementById("pluginsPaneBottom"); + let divPluginsTop = document.getElementById("pluginsPaneTop"); + let divPluginBottomTabs = document.getElementById("pluginsListPaneBottom"); + let divPluginTopTabs = document.getElementById("pluginsListPaneTop"); + let coreLoaded = false; + + //Hide waiting page and let's bounce the logo ! + DashboardManager.DisplayBouncingLogo(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { @@ -91,19 +260,14 @@ module VORLON { } } var pluginscript = document.createElement("script"); - pluginscript.setAttribute("src", "/vorlon/plugins/" + plugin.foldername + "/vorlon." + plugin.foldername + ".dashboard.min.js"); + pluginscript.setAttribute("src", vorlonBaseURL + "/vorlon/plugins/" + plugin.foldername + "/vorlon." + plugin.foldername + ".dashboard.min.js"); pluginscript.onload = (oError) => { pluginLoaded++; if (pluginLoaded >= pluginstoload) { - var getUrl = window.location; - var baseUrl = getUrl.protocol + "//" + getUrl.host; - Core.StartDashboardSide(baseUrl, DashboardManager.SessionId, DashboardManager.ListenClientid, this.divMapper); - if (!coreLoaded && !Core.Messenger.onWaitingEventsReceived) { - Core.Messenger.onWaitingEventsReceived = this._onClientUpdateWaitingEvents; - Core.Messenger.onRefreshClients = this._onRefreshClients; - coreLoaded = true; - } + DashboardManager.StartListeningServer(DashboardManager.ListenClientid); + coreLoaded = true; + this.PluginsLoaded = true; } }; document.body.appendChild(pluginscript); @@ -134,95 +298,28 @@ module VORLON { divPluginsTop.style.height = 'calc(100% - 58px)'; $('.hsplitter', divPluginsTop.parentElement).css('top', 'calc(100% - 58px)'); }); + DashboardManager.UpdateClientInfo(); } } } - xhr.open("GET", this._catalogUrl); + xhr.open("GET", DashboardManager.CatalogUrl); xhr.send(); } - public divMapper(pluginId: string): HTMLDivElement { - var divId = pluginId + "div"; + public static divMapper(pluginId: string): HTMLDivElement { + let divId = pluginId + "div"; return (document.getElementById(divId) || document.querySelector(`[data-plugin=${pluginId}]`)); } - public static RefreshClients(): void { - var xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - DashboardManager.ClientList = new Array(); - - var clients = JSON.parse(xhr.responseText); - //console.log("dashboard clients ", clients); - - var divClientsListPane = document.getElementById("clientsListPaneContent"); - - while (divClientsListPane.hasChildNodes()) { - divClientsListPane.removeChild(divClientsListPane.lastChild); - } - - var clientlist = document.createElement("ul"); - divClientsListPane.appendChild(clientlist); - - var contains = false; - if (clients && clients.length) { - for (var j = 0; j < clients.length; j++) { - if (clients[j].clientid === DashboardManager.ListenClientid) { - contains = true; - break; - } - } - } - if (!contains || clients.length === 0) { - var elt = document.querySelector('.dashboard-plugins-overlay'); - VORLON.Tools.RemoveClass(elt, 'hidden'); - } - - if (clients.length === 0) { - DashboardManager.ResetDashboard(false); - } - - for (var i = 0; i < clients.length; i++) { - var client = clients[i]; - if (DashboardManager.ListenClientid === "") { - DashboardManager.ListenClientid = client.clientid; - } - - var pluginlistelement = document.createElement("li"); - pluginlistelement.classList.add('client'); - if (client.clientid === DashboardManager.ListenClientid) { - pluginlistelement.classList.add('active'); - } - clientlist.appendChild(pluginlistelement); - - var pluginlistelementa = document.createElement("a"); - pluginlistelementa.textContent = " " + client.name + " - " + client.displayid; - pluginlistelementa.setAttribute("href", "/dashboard/" + DashboardManager.SessionId + "/" + client.clientid); - pluginlistelementa.id = client.clientid; - pluginlistelement.appendChild(pluginlistelementa); - - DashboardManager.ClientList.push(client); - DashboardManager.UpdateClientWaitingInfo(client.clientid, client.waitingevents); - } - } - } - } - - xhr.open("GET", "/api/getclients/" + DashboardManager.SessionId); - xhr.send(); - } - public identify(): void { Core.Messenger.sendRealtimeMessage("", { "_sessionid": DashboardManager.SessionId }, VORLON.RuntimeSide.Dashboard, "identify"); } public static ResetDashboard(reload: boolean = true): void { - var sessionid = DashboardManager.SessionId; - var xhr = new XMLHttpRequest(); + let sessionid = DashboardManager.SessionId; + let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { @@ -233,43 +330,60 @@ module VORLON { } } - xhr.open("GET", "/api/reset/" + sessionid); + xhr.open("GET", vorlonBaseURL + "/api/reset/" + sessionid); xhr.send(); } - private _onRefreshClients(): void { - DashboardManager.RefreshClients(); + public static ReloadClient(): void { + Core.Messenger.sendRealtimeMessage("", DashboardManager.ListenClientid, VORLON.RuntimeSide.Dashboard, "reload"); } - private _onClientUpdateWaitingEvents(message: VorlonMessage): void { - DashboardManager.UpdateClientWaitingInfo(message.metadata.clientId, message.metadata.waitingEvents); + public static addClient(client: any): void { + DashboardManager.AddClientToList(client); + if(!DashboardManager.DisplayingClient){ + DashboardManager.loadPlugins(); + } } - - public static UpdateClientWaitingInfo(clientid: string, waitingevents: number): void { - var clientLink = document.getElementById(clientid); - for (var id in DashboardManager.ClientList) { - var client = DashboardManager.ClientList[id]; - if (client.clientid === clientid) { - clientLink.textContent = " " + client.name + " - " + client.displayid + " (" + waitingevents + ")"; + + public static removeClient(client: any): void { + let clientInList = document.getElementById(client.clientid); + if(clientInList){ + if(client.clientid === DashboardManager.ListenClientid){ + DashboardManager.ListenClientid = ""; + DashboardManager.StartListeningServer(); + DashboardManager.DisplayWaitingLogo(); + } + + clientInList.parentElement.removeChild(clientInList); + DashboardManager.removeInClientList(client); + + if (DashboardManager.ClientCount() === 0) { + DashboardManager.ResetDashboard(false); + DashboardManager.DisplayingClient = false; } } } + + public static removeInClientList(client: any): void{ + if(DashboardManager.ClientList[client.clientid] != null){ + delete DashboardManager.ClientList[client.clientid]; + } + } public static getSessionId(): void { - var xhr = new XMLHttpRequest(); + let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { var sessionId = xhr.responseText; - window.location.assign("/dashboard/" + sessionId); + window.location.assign(vorlonBaseURL + "/dashboard/" + sessionId); } } } - xhr.open("GET", "/api/createsession"); + xhr.open("GET", vorlonBaseURL + "/api/createsession"); xhr.send(); } } } - diff --git a/Server/server.ts b/Server/server.ts index f2d11f4c..589e2c3d 100644 --- a/Server/server.ts +++ b/Server/server.ts @@ -1,16 +1,31 @@ -import vorlonServer = require("./Scripts/vorlon.server"); +import httpConfig = require("./config/vorlon.httpconfig"); +import vorlonServer = require("./Scripts/vorlon.server"); import vorlonDashboard = require("./Scripts/vorlon.dashboard"); import vorlonWebserver = require("./Scripts/vorlon.webServer"); +import vorlonHttpProxy = require("./Scripts/vorlon.httpproxy.server"); -//WEBSERVER -var webServer = new vorlonWebserver.VORLON.WebServer(); +var config = new httpConfig.VORLON.HttpConfig(); +// if proxyEnvPort==true start a standalone instance of httpProxy +if (!config.proxyEnvPort) { + //WEBSERVER + var webServer = new vorlonWebserver.VORLON.WebServer(); -//DASHBOARD -var dashboard = new vorlonDashboard.VORLON.Dashboard(); + //DASHBOARD + var dashboard = new vorlonDashboard.VORLON.Dashboard(); -//VORLON SERVER -var server = new vorlonServer.VORLON.Server(); + //VORLON SERVER + var server = new vorlonServer.VORLON.Server(); -webServer.components.push(dashboard); -webServer.components.push(server); -webServer.start(); + //VORLON HTTPPROXY + var HttpProxy = new vorlonHttpProxy.VORLON.HttpProxy(false); + + webServer.components.push(dashboard); + webServer.components.push(server); + webServer.components.push(HttpProxy); + webServer.start(); +} +else { + + var serverProxy = new vorlonHttpProxy.VORLON.HttpProxy(true); + serverProxy.start(); +} diff --git a/Server/views/dashboard.jade b/Server/views/dashboard.jade index ad6a0c18..f91dffa8 100644 --- a/Server/views/dashboard.jade +++ b/Server/views/dashboard.jade @@ -4,17 +4,16 @@ block content .dashboard.cf include includes/sidebar - include includes/dashboard-plugins - + block scripts - script(src='/javascripts/socket.io-1.3.5.js') - script(src='/javascripts/x-tag-components.js') - script(src='/vorlon/vorlon-noplugin.max.js') - script(src='/vorlon.dashboardManager.js') + script(src=baseURL + '/javascripts/socket.io-1.3.6.js') + script(src=baseURL + '/javascripts/x-tag-components.js') + script(src=baseURL + '/vorlon/vorlon-noplugin.max.js') + script(src=baseURL + '/vorlon.dashboardManager.js') script!='var vorlonDashboard = new VORLON.DashboardManager("' + sessionid + '", "' + clientid + '");' - script(src='/javascripts/jquery.splitter-0.14.0.js') - script(src='/javascripts/dashboard.js') - script(src='/javascripts/jquery-ui.js' type="text/javascript") - script(src='/javascripts/contextMenu.js' type="text/javascript") - script(src='/javascripts/jquery.switchButton.js' type="text/javascript") \ No newline at end of file + script(src=baseURL + '/javascripts/jquery.splitter-0.14.0.js') + script(src=baseURL + '/javascripts/dashboard.js') + script(src=baseURL + '/javascripts/jquery-ui.js' type="text/javascript") + script(src=baseURL + '/javascripts/contextMenu.js' type="text/javascript") + script(src=baseURL + '/javascripts/jquery.switchButton.js' type="text/javascript") diff --git a/Server/views/getsession.jade b/Server/views/getsession.jade index e286b373..ce75a2b0 100644 --- a/Server/views/getsession.jade +++ b/Server/views/getsession.jade @@ -6,5 +6,5 @@ block content a#createSessionButton.btn.btn-default.btn-getsession(onclick='VORLON.DashboardManager.getSessionId()') Create Session block scripts - script(src='/javascripts/socket.io-1.3.5.js') - script(src='/vorlon.dashboardManager.js') \ No newline at end of file + script(src=baseURL + '/javascripts/socket.io-1.3.6.js') + script(src=baseURL + '/vorlon.dashboardManager.js') diff --git a/Server/views/httpproxy.jade b/Server/views/httpproxy.jade new file mode 100644 index 00000000..d8d507c1 --- /dev/null +++ b/Server/views/httpproxy.jade @@ -0,0 +1,113 @@ +doctype html +html(lang='en') + head + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge') + meta(name='viewport', content='width=device-width, initial-scale=1') + meta(name='description', content='') + meta(name='author', content='') + title Vorlonjs HTTP Proxy + link(rel='stylesheet', href='/stylesheets/httpproxy.css') + link(href='https://fonts.googleapis.com/css?family=Oswald:400' rel='stylesheet') + link(href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,300|Roboto:400,300' rel='stylesheet') + script(src='/javascripts/jquery-2.1.1.min.js' type="text/javascript") + body + + div.home + div.top + img.logo(src="/images/VorlonLogo_Smooth.svg") + div.form + div.message Vorlon proxy allows you to use Vorlon on an existing website. Just enter the website url, and click on the button. Couldn't be easier ! + div.message Please note that, for now, the proxy will not work with websites using https. + div.input + input#url(type='text', placeholder='enter website url') + div.button + div.spacebutton + a#inject.btn.btn-default.left Inspect with VORLON.JS + a#openSite.btn.btn-default.left Open website only + a#openDashboard.btn.btn-default.left Open dashboard only + a#error.message.left.error.hide URL is not valid + + script(type='text/javascript'). + $(document).ready(function() { + function getProxyData(url, callback){ + $.ajax({ + type: "GET", + url: "/HttpProxy/inject?url=" + encodeURIComponent(url) + "&ts=" + new Date(), + success: function (data) { + callback(JSON.parse(data)); + }, + }); + } + + function openDashboard(url){ + var pat = /^(https?:\/\/)?(?:www\.)?([^\/]+)/; + var match = url.match(pat); + var target = match[2].replace('.', ''); + window.open("/dashboard/" + target); + } + + function inject(){ + var url = urlinput.value; + if (checkUrl()) { + getProxyData(url, function(data){ + window.open("/dashboard/" + data.session); + setTimeout(function(){ + window.open(data.url); + }, 200); + }); + } + } + + function checkUrl() { + var url = urlinput.value; + if (url && url.length) { + if (url.match(/^(http[s]?:\/\/)/)) { + document.querySelector("#error").classList.add("hide"); + return true; + } else { + document.querySelector("#error").classList.remove("hide"); + return false; + } + } + } + + var urlinput = document.querySelector("#url"); + + var btninject = document.querySelector("#inject"); + btninject.addEventListener("click", function () { + inject(); + }); + + var btnopenSite = document.querySelector("#openSite"); + btnopenSite.addEventListener("click", function () { + var url = urlinput.value; + if (checkUrl()) { + getProxyData(url, function(data){ + window.open(data.url); + }); + } + }); + + var btnopenDashboard = document.querySelector("#openDashboard"); + btnopenDashboard.addEventListener("click", function () { + var url = urlinput.value; + if (checkUrl()) { + getProxyData(url, function(data){ + window.open("/dashboard/" + data.session); + }); + } + }); + + urlinput.addEventListener("keypress", function(e){ + var key = e.which ? e.which : e.keyCode; + if (key == 13) { + e.preventDefault(); + inject(); + } + }); + + urlinput.addEventListener("blur", function (e) { + checkUrl(); + }); + }); \ No newline at end of file diff --git a/Server/views/httpproxyerror.jade b/Server/views/httpproxyerror.jade new file mode 100644 index 00000000..636accb4 --- /dev/null +++ b/Server/views/httpproxyerror.jade @@ -0,0 +1,20 @@ +doctype html +html(lang='en') + head + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge') + meta(name='viewport', content='width=device-width, initial-scale=1') + meta(name='description', content='') + meta(name='author', content='') + title Vorlonjs HTTP Proxy Error + link(rel='stylesheet', href='/stylesheets/httpproxy.css') + link(href='https://fonts.googleapis.com/css?family=Oswald:400' rel='stylesheet') + link(href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,300|Roboto:400,300' rel='stylesheet') + script(src='/javascripts/jquery-2.1.1.min.js' type="text/javascript") + body + + div.home + div.top + img.logo(src="/images/VorlonLogo_Smooth.svg") + div.form + div.message error \ No newline at end of file diff --git a/Server/views/includes/dashboard-plugins.jade b/Server/views/includes/dashboard-plugins.jade index bab23df1..1ece35cc 100644 --- a/Server/views/includes/dashboard-plugins.jade +++ b/Server/views/includes/dashboard-plugins.jade @@ -6,9 +6,12 @@ section.dashboard-plugins-container section.panel-bottom#pluginsPaneBottom header.tab-bar.cf#pluginsListPaneBottom section.dashboard-plugins-overlay - div.message - img.logo(src="/images/VorlonLogo_Smooth.svg") + div.message + img.logo(src=baseURL + "/images/VorlonLogo_Smooth.svg") h3 To get started, you must add a reference to client script in the page of your website, like this : - h4 <script src="http://localhost:1337/vorlon.js"></script> - h3 Once your page is loaded, the client will appear in the sidebar on the left. - h3 Click on it to start inspecting your website. + h4#scriptSrc <script src="http://localhost:1337/vorlon.js"></script> + h3 Once your page is loaded, the client will appear in the sidebar on the left. + h3 Click on it to start inspecting your website. + br + div.waitLoader.hidden + span.fa.fa-spin.fa-spinner diff --git a/Server/views/includes/sidebar.jade b/Server/views/includes/sidebar.jade index 8ce76925..cd6adebb 100644 --- a/Server/views/includes/sidebar.jade +++ b/Server/views/includes/sidebar.jade @@ -11,3 +11,6 @@ section.sidebar h2 Client List #clientsListPaneContent.client-list a(onclick='VORLON.DashboardManager.ResetDashboard()').btn.btn-secondary.btn-small.btn-reset Reset Dashboard + if authenticated + a#logout(href='/logout').btn.btn-secondary.btn-small.btn-reset logout + a(onclick='VORLON.DashboardManager.ReloadClient()')#test.btn.btn-secondary1.btn-small.btn-reset Reload Client \ No newline at end of file diff --git a/Server/views/layout.jade b/Server/views/layout.jade index 6f85a36b..42f34c51 100644 --- a/Server/views/layout.jade +++ b/Server/views/layout.jade @@ -8,22 +8,22 @@ html(lang='en') meta(name='author', content='') title Vorlon.js - - link(rel='stylesheet', href='/stylesheets/x-tag-components.css') - link(rel='stylesheet', href='/stylesheets/jquery-ui.css') - link(rel='stylesheet', href='/stylesheets/contextMenu.css') - link(rel='stylesheet', href='/stylesheets/jquery.switchButton.css') + script!='var vorlonBaseURL = "' + baseURL + '";' + link(rel='stylesheet', href=baseURL + '/stylesheets/x-tag-components.css') + link(rel='stylesheet', href=baseURL + '/stylesheets/jquery-ui.css') + link(rel='stylesheet', href=baseURL + '/stylesheets/contextMenu.css') + link(rel='stylesheet', href=baseURL + '/stylesheets/jquery.switchButton.css') link(href='https://fonts.googleapis.com/css?family=Oswald:400' rel='stylesheet') link(href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,300|Roboto:400,300' rel='stylesheet') - link(rel='icon', type='image/png', href='/images/favicon.png') - link(rel='stylesheet', href='/stylesheets/style.css') - link(rel='stylesheet', href='/fonts/fontawesome/css/font-awesome.min.css') + link(rel='icon', type='image/png', href=baseURL + '/images/favicon.png') + link(rel='stylesheet', href=baseURL + '/stylesheets/style.css') + link(rel='stylesheet', href=baseURL + '/fonts/fontawesome/css/font-awesome.min.css') body block content - script(src='/javascripts/jquery-2.1.1.min.js') + script(src=baseURL + '/javascripts/jquery-2.1.1.min.js') block scripts diff --git a/Server/views/login.jade b/Server/views/login.jade index 04e33f55..8664232d 100644 --- a/Server/views/login.jade +++ b/Server/views/login.jade @@ -1,12 +1,15 @@ extends layout block content - //- display the error message here - if message - p {message} + section.login-container + div.message + img.logo(src=baseURL + "/images/VorlonLogo_Smooth.svg") + h1 We need your credentials :) - //- submit to /login - form(action="/login", method="post") - input(type="text", id="username", name="username") - input(type="password", id="password", name="password") - button(type="submit") Login \ No newline at end of file + //- submit to /login + form(action="/login", method="post") + input(type="text", id="username", name="username", placeholder="Username") + input(type="password", id="password", name="password", placeholder="Password") + button(type="submit") Login + +block scripts diff --git a/VorlonJS.sln b/VorlonJS.sln index e4bdda86..c0d4c76f 100644 --- a/VorlonJS.sln +++ b/VorlonJS.sln @@ -1,48 +1,61 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22609.0 +VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins", "Plugins\Plugins.csproj", "{54E4B466-DFD4-4853-8BF5-939E9AFFED9C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{200CA1D1-B855-499E-A24B-65C594058A39}" - ProjectSection(SolutionItems) = preProject - readme.txt = readme.txt +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Server", "Server\", "{2632468E-0332-4119-87D3-0CF2EE420540}" + ProjectSection(WebsiteProperties) = preProject + TargetFrameworkMoniker = ".NETFramework,Version%3Dv2.0" + Debug.AspNetCompiler.VirtualPath = "/localhost_32047" + Debug.AspNetCompiler.PhysicalPath = "Server\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_32047\" + Debug.AspNetCompiler.Updateable = "true" + Debug.AspNetCompiler.ForceOverwrite = "true" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.VirtualPath = "/localhost_32047" + Release.AspNetCompiler.PhysicalPath = "Server\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_32047\" + Release.AspNetCompiler.Updateable = "true" + Release.AspNetCompiler.ForceOverwrite = "true" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.Debug = "False" + VWDPort = "32047" + SlnRelativePath = "Server\" EndProjectSection EndProject -Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Server", "Server\Server.njsproj", "{FF85083B-DF28-448B-8280-60CA2E5507DC}" - ProjectSection(ProjectDependencies) = postProject - {54E4B466-DFD4-4853-8BF5-939E9AFFED9C} = {54E4B466-DFD4-4853-8BF5-939E9AFFED9C} +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Plugins", "Plugins\", "{B24FFA7F-1251-435F-ACEF-9F21252A171B}" + ProjectSection(WebsiteProperties) = preProject + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" + Debug.AspNetCompiler.VirtualPath = "/localhost_32049" + Debug.AspNetCompiler.PhysicalPath = "Plugins\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_32049\" + Debug.AspNetCompiler.Updateable = "true" + Debug.AspNetCompiler.ForceOverwrite = "true" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.VirtualPath = "/localhost_32049" + Release.AspNetCompiler.PhysicalPath = "Plugins\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_32049\" + Release.AspNetCompiler.Updateable = "true" + Release.AspNetCompiler.ForceOverwrite = "true" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.Debug = "False" + VWDPort = "32049" + SlnRelativePath = "Plugins\" EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {54E4B466-DFD4-4853-8BF5-939E9AFFED9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54E4B466-DFD4-4853-8BF5-939E9AFFED9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54E4B466-DFD4-4853-8BF5-939E9AFFED9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54E4B466-DFD4-4853-8BF5-939E9AFFED9C}.Release|Any CPU.Build.0 = Release|Any CPU - {FF85083B-DF28-448B-8280-60CA2E5507DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF85083B-DF28-448B-8280-60CA2E5507DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF85083B-DF28-448B-8280-60CA2E5507DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF85083B-DF28-448B-8280-60CA2E5507DC}.Release|Any CPU.Build.0 = Release|Any CPU + {2632468E-0332-4119-87D3-0CF2EE420540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2632468E-0332-4119-87D3-0CF2EE420540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B24FFA7F-1251-435F-ACEF-9F21252A171B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B24FFA7F-1251-435F-ACEF-9F21252A171B}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(TeamFoundationVersionControl) = preSolution - SccNumberOfProjects = 3 - SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} - SccTeamFoundationServer = https://deltakosh.visualstudio.com/defaultcollection - SccLocalPath0 = . - SccProjectUniqueName1 = Plugins\\Plugins.csproj - SccProjectName1 = Plugins - SccLocalPath1 = Plugins - SccProjectUniqueName2 = Server\\Server.njsproj - SccProjectName2 = Server - SccLocalPath2 = Server - EndGlobalSection EndGlobal diff --git a/azuredeploy.json b/azuredeploy.json index 7a3e2b78..5053f1da 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -28,7 +28,7 @@ "Basic", "Standard" ], - "defaultValue": "Free", + "defaultValue": "Basic", "metadata": { "description": "The pricing tier for the hosting plan." } @@ -85,26 +85,7 @@ "serverFarmId":"[parameters('hostingPlanName')]" }, "resources":[ - { - "apiVersion": "2014-04-01", - "type": "config", - "name": "web", - "dependsOn": [ - "[concat('Microsoft.Web/Sites/', parameters('siteName'))]" - ], - "properties": { - "webSocketsEnabled": true, - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot\\Server", - "preloadEnabled": false, - "virtualDirectories": null - } - ] - } - }, - { + { "apiVersion":"2015-04-01", "name":"web", "type":"sourcecontrols", @@ -116,9 +97,28 @@ "branch": "[parameters('branch')]", "IsManualIntegration":true } + }, + { + "apiVersion": "2014-04-01", + "type": "config", + "name": "web", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]", + "[concat('Microsoft.Web/Sites/', parameters('siteName'), '/sourcecontrols/web')]" + ], + "properties": { + "webSocketsEnabled": true, + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot\\Server", + "preloadEnabled": true, + "virtualDirectories": null + } + ] + } } ] } ] } - diff --git a/deploy.cmd b/deploy.cmd index 1ec3dc38..6d891cae 100644 --- a/deploy.cmd +++ b/deploy.cmd @@ -110,7 +110,6 @@ IF EXIST "%DEPLOYMENT_TARGET%\package.json" ( :: Post deployment stub IF DEFINED POST_DEPLOYMENT_ACTION call "%POST_DEPLOYMENT_ACTION%" IF !ERRORLEVEL! NEQ 0 goto error - goto end :: Execute command routine that will echo out when error diff --git a/disclaimer.js b/disclaimer.js new file mode 100644 index 00000000..021aed1b --- /dev/null +++ b/disclaimer.js @@ -0,0 +1,4 @@ +var colors = require('colors'); + +console.log("Building vorlon.js..."); +console.log('Please ignore errors regarding Python and socket.io. This is due to a known issue with socket.io. More info here: https://github.com/socketio/socket.io/issues/1151'.bgYellow); \ No newline at end of file diff --git a/package.json b/package.json index 32d37a17..761abda9 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,57 @@ { "name": "vorlon", - "version": "0.0.15", + "version": "0.1.0", "description": "vorlon", "main": "Server/server.js", "dependencies": { - "xmlhttprequest": "^1.7.0", - "body-parser": "^1.12.3", - "cookieparser": "^0.1.0", - "express": "^4.12.3", - "express-session": "^1.11.1", - "fakeredis": "^0.3.1", + "colors": "~1.1.2", + "body-parser": "~1.12.3", + "cookie-parser": "~1.3.5", + "cookieparser": "~0.1.0", + "express": "~4.12.3", + "express-session": "~1.11.1", + "fakeredis": "~0.3.1", "favicon": "0.0.2", - "jade": "^1.9.2", - "json": "^9.0.3", - "logger": "0.0.1", - "multer": "^0.1.8", - "passport": "^0.2.1", - "passport-local": "^1.0.0", - "redis": "^0.12.1", - "socket.io": "^1.3.5", - "socket.io-redis": "^0.1.4", - "stylus": "^0.50.0", - "winston": "^0.9.0", + "jade": "~1.9.2", + "json": "~9.0.3", + "method-override": "2.3.4", + "multer": "~0.1.8", + "passport": "~0.2.1", + "passport-local": "~1.0.0", + "passport-twitter": "~1.0.3", + "redis": "~0.12.1", + "socket.io": "1.3.6", + "socket.io-redis": "~0.1.4", + "stylus": "~0.50.0", + "winston": "~1.0.1", "winston-azure": "0.0.4", - "winston-logs-display": "^0.1.1", - "gulp": "^3.8.11", - "gulp-concat": "^2.2.0", - "gulp-filter": "^0.4.1", - "gulp-less": "^3.0.3", - "gulp-rename": "^1.2.0", - "gulp-sourcemaps": "^1.5.2", - "gulp-typescript": "^2.6.0", - "gulp-uglify": "^0.3.0", - "gulp-util": "^2.2.14", - "gulp-webserver": "^0.9.0", - "merge2": "^0.3.5", - "through": "^2.3.4" + "winston-logs-display": "~0.1.1", + "xmlhttprequest": "~1.7.0", + "http-proxy": "~1.11.2" }, - "devDependencies": { + "devDependencies": { + "gulp": "~3.8.11", + "gulp-concat": "~2.2.0", + "gulp-filter": "~0.4.1", + "gulp-less": "~3.0.3", + "gulp-rename": "~1.2.0", + "gulp-sourcemaps": "~1.5.2", + "gulp-typescript": "~2.8.2", + "gulp-uglify": "~0.3.0", + "gulp-util": "~2.2.14", + "gulp-webserver": "~0.9.0", + "merge2": "~0.3.5", + "through": "~2.3.4", + "typescript": "~1.6.0-beta" }, "scripts": { + "global-deploy-gulp": "npm install -g gulp", + "global-deploy-gulp-cli": "npm install -g gulp-cli", "build-server": "gulp --gulpfile=./Server/gulpfile.js --cwd=./Server", "build-client": "gulp --gulpfile=./Plugins/gulpfile.js --cwd=./Plugins", - "build": "npm run build-server && npm run build-client", + "build": "node disclaimer.js && npm run global-deploy-gulp && npm run global-deploy-gulp-cli && npm run build-client && npm run build-server", "prepublish": "npm run build", - "start": "node server.js" + "start": "node ./Server/server.js" }, "bin": { "vorlon": "./bin/vorlon" diff --git a/whatsnew.md b/whatsnew.md index e3ae6bc7..088257e9 100644 --- a/whatsnew.md +++ b/whatsnew.md @@ -1,3 +1,18 @@ +## 0.0.16 + +- Plugins + - Object explorer was revamped for better integration + - New device plugin: Know your numbers! + - New (Q)Unit test plugin + - Wappalyzer plugin (3rd party) + - DOM Explorer: display all html nodes (head and body, instead of body only) in domExplorer, improve UI of search, update css +- Core + - --version now return the current version + - Custom log path and log level +- General + - Added server authentication + - Single click to deploy on Azure + ## 0.0.15 - Plugins