Script.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. */
  4. var Script = function ( editor ) {
  5. var signals = editor.signals;
  6. var container = new UI.Panel();
  7. container.setId( 'script' );
  8. container.setPosition( 'absolute' );
  9. container.setBackgroundColor( '#272822' );
  10. container.setDisplay( 'none' );
  11. var header = new UI.Panel();
  12. header.setPadding( '10px' );
  13. container.add( header );
  14. var title = new UI.Text().setColor( '#fff' );
  15. header.add( title );
  16. var buttonSVG = ( function () {
  17. var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
  18. svg.setAttribute( 'width', 32 );
  19. svg.setAttribute( 'height', 32 );
  20. var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
  21. path.setAttribute( 'd', 'M 12,12 L 22,22 M 22,12 12,22' );
  22. path.setAttribute( 'stroke', '#fff' );
  23. svg.appendChild( path );
  24. return svg;
  25. } )();
  26. var close = new UI.Element( buttonSVG );
  27. close.setPosition( 'absolute' );
  28. close.setTop( '3px' );
  29. close.setRight( '1px' );
  30. close.setCursor( 'pointer' );
  31. close.onClick( function () {
  32. container.setDisplay( 'none' );
  33. } );
  34. header.add( close );
  35. var delay;
  36. var currentMode;
  37. var currentScript;
  38. var currentObject;
  39. var codemirror = CodeMirror( container.dom, {
  40. value: '',
  41. lineNumbers: true,
  42. matchBrackets: true,
  43. indentWithTabs: true,
  44. tabSize: 4,
  45. indentUnit: 4,
  46. hintOptions: {
  47. completeSingle: false
  48. }
  49. } );
  50. codemirror.setOption( 'theme', 'monokai' );
  51. codemirror.on( 'change', function () {
  52. clearTimeout( delay );
  53. delay = setTimeout( function () {
  54. var value = codemirror.getValue();
  55. if ( ! validate( value ) ) return;
  56. if ( typeof( currentScript ) === 'object' ) {
  57. currentScript.source = value;
  58. signals.scriptChanged.dispatch( currentScript );
  59. return;
  60. }
  61. switch ( currentScript ) {
  62. case 'vertexShader':
  63. currentObject.vertexShader = value;
  64. break;
  65. case 'fragmentShader':
  66. currentObject.fragmentShader = value;
  67. break;
  68. case 'programInfo':
  69. var json = JSON.parse( value );
  70. currentObject.defines = json.defines;
  71. currentObject.uniforms = json.uniforms;
  72. currentObject.attributes = json.attributes;
  73. }
  74. currentObject.needsUpdate = true;
  75. signals.materialChanged.dispatch( currentObject );
  76. }, 200 );
  77. });
  78. // prevent backspace from deleting objects
  79. var wrapper = codemirror.getWrapperElement();
  80. wrapper.addEventListener( 'keydown', function ( event ) {
  81. event.stopPropagation();
  82. } );
  83. // validate
  84. var errorLines = [];
  85. var widgets = [];
  86. var validate = function ( string ) {
  87. var errors;
  88. return codemirror.operation( function () {
  89. while ( errorLines.length > 0 ) {
  90. codemirror.removeLineClass( errorLines.shift(), 'background', 'errorLine' );
  91. }
  92. while ( widgets.length > 0 ) {
  93. codemirror.removeLineWidget( widgets.shift() );
  94. }
  95. //
  96. switch ( currentMode ) {
  97. case 'javascript':
  98. try {
  99. var syntax = esprima.parse( string, { tolerant: true } );
  100. errors = syntax.errors;
  101. } catch ( error ) {
  102. errors = [
  103. { lineNumber: error.lineNumber,message: error.message }
  104. ];
  105. }
  106. for ( var i = 0; i < errors.length; i ++ ) {
  107. var error = errors[ i ];
  108. error.message = error.message.replace(/Line [0-9]+: /, '');
  109. }
  110. break;
  111. case 'json':
  112. errors = [];
  113. jsonlint.parseError = function ( message, info ) {
  114. message = message.split('\n')[3];
  115. errors.push({
  116. lineNumber: info.loc.first_line,
  117. message: message
  118. });
  119. };
  120. try {
  121. jsonlint.parse( string );
  122. } catch ( error ) {
  123. // ignore failed error recovery
  124. }
  125. break;
  126. case 'glsl':
  127. // TODO validate GLSL (compiling shader?)
  128. default:
  129. errors = [];
  130. }
  131. for ( var i = 0; i < errors.length; i ++ ) {
  132. var error = errors[ i ];
  133. var message = document.createElement( 'div' );
  134. message.className = 'esprima-error';
  135. message.textContent = error.message;
  136. var lineNumber = error.lineNumber - 1;
  137. errorLines.push( lineNumber );
  138. codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
  139. var widget = codemirror.addLineWidget( lineNumber, message );
  140. widgets.push( widget );
  141. }
  142. return errorLines.length === 0;
  143. });
  144. };
  145. // tern js autocomplete
  146. var server = new CodeMirror.TernServer( {
  147. caseInsensitive: true,
  148. plugins: { threejs: null }
  149. } );
  150. codemirror.setOption( 'extraKeys', {
  151. 'Ctrl-Space': function(cm) { server.complete(cm); },
  152. 'Ctrl-I': function(cm) { server.showType(cm); },
  153. 'Ctrl-O': function(cm) { server.showDocs(cm); },
  154. 'Alt-.': function(cm) { server.jumpToDef(cm); },
  155. 'Alt-,': function(cm) { server.jumpBack(cm); },
  156. 'Ctrl-Q': function(cm) { server.rename(cm); },
  157. 'Ctrl-.': function(cm) { server.selectName(cm); }
  158. } );
  159. codemirror.on( 'cursorActivity', function( cm ) {
  160. if ( currentMode !== 'javascript' ) return;
  161. server.updateArgHints( cm );
  162. } );
  163. codemirror.on( 'keypress', function( cm, kb ) {
  164. if ( currentMode !== 'javascript' ) return;
  165. var typed = String.fromCharCode( kb.which || kb.keyCode );
  166. if ( /[\w\.]/.exec( typed ) ) {
  167. server.complete( cm );
  168. }
  169. } );
  170. //
  171. signals.editorCleared.add( function () {
  172. container.setDisplay( 'none' );
  173. } );
  174. signals.editScript.add( function ( object, script ) {
  175. var mode, name, source;
  176. if ( typeof( script ) === 'object' ) {
  177. mode = 'javascript';
  178. name = script.name;
  179. source = script.source;
  180. } else {
  181. switch ( script ) {
  182. case 'vertexShader':
  183. mode = 'glsl';
  184. name = 'Vertex Shader';
  185. source = object.vertexShader || "";
  186. break;
  187. case 'fragmentShader':
  188. mode = 'glsl';
  189. name = 'Fragment Shader';
  190. source = object.fragmentShader || "";
  191. break;
  192. case 'programInfo':
  193. mode = 'json';
  194. name = 'Program Properties';
  195. var json = {
  196. defines: object.defines,
  197. uniforms: object.uniforms,
  198. attributes: object.attributes
  199. };
  200. source = JSON.stringify( json, null, '\t' );
  201. }
  202. }
  203. currentMode = mode;
  204. currentScript = script;
  205. currentObject = object;
  206. title.setValue( object.name + ' / ' + name );
  207. container.setDisplay( '' );
  208. codemirror.setValue( source );
  209. if (mode === 'json' ) mode = { name: 'javascript', json: true };
  210. codemirror.setOption( 'mode', mode );
  211. } );
  212. return container;
  213. };