Преглед изворни кода

added domkit editor and viewer

Nicolas Cannasse пре 1 година
родитељ
комит
4ca8c075f6
4 измењених фајлова са 625 додато и 2 уклоњено
  1. 24 1
      bin/style.css
  2. 23 1
      bin/style.less
  3. 273 0
      hide/view/Domkit.hx
  4. 305 0
      hrt/impl/DomkitViewer.hx

+ 24 - 1
bin/style.css

@@ -221,7 +221,7 @@ input[type=button]:active {
   background-color: #424242;
   background-color: #424242;
 }
 }
 input[type=range] {
 input[type=range] {
-  -webkit-appearance: none;
+  appearance: none;
   background: transparent;
   background: transparent;
   border: none;
   border: none;
   padding: 0;
   padding: 0;
@@ -2717,3 +2717,26 @@ div.gradient-box {
   margin-bottom: 8px;
   margin-bottom: 8px;
   border-radius: 3px;
   border-radius: 3px;
 }
 }
+.domkitEditor {
+  width: 100%;
+}
+.domkitEditor tr,
+.domkitEditor td {
+  padding: 0;
+  margin: 0;
+  border: 0px;
+  border-collapse: collapse;
+}
+.domkitEditor .cssEditor,
+.domkitEditor .dmlEditor {
+  position: relative;
+  width: 50%;
+}
+.domkitEditor .cssEditor .codeeditor,
+.domkitEditor .dmlEditor .codeeditor {
+  height: 600px;
+}
+.domkitEditor .separator {
+  width: 20;
+  background: #111;
+}

+ 23 - 1
bin/style.less

@@ -226,7 +226,7 @@ input[type=button] {
 }
 }
 
 
 input[type=range] {
 input[type=range] {
-	-webkit-appearance: none;
+	appearance: none;
 	background: transparent;
 	background: transparent;
 	border:none;
 	border:none;
 	padding:0;
 	padding:0;
@@ -3111,4 +3111,26 @@ div.gradient-box {
 	box-sizing: border-box;
 	box-sizing: border-box;
 	margin-bottom: 8px;
 	margin-bottom: 8px;
 	border-radius: 3px;
 	border-radius: 3px;
+}
+
+
+.domkitEditor {
+	tr, td {
+		padding : 0;
+		margin : 0;
+		border : 0px;
+		border-collapse: collapse;
+	}
+	width : 100%;
+	.cssEditor, .dmlEditor {
+		position: relative;
+		width : 50%;
+		.codeeditor {
+			height : 600px;
+		}
+	}
+	.separator {
+		width : 20;
+		background : #111;
+	}
 }
 }

+ 273 - 0
hide/view/Domkit.hx

@@ -0,0 +1,273 @@
+package hide.view;
+
+import hscript.Checker.TType in Type;
+
+private typedef TypedComponent = {
+	var name : String;
+	var ?parent : TypedComponent;
+	var properties : Map<String, Type>;
+	var arguments : Array<{ name : String, type : Type, ?opt : Bool }>;
+}
+
+class Domkit extends FileView {
+
+	var cssEditor : hide.comp.CodeEditor;
+	var dmlEditor : hide.comp.CodeEditor;
+	var cssText : String;
+	var dmlText : String;
+	var prevSave : { css : String, dml : String };
+	var t_string : Type;
+	var checker : hide.comp.ScriptEditor.ScriptChecker;
+	var components : Map<String, TypedComponent>;
+
+	override function onDisplay() {
+		element.html('<table class="domkitEditor">
+			<tr>
+				<td class="cssEditor">
+				</td>
+				<td class="separator">
+				&nbsp;
+				</td>
+				<td class="dmlEditor">
+				</td>
+			</tr>
+		</div>');
+
+		var content = sys.io.File.getContent(getPath());
+		cssText = "";
+
+		if( StringTools.startsWith(content,"<css>") ) {
+			var pos = content.indexOf("</css>");
+			cssText = content.substr(5, pos - 5);
+			content = content.substr(pos + 6);
+		}
+
+		cssText = StringTools.trim(cssText);
+		dmlText = StringTools.trim(content);
+
+		prevSave = { css : cssText, dml : dmlText };
+		cssEditor = new hide.comp.CodeEditor(cssText, "less", element.find(".cssEditor"));
+		cssEditor.onChanged = function() {
+			cssText = cssEditor.code;
+			check();
+		};
+ 		dmlEditor = new hide.comp.CodeEditor(dmlText, "html", element.find(".dmlEditor"));
+		dmlEditor.onChanged = function() {
+			dmlText = dmlEditor.code;
+			check();
+		};
+		cssEditor.onSave = dmlEditor.onSave = save;
+		cssEditor.saveOnBlur = dmlEditor.saveOnBlur = false;
+
+		checker = new hide.comp.ScriptEditor.ScriptChecker(config, "domkit");
+		t_string = checker.checker.types.resolve("String");
+		initComponents();
+		check();
+	}
+
+	function initComponents() {
+		components = [];
+		/*
+		for( t in @:privateAccess checker.checker.types.types ) {
+			var c = switch( t ) {
+			case CTClass(c) if( c.meta != null ): c;
+			default: continue;
+			}
+			var name = null;
+			for( m in c.meta ) {
+				if( m.name == ":uiComp" ) {
+					switch( m.params[0].e ) {
+					case EConst(CString(s)):
+						name = s;
+						break;
+					default:
+					}
+				}
+			}
+			if( name == null )
+				continue;
+			var comp : TypedComponent = {
+				name : name,
+				properties : [],
+				arguments : [],
+			};
+			components.set(name, comp);
+			cdefs.push({ name : name, c : c });
+		}
+		for( def in cdefs ) {
+			var comp = components.get(def.name);
+			var c = def.c;
+		}*/
+	}
+
+	function check() {
+		modified = prevSave.css != cssText || prevSave.dml != dmlText;
+
+		// reset locals
+		checker.checker.check({ e : EBlock([]), pmin : 0, pmax : 0, origin : "", line : 0 });
+
+		function getLine( text:String, min:Int) {
+			var lines = text.substr(0, min).split("\n");
+			return lines.length;
+		}
+
+		try {
+			var parser = new domkit.MarkupParser();
+			parser.allowRawText = true;
+			var expr = parser.parse(dmlText,state.path, 0);
+			checkDML(expr, true);
+			dmlEditor.clearError();
+		} catch( e : domkit.Error ) {
+			var line = getLine(dmlText, e.pmin);
+			dmlEditor.setError(e.message, line, e.pmin, e.pmax);
+		} catch( e : hscript.Expr.Error ) {
+			var line = getLine(dmlText, e.pmin);
+			dmlEditor.setError(e.toString(), line, e.pmin, e.pmax);
+		}
+
+
+		var includes : Array<String> = config.get("less.includes", []);
+		var parser = new domkit.CssParser();
+		parser.allowSubRules = true;
+		parser.allowVariablesDecl = true;
+		for( file in includes ) {
+			var content = sys.io.File.getContent(ide.getPath(file));
+			try {
+				parser.parseSheet(content);
+			}  catch( e : domkit.Error ) {
+				var line = content.substr(0, e.pmin).split("\n").length;
+				ide.quickError(file+":"+line+": "+e.message);
+			}
+		}
+
+		try {
+			var css = parser.parseSheet(cssText);
+			cssEditor.clearError();
+		} catch( e : domkit.Error ) {
+			cssEditor.setError(e.message, getLine(cssText,e.pmin), e.pmin, e.pmax);
+		}
+
+	}
+
+	function resolveComp( name : String ) : TypedComponent {
+		return components.get(name);
+	}
+
+	function parseCode( code : String, pos : Int ) {
+		var parser = new hscript.Parser();
+		return parser.parseString(code, pos);
+	}
+
+	function typeCode( code : String, pos : Int ) : Type {
+		var e = parseCode(code, pos);
+		return @:privateAccess checker.checker.typeExpr(e, Value);
+	}
+
+	function tryUnify( t1 : Type, t2 : Type ) {
+		return checker.checker.tryUnify(t1,t2);
+	}
+
+	function typeStr( t : Type ) {
+		return hscript.Checker.typeStr(t);
+	}
+
+	function checkDML( e : domkit.MarkupParser.Markup, isRoot=false ) {
+		switch( e.kind ) {
+		case Node(null):
+			for( c in e.children )
+				checkDML(c,isRoot);
+		case Node(name):
+			var c = resolveComp(name);
+			if( isRoot ) {
+				var arg0 = e.arguments[0];
+				switch( arg0?.value ) {
+				case null:
+				case Code(code):
+					e.arguments.shift();
+					var code = parseCode(code, e.pmin);
+					switch( code.e ) {
+					case EIdent(name):
+						c = resolveComp(name);
+						//if( c == null )
+						//	throw new domkit.Error("Unknown parent component "+name, arg0.pmin, arg0.pmax);
+					default:
+					}
+				default:
+				}
+			}
+			if( c == null && isRoot )
+				c = resolveComp("flow");
+			//if( c == null )
+			//	throw new domkit.Error("Unknown component "+name, e.pmin, e.pmin + name.length);
+			for( i => a in e.arguments ) {
+				/*
+				var arg = c.arguments[i];
+				if( arg == null )
+					throw new domkit.Error("Too many arguments (require "+[for( a in c.arguments ) a.name].join(",")+")",a.pmin,a.pmax);
+				*/
+				var t = switch( a.value ) {
+				case RawValue(_): t_string;
+				case Code(code): typeCode(code, a.pmin);
+				};
+				/*
+				if( !tryUnify(t, arg.type) )
+					throw new domkit.Error(typeStr(t)+" should be "+typeStr(arg.type)+" for "+arg.name, a.pmin, a.pmax);
+				*/
+			}
+			/*for( i in e.arguments.length...c.arguments.length )
+				if( !c.arguments[i].opt )
+					throw new domkit.Error("Missing required argument "+c.arguments[i].name,e.pmin,e.pmax);
+			*/
+			for( a in e.attributes ) {
+				//var pt = c.properties.get(a.name);
+				//if( pt == null )
+				//	throw new domkit.Error(c.name+" does not have property "+a.name, a.pmin, a.pmax);
+				var t = switch( a.value ) {
+				case RawValue(_): continue; // will be parsed as CSS
+				case Code(code): typeCode(code, a.pmin);
+				};
+				//if( !tryUnify(t, pt) )
+				//	throw new domkit.Error(typeStr(t)+" should be "+typeStr(pt)+" for "+c+"."+a.name, a.pmin, a.pmax);
+			}
+			for( c in e.children )
+				checkDML(c);
+		case For(cond):
+			var expr = parseCode("for"+cond+"{}", e.pmin);
+			switch( expr.e ) {
+			case EFor(n,it,_): @:privateAccess {
+				var et = checker.checker.getIteratorType(expr,checker.checker.typeExpr(it,Value));
+				var prev = checker.checker.locals.get(n);
+				checker.checker.locals.set(n, et);
+				for( c in e.children )
+					checkDML(c);
+				if( prev == null )
+					checker.checker.locals.remove(n);
+				else
+					checker.checker.locals.set(n, prev);
+			}
+			default:
+				throw "assert";
+			}
+		case Text(_):
+			// nothing
+		case CodeBlock(v):
+			throw new domkit.Error("Code block not supported", e.pmin);
+		case Macro(id):
+			throw new domkit.Error("Macro not supported", e.pmin);
+		}
+	}
+
+	override function save() {
+		super.save();
+		sys.io.File.saveContent(getPath(),'<css>\n$cssText\n</css>\n$dmlText');
+		prevSave = { css : cssText, dml : dmlText };
+	}
+
+	override function getDefaultContent() {
+		var tag = getPath().split("/").pop().split(".").shift();
+		return haxe.io.Bytes.ofString('<css>\n$tag {\n}\n</css>\n<$tag>\n</$tag>');
+	}
+
+	static var _ = FileTree.registerExtension(Domkit,["domkit"],{ icon : "id-card-o", createNew : "Domkit Component" });
+
+}

+ 305 - 0
hrt/impl/DomkitViewer.hx

@@ -0,0 +1,305 @@
+package hrt.impl;
+
+#if domkit
+class DomkitInterp extends hscript.Async.AsyncInterp {
+
+	public function executeLoop( n : String, it : hscript.Expr, callb ) {
+		var old = declared.length;
+		declared.push({ n : n, old : locals.get(n) });
+		var it = makeIterator(expr(it));
+		while( it.hasNext() ) {
+			locals.set(n,{ r : it.next() });
+			if( !loopRun(callb) )
+				break;
+		}
+		restore(old);
+	}
+
+}
+
+class CssEntry extends hxd.fs.FileEntry {
+
+	var nativePath : String;
+	public var text : String;
+
+	public function new(path) {
+		this.nativePath = path;
+	}
+
+	override function getText() {
+		return text;
+	}
+
+	override function get_path():String {
+		return nativePath;
+	}
+
+}
+
+class CssResource extends hxd.res.Resource {
+
+	public var cssEntry : CssEntry;
+	public var watchCallb : Void -> Void;
+
+	public function new(path) {
+		cssEntry = new CssEntry(path);
+		super(cssEntry);
+	}
+
+	override function watch( callb : Null<Void->Void> ) {
+		watchCallb = callb;
+	}
+
+}
+
+class DomkitViewer extends h2d.Object {
+
+	var resource : hxd.res.Resource;
+	var variablesFiles : Array<hxd.res.Resource> = [];
+	var current : h2d.Object;
+	var cssResource : CssResource;
+	var interp : DomkitInterp;
+	var style : h2d.domkit.Style;
+	var baseVariables : Map<String,domkit.CssValue>;
+	var contexts : Array<Dynamic> = [];
+	var rebuilding = false;
+
+	public function new( style : h2d.domkit.Style, res : hxd.res.Resource, ?parent ) {
+		super(parent);
+		this.style = style;
+		this.resource = res;
+		res.watch(rebuild);
+		baseVariables = style.cssParser.variables.copy();
+		rebuildDelay();
+	}
+
+	function rebuildDelay() {
+		if( rebuilding ) return;
+		rebuilding = true;
+		haxe.Timer.delay(() -> { rebuilding = false; rebuild(); },0);
+	}
+
+	public function addVariables( res : hxd.res.Resource ) {
+		variablesFiles.push(res);
+		res.watch(rebuild);
+		rebuildDelay();
+	}
+
+	public function addContext( ctx : Dynamic ) {
+		contexts.push(ctx);
+		rebuildDelay();
+	}
+
+	function reloadVariables() {
+		var vars = baseVariables.copy();
+		for( r in variablesFiles ) {
+			var p = new domkit.CssParser();
+			p.variables = vars;
+			try {
+				p.parseSheet(r.entry.getText());
+			} catch( e : domkit.Error ) {
+				onError(e);
+			}
+		}
+		style.cssParser.variables = vars;
+		if( cssResource != null )
+			cssResource.watchCallb();
+	}
+
+	override function onRemove() {
+		super.onRemove();
+		for( r in variablesFiles )
+			r.watch(null);
+		style.cssParser.variables = baseVariables;
+		resource.watch(null);
+		if( cssResource != null )
+			style.unload(cssResource);
+	}
+
+	public dynamic function onError( e : domkit.Error ) @:privateAccess {
+		var text = resource.entry.getText();
+		var line = text.substr(0, e.pmin).split("\n").length;
+		var err = resource.entry.path+":"+line+": "+e.message;
+		style.errors.remove(err);
+		style.errors.push(err);
+		style.refreshErrors(getScene());
+	}
+
+	function makeInterp() {
+		var interp = new DomkitInterp();
+		for( c in contexts )
+			interp.setContext(c);
+		return interp;
+	}
+
+	function rebuild() {
+		var fullText = resource.entry.getText();
+		var text = fullText;
+		var startPos = 0;
+		var cssStart = 0;
+		var cssText = "";
+		if( StringTools.startsWith(text,"<css>") ) {
+			var pos = text.indexOf("</css>");
+			cssStart = 5;
+			cssText = text.substr(cssStart, pos - cssStart);
+			startPos = pos + 6;
+			text = text.substr(startPos);
+		}
+
+		@:privateAccess {
+			style.errors = [];
+			style.refreshErrors();
+		}
+
+		reloadVariables();
+
+		var root;
+		try {
+			var parser = new domkit.MarkupParser();
+			parser.allowRawText = true;
+			var expr = parser.parse(text,resource.entry.path, startPos);
+			root = domkit.Component.build(<flow class="debugRoot" layout="stack" content-align="middle middle" fill-width="true" fill-height="true"/>);
+
+			interp = makeInterp();
+			addRec(expr, root);
+			interp = null;
+		} catch( e : domkit.Error ) {
+			onError(e);
+			return;
+		}
+
+		if( current != null ) {
+			current.remove();
+			style.removeObject(current);
+		}
+		addChild(root);
+		style.addObject(root);
+		current = root;
+
+		if( cssResource == null ) {
+			cssResource = new CssResource(resource.entry.path);
+			cssResource.cssEntry.text = "";
+			style.load(cssResource);
+		}
+		cssResource.cssEntry.text = cssText;
+		cssResource.watchCallb();
+	}
+
+
+	function parseCode( codeStr : String, pos : Int ) {
+		var parser = new hscript.Parser();
+		try {
+			return parser.parseString(codeStr);
+		} catch( e : hscript.Expr.Error ) {
+			throw new domkit.Error(e.toString(), e.pmin + pos, e.pmax + pos);
+		}
+	}
+
+	function evalCode( e : hscript.Expr ) : Dynamic {
+		return @:privateAccess interp.expr(e);
+	}
+
+	public static function getParentName( expr : hscript.Expr ) {
+		switch( expr.e ) {
+		case EIdent(name):
+			return name;
+		case EBinop("-", e1, e2):
+			var e1 = getParentName(e1);
+			var e2 = getParentName(e2);
+			return e1 == null || e2 == null ? null : e1+"-"+e2;
+		default:
+			return null;
+		}
+	}
+
+	function addRec( e : domkit.MarkupParser.Markup, parent : h2d.Object ) {
+		switch( e.kind ) {
+		case Node(null):
+			for( c in e.children )
+				addRec(c, parent);
+		case Node(name):
+			var c = domkit.Component.get(name, true);
+			// if we are top component, resolve our parent component
+			if( parent.parent == null ) {
+				var parent = null;
+				var arg0 = e.arguments[0];
+				switch( arg0?.value ) {
+				case null:
+				case Code(code):
+					var code = parseCode(code, e.pmin);
+					var name = getParentName(code);
+					if( name != null ) {
+						e.arguments.shift();
+						parent = domkit.Component.get(name, true);
+						if( parent == null )
+							throw new domkit.Error("Unknown parent component "+name, arg0.pmin, arg0.pmax);
+					}
+				default:
+				}
+				if( parent == null && c == null )
+					parent = domkit.Component.get("flow");
+				if( c == null || (parent != null && c.parent != parent) )
+					c = new domkit.Component(name,parent.make,parent);
+			} else if( c == null ) {
+				// TODO : load other ui component
+			}
+			if( c == null )
+				throw new domkit.Error("Unknown component "+name, e.pmin, e.pmax);
+			var args = [for( a in e.arguments ) {
+				var v : Dynamic = switch( a.value ) {
+				case RawValue(v): v;
+				case Code(code):
+					var code = parseCode(code, a.pmin);
+					// TODO : typecheck code
+					evalCode(code);
+				}
+				// TODO : typecheck argument
+				v;
+			}];
+			var attributes = {};
+			for( a in e.attributes ) {
+				switch( a.value ) {
+				case RawValue(v):
+					Reflect.setField(attributes,a.name,v);
+				case Code(_):
+				}
+			}
+			var p = @:privateAccess domkit.Properties.createNew(c.name, parent.dom, args, attributes);
+			for( a in e.attributes ) {
+				var h = p.component.getHandler(domkit.Property.get(a.name));
+				if( h == null ) {
+					// TODO : add warning
+					continue;
+				}
+				switch( a.value ) {
+				case RawValue(_):
+				case Code(code):
+					var v : Dynamic = evalCode(parseCode(code, a.pmin));
+					@:privateAccess p.initStyle(a.name, v);
+				}
+			}
+			for( c in e.children )
+				addRec(c, cast p.contentRoot);
+		case Text(text):
+			var tf = new h2d.HtmlText(hxd.res.DefaultFont.get(), parent);
+			tf.dom = domkit.Properties.create("html-text", tf);
+			tf.text = text;
+		case For(cond):
+			var expr = parseCode("for"+cond+"{}", e.pmin);
+			switch( expr.e ) {
+			case EFor(n,it,_):
+				interp.executeLoop(n, it, function() {
+					for( c in e.children )
+						addRec(c, parent);
+				});
+			default:
+				throw "assert";
+			}
+		case CodeBlock(v):
+			throw new domkit.Error("Code block not supported", e.pmin);
+		case Macro(id):
+			throw new domkit.Error("Macro not supported", e.pmin);
+		}
+	}
+}
+#end