DomkitViewer.hx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. package hrt.impl;
  2. #if (!hscript || !hscriptPos)
  3. #error "DomkitViewer requires --library hscript with -D hscriptPos"
  4. #end
  5. #if domkit
  6. import h2d.domkit.BaseComponents;
  7. typedef DomkitFileData = { css : String, params : String, dml : String };
  8. class DomkitInterp extends hscript.Async.AsyncInterp {
  9. public function executeLoop( n : String, it : hscript.Expr, callb ) {
  10. var old = declared.length;
  11. declared.push({ n : n, old : locals.get(n) });
  12. var it = makeIterator(expr(it));
  13. while( it.hasNext() ) {
  14. locals.set(n,{ r : it.next() });
  15. if( !loopRun(callb) )
  16. break;
  17. }
  18. restore(old);
  19. }
  20. }
  21. class CssEntry extends hxd.fs.FileEntry {
  22. var nativePath : String;
  23. public var text : String;
  24. public function new(path) {
  25. this.nativePath = path;
  26. }
  27. override function getText() {
  28. return text;
  29. }
  30. override function get_path():String {
  31. return nativePath;
  32. }
  33. }
  34. class CssResource extends hxd.res.Resource {
  35. public var cssEntry : CssEntry;
  36. public var watchCallb : Void -> Void;
  37. public function new(path) {
  38. cssEntry = new CssEntry(path);
  39. super(cssEntry);
  40. }
  41. override function watch( callb : Null<Void->Void> ) {
  42. watchCallb = callb;
  43. }
  44. }
  45. class SourceComponent extends domkit.Component<h2d.Object, h2d.Object> {
  46. var res : hxd.res.Resource;
  47. var viewer : DomkitViewer;
  48. var isRec = false;
  49. public function new(name, res, viewer) {
  50. this.res = res;
  51. this.viewer = viewer;
  52. this.name = name;
  53. res.watch(function() {
  54. reload();
  55. @:privateAccess viewer.rebuild();
  56. });
  57. reload();
  58. super(name, makeComp, parent);
  59. }
  60. function reload() {
  61. var fullText = res.entry.getText();
  62. var data = DomkitViewer.parse(fullText);
  63. var p = new domkit.MarkupParser();
  64. p.allowRawText = true;
  65. var dml = p.parse(data.dml, res.entry.path, fullText.indexOf(data.dml));
  66. switch( dml.kind ) {
  67. case Node(null):
  68. for( c in dml.children )
  69. switch( c.kind ) {
  70. case Node(n) if( n.split(":")[0] == name ):
  71. dml = c;
  72. break;
  73. default:
  74. }
  75. default:
  76. }
  77. var parentName = "flow"; // todo : extract from source
  78. switch( dml.kind ) {
  79. case Node(n):
  80. var p = n.split(":");
  81. if( p.length > 1 )
  82. parentName = p[1];
  83. default:
  84. }
  85. argsNames = [];
  86. if( dml.arguments != null ) {
  87. for( e in dml.arguments )
  88. switch( e.value ) {
  89. case Code(n): argsNames.push(n);
  90. default:
  91. }
  92. }
  93. parent = cast @:privateAccess viewer.resolveComponent(parentName);
  94. }
  95. function makeComp(args:Array<Dynamic>, parent) : h2d.Object {
  96. if( isRec ) {
  97. isRec = false;
  98. var p = this.parent;
  99. while( p is SourceComponent )
  100. p = p.parent;
  101. var obj : h2d.Object = p.make(args, parent);
  102. if( obj.dom != null ) @:privateAccess obj.dom.component = this;
  103. return obj;
  104. }
  105. isRec = true;
  106. return @:privateAccess viewer.createComponent(res, parent, args);
  107. }
  108. }
  109. class DomkitViewer extends h2d.Object {
  110. var resource : hxd.res.Resource;
  111. var variablesFiles : Array<hxd.res.Resource> = [];
  112. var current : h2d.Object;
  113. var cssResources : Map<String,CssResource> = [];
  114. var interp : DomkitInterp;
  115. var style : h2d.domkit.Style;
  116. var baseVariables : Map<String,domkit.CssValue>;
  117. var contexts : Array<Dynamic> = [];
  118. var variables : Map<String,Dynamic> = [];
  119. var rebuilding = false;
  120. var rootObject : h2d.Object;
  121. var componentsPaths : Array<String> = [];
  122. var createRootArgs : Array<Dynamic>;
  123. var evaluatedParams : Dynamic;
  124. var loadedComponents : Array<domkit.Component<h2d.Object, h2d.Object>> = [];
  125. public function new( style : h2d.domkit.Style, res : hxd.res.Resource, ?parent ) {
  126. super(parent);
  127. this.style = style;
  128. this.resource = res;
  129. res.watch(rebuild);
  130. baseVariables = style.cssParser.variables.copy();
  131. rebuildDelay();
  132. }
  133. function rebuildDelay() {
  134. if( rebuilding ) return;
  135. rebuilding = true;
  136. haxe.Timer.delay(() -> { rebuilding = false; rebuild(); },0);
  137. }
  138. public function addComponentsPath( dir : String ) {
  139. componentsPaths.push(dir);
  140. rebuildDelay();
  141. }
  142. public function addVariables( res : hxd.res.Resource ) {
  143. variablesFiles.push(res);
  144. res.watch(rebuild);
  145. rebuildDelay();
  146. }
  147. public function addContext( ctx : Dynamic ) {
  148. contexts.push(ctx);
  149. rebuildDelay();
  150. }
  151. #if castle
  152. public function addCDB( cdb : cdb.Types.IndexId<Dynamic,Dynamic> ) {
  153. var obj = {};
  154. var idName = null;
  155. for( c in @:privateAccess cdb.sheet.columns ) {
  156. switch( c.type ) {
  157. case TId: idName = c.name; break;
  158. default:
  159. }
  160. }
  161. for( o in cdb.all ) {
  162. var id = Reflect.field(o, idName);
  163. if( id == null || id == "" ) continue;
  164. Reflect.setField(obj, id, id);
  165. }
  166. var name = @:privateAccess cdb.name;
  167. name = name.charAt(0).toUpperCase() + name.substr(1);
  168. variables.set(name, obj);
  169. rebuildDelay();
  170. }
  171. #end
  172. function reloadVariables() {
  173. var vars = baseVariables.copy();
  174. for( r in variablesFiles ) {
  175. var p = new domkit.CssParser();
  176. p.variables = vars;
  177. try {
  178. p.parseSheet(r.entry.getText(), r.name);
  179. } catch( e : domkit.Error ) {
  180. onError(e);
  181. }
  182. }
  183. style.cssParser.variables = vars;
  184. for( c in cssResources )
  185. c.watchCallb();
  186. }
  187. override function onRemove() {
  188. super.onRemove();
  189. for( r in variablesFiles )
  190. r.watch(null);
  191. style.cssParser.variables = baseVariables;
  192. resource.watch(null);
  193. for( c in cssResources )
  194. style.unload(c);
  195. cssResources = new Map();
  196. for( c in loadedComponents ) {
  197. @:privateAccess domkit.Component.COMPONENTS.remove(c.name);
  198. @:privateAccess domkit.CssStyle.CssData.COMPONENTS.remove(c);
  199. }
  200. loadedComponents = [];
  201. }
  202. public dynamic function onError( e : domkit.Error ) @:privateAccess {
  203. var text = resource.entry.getText();
  204. var line = text.substr(0, e.pmin).split("\n").length;
  205. var err = resource.entry.path+":"+line+": "+e.message;
  206. style.errors.remove(err);
  207. style.errors.push(err);
  208. style.refreshErrors(getScene());
  209. }
  210. function makeInterp() {
  211. var interp = new DomkitInterp();
  212. for( c in contexts )
  213. interp.setContext(c);
  214. for( name => value in variables )
  215. interp.variables.set(name, value);
  216. return interp;
  217. }
  218. public static function parse( content : String ) : DomkitFileData {
  219. var cssText = "";
  220. var paramsText = "{}";
  221. content = StringTools.trim(content);
  222. if( StringTools.startsWith(content,"<css>") ) {
  223. var pos = content.indexOf("</css>");
  224. cssText = StringTools.trim(content.substr(5, pos - 6));
  225. content = content.substr(pos + 6);
  226. content = StringTools.trim(content);
  227. }
  228. if( StringTools.startsWith(content,"<params>") ) {
  229. var pos = content.indexOf("</params>");
  230. paramsText = StringTools.trim(content.substr(8, pos - 9));
  231. content = content.substr(pos + 9);
  232. content = StringTools.trim(content);
  233. }
  234. return {
  235. css : cssText,
  236. params : paramsText,
  237. dml : content,
  238. }
  239. }
  240. public static function toStr(data:DomkitFileData) {
  241. var parts = ['<css>\n${data.css}\n</css>'];
  242. if( data.params != '' && data.params != '{}' )
  243. parts.push('<params>\n${data.params}\n</params>');
  244. parts.push(data.dml);
  245. return parts.join('\n\n');
  246. }
  247. function rebuild() {
  248. @:privateAccess {
  249. style.errors = [];
  250. style.refreshErrors();
  251. }
  252. reloadVariables();
  253. var root = new h2d.Flow();
  254. root.dom = domkit.Properties.create("flow",root,{ "class" : "debugRoot", layout : "stack", "content-align" : "middle middle", "fill-width" : "true", "fill-height" : "true" });
  255. var obj = createComponent(resource, root, null);
  256. if( evaluatedParams != null ) {
  257. var classes : Array<String> = Std.downcast(evaluatedParams.classes,Array);
  258. if( classes != null ) {
  259. var checks = new h2d.Flow(root);
  260. checks.dom = domkit.Properties.create("flow",checks,{ "class" : "debugClasses", "position" : "absolute", "align" : "middle top", "margin-top" : "5" });
  261. for( cl in classes ) {
  262. var c = new h2d.CheckBox(checks);
  263. c.dom = domkit.Properties.create("flow",c);
  264. c.text = cl;
  265. c.onChange = function() {
  266. obj.dom.toggleClass(cl, c.selected);
  267. };
  268. }
  269. }
  270. }
  271. if( current != null ) {
  272. current.remove();
  273. style.removeObject(current);
  274. }
  275. addChild(root);
  276. style.addObject(root);
  277. current = root;
  278. for( c in cssResources )
  279. c.watchCallb();
  280. }
  281. function createComponent( res : hxd.res.Resource, parent, args : Array<Dynamic> ) {
  282. var fullText = res.entry.getText();
  283. var data = parse(fullText);
  284. var comp = null;
  285. var prev = interp;
  286. createRootArgs = args;
  287. try {
  288. var parser = new domkit.MarkupParser();
  289. parser.allowRawText = true;
  290. var eparams = parseCode(data.params, fullText.indexOf(data.params));
  291. var expr = parser.parse(data.dml,res.entry.path, fullText.indexOf(data.dml));
  292. interp = makeInterp();
  293. var vparams : Dynamic = evalCode(eparams);
  294. if( vparams != null ) {
  295. for( f in Reflect.fields(vparams) )
  296. interp.variables.set(f, Reflect.field(vparams,f));
  297. }
  298. comp = addRec(expr, parent, true);
  299. interp = prev;
  300. evaluatedParams = vparams;
  301. } catch( e : domkit.Error ) {
  302. interp = prev;
  303. onError(e);
  304. return null;
  305. } catch( e : hscript.Expr.Error ) {
  306. interp = prev;
  307. onError(new domkit.Error(e.toString(), e.pmin, e.pmax));
  308. return null;
  309. }
  310. createRootArgs = null;
  311. var css = cssResources.get(res.entry.path);
  312. if( css == null ) {
  313. css = new CssResource(res.entry.path);
  314. css.cssEntry.text = "";
  315. style.load(css);
  316. cssResources.set(res.entry.path, css);
  317. }
  318. css.cssEntry.text = data.css;
  319. return comp;
  320. }
  321. function parseCode( codeStr : String, pos : Int ) {
  322. var parser = new hscript.Parser();
  323. try {
  324. return parser.parseString(codeStr);
  325. } catch( e : hscript.Expr.Error ) {
  326. throw new domkit.Error(e.toString(), e.pmin + pos, e.pmax + pos);
  327. }
  328. }
  329. function evalCode( e : hscript.Expr ) : Dynamic {
  330. return @:privateAccess interp.expr(e);
  331. }
  332. public static function getParentName( expr : hscript.Expr ) {
  333. switch( expr.e ) {
  334. case EIdent(name):
  335. return name;
  336. case EBinop("-", e1, e2):
  337. var e1 = getParentName(e1);
  338. var e2 = getParentName(e2);
  339. return e1 == null || e2 == null ? null : e1+"-"+e2;
  340. default:
  341. return null;
  342. }
  343. }
  344. function resolveComponent( name : String ) {
  345. var c = domkit.Component.get(name, true);
  346. if( c != null )
  347. return c;
  348. for( dir in componentsPaths ) {
  349. var r = try hxd.res.Loader.currentInstance.load(dir+"/"+name+".domkit") catch( e : hxd.res.NotFound ) continue;
  350. var c = new SourceComponent(name, r, this);
  351. loadedComponents.push(c);
  352. return c;
  353. }
  354. return null;
  355. }
  356. function addRec( e : domkit.MarkupParser.Markup, parent : h2d.Object, isRoot ) {
  357. var comp : h2d.Object = null;
  358. switch( e.kind ) {
  359. case Node(null):
  360. for( c in e.children )
  361. comp = addRec(c, parent, isRoot);
  362. case Node(name):
  363. if( e.condition != null ) {
  364. var expr = parseCode(e.condition.cond, e.condition.pmin);
  365. if( !evalCode(expr) )
  366. return null;
  367. }
  368. var c = domkit.Component.get(name, true);
  369. // if we are top component, resolve our parent component
  370. if( isRoot ) {
  371. var parts = name.split(":");
  372. var parent = null;
  373. if( parts.length == 2 ) {
  374. name = parts[0];
  375. c = domkit.Component.get(name, true);
  376. parent = resolveComponent(parts[1]);
  377. if( parent == null )
  378. throw new domkit.Error("Unknown parent component "+parts[1], e.pmin + name.length, e.pmin + name.length + parts[1].length + 1);
  379. }
  380. if( parent == null && (c == null || loadedComponents.indexOf(cast c) >= 0) )
  381. parent = domkit.Component.get("flow");
  382. if( c == null || (parent != null && c.parent != parent) ) {
  383. c = new domkit.Component(name,function(args,p) {
  384. var obj = c.parent.make(args,p);
  385. if( obj.dom != null )
  386. obj.dom.component = c;
  387. return obj;
  388. },parent);
  389. domkit.CssStyle.CssData.registerComponent(c);
  390. @:privateAccess c.argsNames = [];
  391. loadedComponents.push(cast c);
  392. }
  393. } else if( c == null ) {
  394. c = resolveComponent(name);
  395. }
  396. if( c == null )
  397. throw new domkit.Error("Unknown component "+name, e.pmin, e.pmax);
  398. var args = [for( a in e.arguments ) {
  399. var v : Dynamic = switch( a.value ) {
  400. case RawValue(v): v;
  401. case Code(code):
  402. var code = parseCode(code, a.pmin);
  403. evalCode(code);
  404. }
  405. v;
  406. }];
  407. if( isRoot && e.arguments.length == 0 && @:privateAccess c.argsNames != null ) {
  408. for( a in @:privateAccess c.argsNames ) {
  409. var v : Dynamic = interp.variables.get(a);
  410. args.push(v);
  411. }
  412. }
  413. if( createRootArgs != null ) {
  414. args = createRootArgs;
  415. createRootArgs = null;
  416. for( i => a in @:privateAccess c.argsNames )
  417. interp.variables.set(a, args[i]);
  418. }
  419. var attributes = {};
  420. var objId = null, objIdArray = false;
  421. for( a in e.attributes ) {
  422. if( a.name == "id" ) {
  423. objId = switch( a.value ) {
  424. case RawValue("true"):
  425. var name = null;
  426. for( a in e.attributes )
  427. if( a.name == "class" ) {
  428. name = switch( a.value ) { case RawValue(v): v; default: null; };
  429. break;
  430. }
  431. name;
  432. case RawValue(name) if( StringTools.endsWith(name,"[]") ):
  433. objIdArray = true;
  434. name.substr(0,name.length - 2);
  435. case RawValue(name):
  436. name;
  437. case Code(_): null;
  438. }
  439. if( objId != null )
  440. (attributes:Dynamic).id = objId;
  441. continue;
  442. }
  443. switch( a.value ) {
  444. case RawValue(v):
  445. Reflect.setField(attributes,a.name,v);
  446. case Code(_):
  447. // skip (init after)
  448. }
  449. }
  450. var childrenCreated = false;
  451. if( isRoot ) {
  452. // only create parent structure, since we will create our own structure here
  453. @:privateAccess c.createHook = function(obj) {
  454. rootObject = obj;
  455. interp.variables.set("this", obj);
  456. // create children immediately as our post-init code might require some components to be init
  457. childrenCreated = true;
  458. for( c in e.children )
  459. addRec(c, obj, false);
  460. };
  461. }
  462. var obj = c.make(args, parent.dom?.contentRoot);
  463. var p = obj.dom;
  464. if( p == null ) p = obj.dom = new domkit.Properties(obj, c);
  465. p.initAttributes(attributes);
  466. if( isRoot ) {
  467. rootObject = cast p.obj;
  468. @:privateAccess c.createHook = null;
  469. interp.variables.set("this", p.obj);
  470. }
  471. if( objId != null ) {
  472. if( objIdArray ) {
  473. var arr : Array<Dynamic> = try Reflect.getProperty(rootObject, objId) catch( e : Dynamic ) null;
  474. if( arr == null ) {
  475. arr = [];
  476. try Reflect.setProperty(rootObject, objId, arr) catch( e : Dynamic ) {};
  477. }
  478. arr.push(p.obj);
  479. } else {
  480. try Reflect.setProperty(rootObject, objId, p.obj) catch( e : Dynamic ) {}
  481. }
  482. }
  483. for( a in e.attributes ) {
  484. var h = p.component.getHandler(domkit.Property.get(a.name));
  485. if( h == null ) {
  486. // TODO : add warning
  487. continue;
  488. }
  489. switch( a.value ) {
  490. case RawValue(_):
  491. case Code(code):
  492. var v : Dynamic = evalCode(parseCode(code, a.pmin));
  493. @:privateAccess p.initStyle(a.name, v);
  494. h.apply(p.obj, v);
  495. }
  496. }
  497. if( !childrenCreated ) {
  498. for( c in e.children )
  499. addRec(c, cast p.contentRoot, false);
  500. }
  501. comp = cast p.obj;
  502. case Text(text):
  503. var tf = new h2d.HtmlText(hxd.res.DefaultFont.get(), parent);
  504. tf.dom = domkit.Properties.create("html-text", tf);
  505. tf.text = text;
  506. comp = tf;
  507. case For(cond):
  508. var expr = parseCode("for"+cond+"{}", e.pmin);
  509. switch( expr.e ) {
  510. case EFor(n,it,_):
  511. interp.executeLoop(n, it, function() {
  512. for( c in e.children )
  513. addRec(c, parent, false);
  514. });
  515. default:
  516. throw "assert";
  517. }
  518. case CodeBlock(v):
  519. throw new domkit.Error("Code block not supported", e.pmin);
  520. case Macro(id):
  521. throw new domkit.Error("Macro not supported", e.pmin);
  522. }
  523. return comp;
  524. }
  525. }
  526. #end