[macro] added @:view macro, refactored @:template macro
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"license": "BSD",
|
||||
"tags": ["flash", "openfl"],
|
||||
"description": "Framework.",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"releasenote": "Update.",
|
||||
"contributors": ["shmyga"],
|
||||
"classPath": "src/main",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
-cp src
|
||||
-lib haxework
|
||||
-cp ../../src/main
|
||||
-lib yaml
|
||||
-lib promhx
|
||||
-main ViewExample.hx
|
||||
-swf target/ViewExample.swf
|
||||
--macro haxework.parser.Parser.auto()
|
||||
|
||||
-swf target/ViewExample.swf
|
||||
#-as3 target
|
||||
@@ -1,29 +1,39 @@
|
||||
package;
|
||||
|
||||
import haxework.gui.ViewBuilder;
|
||||
import haxework.gui.View;
|
||||
import haxework.gui.VGroupView;
|
||||
import haxework.gui.ButtonView;
|
||||
import haxework.gui.Root;
|
||||
|
||||
@:template("form.json")
|
||||
class FormView extends VGroupView implements ViewBuilder {}
|
||||
|
||||
@:template2("form.json")
|
||||
class FormView extends VGroupView {
|
||||
@:view public var panel(default, null):View;
|
||||
@:view public var button1(default, null):View;
|
||||
@:view public var button2(default, null):View;
|
||||
@:view public var button3(default, null):View;
|
||||
|
||||
private function init() {
|
||||
trace('Init');
|
||||
}
|
||||
}
|
||||
|
||||
class ViewExample {
|
||||
|
||||
public static function main() {
|
||||
new ViewExample();
|
||||
}
|
||||
public static function main() {
|
||||
new ViewExample();
|
||||
}
|
||||
|
||||
public function new() {
|
||||
var form = new FormView({listener:this});
|
||||
Root.bind(form);
|
||||
trace(form.panel);
|
||||
trace(form.button1);
|
||||
trace(form.button2);
|
||||
trace(form.button3);
|
||||
}
|
||||
public function new() {
|
||||
var form:FormView = new FormView();
|
||||
Root.bind(form);
|
||||
trace(form.panel);
|
||||
trace(form.button1);
|
||||
trace(form.button2);
|
||||
trace(form.button3);
|
||||
}
|
||||
|
||||
public function onPress(view:ButtonView):Void {
|
||||
trace("onPress: " + view.id);
|
||||
}
|
||||
public function onPress(view:ButtonView):Void {
|
||||
trace("onPress: " + view.id);
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,7 @@
|
||||
"width":100,
|
||||
"pHeight":100,
|
||||
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0xcc0000"},
|
||||
"text":"Text1",
|
||||
"onPress":"@link:listener"
|
||||
"text":"Text1"
|
||||
},
|
||||
{
|
||||
"id":"button2",
|
||||
@@ -47,8 +46,7 @@
|
||||
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cc00"},
|
||||
"text":"Text2",
|
||||
"fontFamily":"Georgia",
|
||||
"fontColor":"0xffffff",
|
||||
"onPress":"@link:listener"
|
||||
"fontColor":"0xffffff"
|
||||
},
|
||||
{
|
||||
"id":"button3",
|
||||
@@ -57,8 +55,7 @@
|
||||
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cccc"},
|
||||
"text":"Text 3333333333 ddd",
|
||||
"fontFamily":"Tahoma",
|
||||
"fontColor":"0xff0000",
|
||||
"onPress":"@link:listener"
|
||||
"fontColor":"0xff0000"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package haxework.gui;
|
||||
|
||||
#if macro
|
||||
import haxework.gui.build.Builder;
|
||||
#end
|
||||
|
||||
@:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build())
|
||||
extern interface ViewBuilder {}
|
||||
|
||||
class ViewBuilderImpl {
|
||||
#if macro
|
||||
public static function build() {
|
||||
return new Builder().build();
|
||||
}
|
||||
#end
|
||||
}
|
||||
30
src/main/haxework/gui/build/BuilderUtil.hx → src/main/haxework/macro/FileUtil.hx
Executable file → Normal file
30
src/main/haxework/gui/build/BuilderUtil.hx → src/main/haxework/macro/FileUtil.hx
Executable file → Normal file
@@ -1,13 +1,11 @@
|
||||
package haxework.gui.build;
|
||||
#if macro
|
||||
package haxework.macro;
|
||||
|
||||
import yaml.Parser;
|
||||
import yaml.Yaml;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr.Constant;
|
||||
import haxe.macro.Expr.ExprDef;
|
||||
|
||||
class BuilderUtil {
|
||||
|
||||
class FileUtil {
|
||||
|
||||
public static function loadJsonFile(path:String):Dynamic {
|
||||
Context.registerModuleDependency(Context.getLocalModule(), path);
|
||||
@@ -39,23 +37,9 @@ class BuilderUtil {
|
||||
public static function loadFile(path:String):Dynamic {
|
||||
var ext = path.split('.').pop();
|
||||
return switch(ext) {
|
||||
case 'json': BuilderUtil.loadJsonFile(path);
|
||||
case 'yml' | 'yaml': BuilderUtil.loadYamlFile(path);
|
||||
case x: throw 'Unsupported template format: "${x}"';
|
||||
case 'json': loadJsonFile(path);
|
||||
case 'yml' | 'yaml': loadYamlFile(path);
|
||||
case x: throw 'Unsupported file format: "${x}"';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMeta(key:String):Array<String> {
|
||||
var c = Context.getLocalClass().get();
|
||||
for (meta in c.meta.get()) {
|
||||
if (meta.name == key) {
|
||||
return meta.params.map(function(param) return switch(param.expr) {
|
||||
case ExprDef.EConst(Constant.CString(value)): value;
|
||||
case _: null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package haxework.gui.build;
|
||||
package haxework.macro;
|
||||
|
||||
typedef JsonKeyPosition = {
|
||||
var min:Int;
|
||||
52
src/main/haxework/macro/ProvideMacro.hx
Normal file
52
src/main/haxework/macro/ProvideMacro.hx
Normal file
@@ -0,0 +1,52 @@
|
||||
package haxework.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
|
||||
class ProvideMacro {
|
||||
|
||||
public static function has(field:Field):Bool {
|
||||
for (md in field.meta) if (md.name == ":provide") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private var field:Field;
|
||||
|
||||
public function new(field:Field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public function apply():Array<Field> {
|
||||
var result:Array<Field> = [];
|
||||
var type:ComplexType = switch field.kind {
|
||||
case FieldType.FVar(t): t;
|
||||
default: null;
|
||||
}
|
||||
var name:String = switch type {
|
||||
case ComplexType.TPath(p): p.name;
|
||||
default: null;
|
||||
}
|
||||
result.push({
|
||||
name: field.name,
|
||||
access: [Access.APublic],
|
||||
pos: field.pos,
|
||||
kind: FieldType.FProp('get', 'never', type)
|
||||
});
|
||||
result.push({
|
||||
name: 'get_${field.name}',
|
||||
access: [Access.APrivate, Access.AInline],
|
||||
pos: field.pos,
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos),
|
||||
params: [],
|
||||
ret: type
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
182
src/main/haxework/gui/build/Builder.hx → src/main/haxework/macro/TemplateMacro.hx
Executable file → Normal file
182
src/main/haxework/gui/build/Builder.hx → src/main/haxework/macro/TemplateMacro.hx
Executable file → Normal file
@@ -1,44 +1,54 @@
|
||||
package haxework.gui.build;
|
||||
#if macro
|
||||
package haxework.macro;
|
||||
|
||||
import haxework.gui.build.BuilderUtil;
|
||||
import haxe.macro.Context;
|
||||
import haxework.gui.build.PositionJsonParser;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Expr.Field;
|
||||
import haxe.macro.Type;
|
||||
import haxework.macro.PositionJsonParser;
|
||||
|
||||
class Builder {
|
||||
|
||||
private var templateFile:String;
|
||||
private var templateKey:String;
|
||||
private var styleFile:String;
|
||||
class TemplateMacro {
|
||||
|
||||
private var template:Dynamic;
|
||||
private static inline var metaName:String = ':template';
|
||||
|
||||
public static function has(classType:ClassType):Bool {
|
||||
for (md in classType.meta.get()) if (md.name == metaName) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private var classType:ClassType;
|
||||
private var fields:Array<Field>;
|
||||
private var exprs:Array<Expr>;
|
||||
private var style:Dynamic;
|
||||
private var bindings:Map<String, String>;
|
||||
|
||||
private var meta(get, never):MetadataEntry;
|
||||
private var templateFile:String;
|
||||
private var template:Dynamic;
|
||||
private var style:Dynamic;
|
||||
private var i:Int;
|
||||
|
||||
public function new() {
|
||||
var templateMeta = BuilderUtil.getMeta(":template");
|
||||
var templatePath = templateMeta[0].split("@");
|
||||
templateFile = Context.resolvePath(templatePath[0]);
|
||||
templateKey = templatePath[1];
|
||||
|
||||
template = BuilderUtil.loadFile(templateFile);
|
||||
if (templateKey != null) template = Reflect.field(template, templateKey);
|
||||
|
||||
if (templateMeta[1] != null) {
|
||||
styleFile = Context.resolvePath(templateMeta[1]);
|
||||
style = BuilderUtil.loadFile(styleFile);
|
||||
private function get_meta():MetadataEntry {
|
||||
for (md in classType.meta.get()) if (md.name == metaName) {
|
||||
return md;
|
||||
}
|
||||
|
||||
fields = Context.getBuildFields();
|
||||
exprs = [];
|
||||
i = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function new(classType:ClassType, fields:Array<Field>) {
|
||||
this.classType = classType;
|
||||
this.fields = fields;
|
||||
var params = Util.getMetaParams(meta);
|
||||
templateFile = Context.resolvePath(params[0]);
|
||||
template = FileUtil.loadFile(templateFile);
|
||||
if (params.length > 1) {
|
||||
var styleFile = params.length > 1 ? Context.resolvePath(params[1]) : null;
|
||||
style = FileUtil.loadFile(styleFile);
|
||||
}
|
||||
bindings = findViewsBindings(fields);
|
||||
}
|
||||
|
||||
|
||||
private static function getSpecField(object:Dynamic, field:String):Dynamic {
|
||||
if (Reflect.hasField(object, "@" + field)) {
|
||||
return Reflect.field(object, "@" + field);
|
||||
@@ -56,7 +66,7 @@ class Builder {
|
||||
return Context.makePosition({min:min, max:max, file:file});
|
||||
}
|
||||
|
||||
private function specialValue(name:String, key:String, a:Array<String>, position:JsonKeyPosition):Dynamic {
|
||||
private function specialValue(name:String, key:String, a:Array<String>, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
|
||||
return switch (a[0]) {
|
||||
case "asset":
|
||||
switch (a[1]) {
|
||||
@@ -76,8 +86,8 @@ class Builder {
|
||||
case "class":
|
||||
a[1];
|
||||
case "layout":
|
||||
var template = BuilderUtil.loadJsonFile(a[1]);
|
||||
return getValue(name, key, template, position);
|
||||
var template = FileUtil.loadJsonFile(a[1]);
|
||||
return createValue(name, key, template, position, exprs);
|
||||
case "link":
|
||||
"(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")";
|
||||
case _:
|
||||
@@ -93,14 +103,14 @@ class Builder {
|
||||
return type;
|
||||
}
|
||||
|
||||
private function getValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition):Dynamic {
|
||||
private function createValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
|
||||
return if (Std.is(value, Array)) {
|
||||
value.map(function(v) {
|
||||
return getValue(null, null, v, position);
|
||||
return createValue(null, null, v, position, exprs);
|
||||
});
|
||||
} else if (Std.is(value, String)) {
|
||||
if (value.charAt(0) == "@" || value.charAt(0) == "$") {
|
||||
specialValue(name, key, value.substring(1, value.length).split(":"), position);
|
||||
specialValue(name, key, value.substring(1, value.length).split(":"), position, exprs);
|
||||
} else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) {
|
||||
Std.parseInt(StringTools.replace(Std.string(value), "#", "0x"));
|
||||
} else {
|
||||
@@ -111,14 +121,14 @@ class Builder {
|
||||
} else if (value != null) {
|
||||
var type = getType(value, getPosition(position));
|
||||
if (type != null) {
|
||||
var n = "a" + i++;
|
||||
var n = 'a${i++}';
|
||||
if (type == "Dynamic") {
|
||||
//ToDo:
|
||||
exprs.push(Context.parse("var " + n + " = cast {}", getPosition(position)));
|
||||
} else {
|
||||
exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition(position)));
|
||||
}
|
||||
createElement(value, n);
|
||||
createElement(n, value, exprs);
|
||||
n;
|
||||
} else {
|
||||
null;
|
||||
@@ -128,85 +138,95 @@ class Builder {
|
||||
}
|
||||
}
|
||||
|
||||
private function createElement(template:Dynamic, name:String):String {
|
||||
var s = getSpecField(template, "style");
|
||||
private function createElement(name:String, data:Dynamic, exprs:Array<Expr>):String {
|
||||
var s = getSpecField(data, "style");
|
||||
if (s != null) {
|
||||
var s = Reflect.field(style, s);
|
||||
for (key in Reflect.fields(s)) {
|
||||
if (key.charAt(0) != "$" && !Reflect.hasField(template, key)) {
|
||||
Reflect.setField(template, key, Reflect.field(s, key));
|
||||
Reflect.setField(template, "$" + key, Reflect.field(s, "$" + key));
|
||||
if (key.charAt(0) != "$" && !Reflect.hasField(data, key)) {
|
||||
Reflect.setField(data, key, Reflect.field(s, key));
|
||||
Reflect.setField(data, "$" + key, Reflect.field(s, "$" + key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Reflect.hasField(template, "id")) {
|
||||
var id = Reflect.field(template, "id");
|
||||
var type = getType(template, getPosition());
|
||||
var expr = Context.parse("var a:" + type, getPosition());
|
||||
var complexType = switch (expr.expr) {
|
||||
case EVars(vars): vars[0].type;
|
||||
case _: null;
|
||||
if (Reflect.hasField(data, "id")) {
|
||||
var id = Reflect.field(data, "id");
|
||||
if (bindings.exists(id)) {
|
||||
var bind = bindings.get(id);
|
||||
exprs.push(Context.parse('this.${bind} = ${name}', getPosition()));
|
||||
}
|
||||
fields.push({
|
||||
name: id,
|
||||
access: [APublic],
|
||||
pos: getPosition(),
|
||||
kind: FProp("default", "null", complexType)
|
||||
});
|
||||
exprs.push(Context.parse("this." + id + " = " + name, getPosition()));
|
||||
}
|
||||
|
||||
for (key in Reflect.fields(template)) {
|
||||
for (key in Reflect.fields(data)) {
|
||||
if (key.charAt(0) == "$" || key.charAt(0) == "@") continue;
|
||||
var position = Reflect.field(template, "$" + key);
|
||||
var value = getValue(name, key, Reflect.field(template, key), position);
|
||||
var position = Reflect.field(data, "$" + key);
|
||||
var value = createValue(name, key, Reflect.field(data, key), position, exprs);
|
||||
if (value != null) {
|
||||
exprs.push(Context.parse(name + "." + key + " = " + value, getPosition(position)));
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public function build():Array<Field> {
|
||||
createElement(template, "this");
|
||||
|
||||
var init = false;
|
||||
for (f in fields) if (f.name == "init") {
|
||||
init = true;
|
||||
break;
|
||||
}
|
||||
|
||||
fields.push({
|
||||
private function buildBuild(exprs:Array<Expr>):Field {
|
||||
return {
|
||||
name: "build",
|
||||
access: [APublic],
|
||||
access: [Access.APrivate],
|
||||
pos: getPosition(),
|
||||
kind: FFun({
|
||||
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: macro $b{exprs},
|
||||
params: [],
|
||||
ret: null
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function buildConstructor(init:Bool):Field {
|
||||
var contstrExprs = [];
|
||||
contstrExprs.push(macro super());
|
||||
contstrExprs.push(macro build(links));
|
||||
contstrExprs.push(macro build());
|
||||
if (init) contstrExprs.push(macro init());
|
||||
|
||||
fields.push({
|
||||
return {
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
access: [Access.APublic],
|
||||
pos: getPosition(),
|
||||
kind: FFun({
|
||||
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: macro $b{contstrExprs},
|
||||
params: [],
|
||||
ret: null
|
||||
})
|
||||
});
|
||||
return fields;
|
||||
};
|
||||
}
|
||||
|
||||
private static function findViewsBindings(fields:Array<Field>):Map<String, String> {
|
||||
var result:Map<String, String> = new Map();
|
||||
for (field in fields) if (field.meta != null) {
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == ':view') {
|
||||
var viewId:String = meta.params.length == 0 ? field.name : switch meta.params[0].expr {
|
||||
case ExprDef.EConst(Constant.CString(value)): value;
|
||||
default: null;
|
||||
}
|
||||
result.set(viewId, field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public function apply():Array<Field> {
|
||||
i = 0;
|
||||
var result:Array<Field> = fields.slice(0);
|
||||
var exprs:Array<Expr> = [];
|
||||
var init = Lambda.exists(result, function(f) return f.name == 'init');
|
||||
createElement("this", template, exprs);
|
||||
result.push(buildBuild(exprs));
|
||||
result.push(buildConstructor(init));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#end
|
||||
18
src/main/haxework/macro/Util.hx
Normal file
18
src/main/haxework/macro/Util.hx
Normal file
@@ -0,0 +1,18 @@
|
||||
package haxework.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
|
||||
|
||||
class Util {
|
||||
|
||||
public static function getMetaParams(meta:MetadataEntry):Array<String> {
|
||||
return meta.params.map(function(param:Expr) return switch(param.expr) {
|
||||
case ExprDef.EConst(Constant.CString(value)): value;
|
||||
case _: null;
|
||||
});
|
||||
}
|
||||
|
||||
public inline static function DynamicType():ComplexType {
|
||||
return ComplexType.TPath({name:'Dynamic', pack:[], params:[]});
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
package haxework.parser;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
#end
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type.ClassType;
|
||||
import haxe.macro.Type.Ref;
|
||||
import haxe.macro.Type;
|
||||
import haxework.macro.ProvideMacro;
|
||||
import haxework.macro.TemplateMacro;
|
||||
|
||||
|
||||
class Parser {
|
||||
|
||||
#if macro
|
||||
private static function auto():Void {
|
||||
haxe.macro.Compiler.addGlobalMetadata("", "@:build(haxework.parser.Parser.autoRun())", true, true, false);
|
||||
}
|
||||
@@ -21,55 +20,31 @@ class Parser {
|
||||
switch (t) {
|
||||
case null: return null;
|
||||
case Type.TInst(_.get() => ct, _):
|
||||
var hasMeta:Bool = false;
|
||||
var hasAutoBuild:Bool = false;
|
||||
for (md in ct.meta.get())
|
||||
if (md.name == ":haxework") {
|
||||
hasMeta = true;
|
||||
var modify:Bool = false;
|
||||
var fields:Array<Field> = Context.getBuildFields();
|
||||
var result:Array<Field> = [];
|
||||
var appends:Array<Field> = [];
|
||||
// process fields meta
|
||||
for (field in fields) {
|
||||
if (ProvideMacro.has(field)) {
|
||||
modify = true;
|
||||
var provide = new ProvideMacro(field);
|
||||
result = result.concat(provide.apply());
|
||||
} else {
|
||||
result.push(field);
|
||||
}
|
||||
if (hasMeta) {
|
||||
var result:Array<Field> = [];
|
||||
var fields:Array<Field> = Context.getBuildFields();
|
||||
for (field in fields) {
|
||||
var remove:Bool = false;
|
||||
for (md in field.meta)
|
||||
if (md.name == ":provide") {
|
||||
var type:ComplexType = switch field.kind {
|
||||
case FieldType.FVar(t): t;
|
||||
default: null;
|
||||
}
|
||||
var name:String = switch type {
|
||||
case ComplexType.TPath(p): p.name;
|
||||
default: null;
|
||||
}
|
||||
remove = true;
|
||||
result.push({
|
||||
name: field.name,
|
||||
access: [Access.APublic],
|
||||
pos: field.pos,
|
||||
kind: FieldType.FProp('get', 'never', type)
|
||||
});
|
||||
result.push({
|
||||
name: 'get_${field.name}',
|
||||
access: field.access,
|
||||
pos: field.pos,
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos),
|
||||
params: [],
|
||||
ret: type
|
||||
})
|
||||
});
|
||||
}
|
||||
if (!remove) {
|
||||
result.push(field);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
if (modify) {
|
||||
fields = result;
|
||||
}
|
||||
// process class meta
|
||||
if (TemplateMacro.has(ct)) {
|
||||
modify = true;
|
||||
var template = new TemplateMacro(ct, fields);
|
||||
fields = template.apply();
|
||||
}
|
||||
return modify ? fields : null;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user