1 module awebview.gui.html;
2 
3 import carbon.utils;
4 import awebview.gui.activity;
5 import awebview.gui.methodhandler;
6 import awebview.gui.application;
7 
8 import awebview.wrapper;
9 import awebview.cssgrammar;
10 
11 import std.variant;
12 import std.typecons;
13 
14 import std.array : appender;
15 import std.format : formattedWrite;
16 import std.functional : forward;
17 import std.conv : to;
18 import std.datetime;
19 public import core.time : Duration;
20 
21 public import carbon.event : FiredContext;
22 import carbon.templates : Lstr;
23 import carbon.utils : toLiteral;
24 
25 
26 string buildHTMLTagAttr(in string[string] attrs)
27 {
28     auto app = appender!string();
29     foreach(k, v; attrs)
30         app.formattedWrite("%s=%s ", k, toLiteral(v));
31 
32     return app.data;
33 }
34 
35 
36 string buildHTMLTagAttr(string tag, string value)
37 {
38     import std.string : format;
39     return format("%s=%s ", tag, toLiteral(value));
40 }
41 
42 unittest
43 {
44     assert(buildHTMLTagAttr("a", "b") == "a=b ");
45     assert(buildHTMLTagAttr(["a": "b"]) == "a=b ");
46 }
47 
48 
49 abstract class HTMLPage
50 {
51     this(string id)
52     {
53         _id =  id;
54     }
55 
56 
57     final
58     @property
59     inout(Activity) activity() inout pure nothrow @safe @nogc
60     {
61         return _activity;
62     }
63 
64 
65     final
66     @property
67     inout(Application) application() inout pure nothrow @safe @nogc
68     {
69         if(_activity is null)
70             return null;
71         else
72             return _activity.application;
73     }
74 
75 
76     final
77     string id() const pure nothrow @safe @nogc @property { return _id; }
78 
79 
80     string html() @property;
81 
82 
83     inout(HTMLElement[string]) elements() inout @property;
84 
85 
86     final
87     inout(HTMLElement) opIndex(string id) inout
88     {
89         return this.elements[id];
90     }
91 
92 
93     void onStart(Activity activity)
94     {
95         _activity = activity;
96 
97         foreach(key, elem; elements.maybeModified){
98             elem.onStart(this);
99         }
100     }
101 
102 
103     void onAttach(bool isInitPhase)
104     {
105         _activity = activity;
106         foreach(key, elem; elements.maybeModified)
107             elem.onAttach(isInitPhase);
108     }
109 
110 
111     void onLoad(bool isInit)
112     {
113         foreach(key, elem; elements.maybeModified)
114             elem.onLoad(isInit);
115 
116         _clientWidth = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
117         _clientHeight = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
118         _resizeStatements = generateResizeStatements(this.activity);
119         onResize(activity.width, activity.height);
120     }
121 
122 
123     void onUpdate()
124     {
125         auto newW = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
126         auto newH = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
127 
128         if(newW != _clientWidth || newH != _clientHeight)
129             onResize(activity.width, activity.height);
130 
131         _clientWidth = newW;
132         _clientHeight = newH;
133 
134         foreach(k, elem; elements.maybeModified)
135             elem.onUpdate();
136     }
137 
138 
139     void onDetach()
140     {
141         foreach(key, elem; elements.maybeModified)
142             elem.onDetach();
143     }
144 
145 
146     void onDestroy()
147     {
148         foreach(k, elem; elements.maybeModified)
149             elem.onDestroy();
150     }
151 
152 
153     void onResize(size_t w, size_t h)
154     {
155         this.activity.runJS(_resizeStatements);
156 
157         foreach(k, elem; elements.maybeModified)
158             elem.onResize(w, h);
159     }
160 
161 
162     void onReceiveImmediateMessage(ImmediateMessage msg)
163     {
164         foreach(k, e; elements.maybeModified){
165             e.onReceiveImmediateMessage(msg);
166         }
167     }
168 
169 
170   private:
171     Activity _activity;
172     string _id;
173 
174     size_t _clientWidth;
175     size_t _clientHeight;
176 
177     string _resizeStatements;
178 }
179 
180 
181 class WebPage : HTMLPage
182 {
183     this(string id, string url)
184     {
185         super(id);
186         _url = url;
187     }
188 
189 
190     @property
191     void location(string str)
192     {
193         if(activity){
194             _url = str;
195             activity.view.loadURL(WebURL(str));
196         }
197         else
198             _url = str;
199     }
200 
201 
202     @property
203     string location()
204     {
205         if(activity)
206             return activity.evalJS(q{document.location}).to!string;
207         else
208             return _url;
209     }
210 
211 
212     override
213     void onAttach(bool isInitPhase)
214     {
215         _bLoaded = false;
216     }
217 
218 
219     override
220     void onUpdate()
221     {
222         if(!_bLoaded)
223             this.location = _url;
224 
225         _bLoaded = true;
226     }
227 
228 
229     override
230     inout(HTMLElement[string]) elements() inout @property { return null; }
231 
232 
233     override
234     string html() @property
235     {
236         return `<html><head><title>Jump</title></head><body>Now loading...</body></html>`;
237     }
238 
239   private:
240     string _url;
241     bool _bLoaded;
242 }
243 
244 
245 class TemplateHTMLPage(string form) : HTMLPage
246 {
247     this(string id, Variant[string] exts = null)
248     {
249         super(id);
250         _exts = exts;
251     }
252 
253 
254     final
255     auto js(this This)() @property pure nothrow @safe @nogc inout
256     {
257         static struct Result
258         {
259             string html() const
260             {
261                 auto app = appender!string();
262                 foreach(_, e; _this._js)
263                     app.formattedWrite(`<script src="%s"></script>`);
264                 return app.data;
265             }
266 
267 
268             alias html this;
269 
270 
271             void opOpAssign(string op : "~")(string src)
272             {
273                 import std.path : baseName;
274 
275                 auto bn = src.baseName;
276                 _this._js[bn] = src;
277             }
278 
279 
280           private:
281             This _this;
282         }
283 
284 
285         return Result(this);
286     }
287 
288 
289     final
290     auto css(this This)() @property pure nothrow @safe @nogc inout
291     {
292         static struct Result
293         {
294             string html() const
295             {
296                 auto app = appender!string();
297                 foreach(_, e; _this._css)
298                     app.formattedWrite(`<link rel="stylesheet" href="%s">`);
299                 return app.data;
300             }
301 
302 
303             alias html this;
304 
305 
306             void opOpAssign(string op : "~")(string src)
307             {
308                 import std.path : baseName;
309 
310                 auto bn = src.baseName;
311                 _this._css[bn] = src;
312             }
313 
314 
315           private:
316             This _this;
317         }
318 
319 
320         return Result(this);
321     }
322 
323 
324     override
325     @property
326     string html()
327     {
328         return mixin(Lstr!(form));
329     }
330 
331 
332     override
333     @property
334     inout(HTMLElement[string]) elements() inout { return _elems; }
335 
336 
337     void opOpAssign(string op : "~")(HTMLElement element)
338     {
339         addElement(element);
340     }
341 
342 
343     void addElement(HTMLElement element)
344     {
345         _elems[element.id] = element;
346     }
347 
348 
349     @property
350     inout(Variant[string]) exts() inout { return _exts; }
351 
352     @property
353     inout(T) exts(T)(string str) inout { return *_exts[str].peek!T; }
354 
355 
356   private:
357     HTMLElement[string] _elems;
358     Variant[string] _exts;
359     string[string] _js;
360     string[string] _css;
361 }
362 
363 
364 class HTMLElement
365 {
366     import awebview.jsvariable;
367 
368 
369     this(string id, bool doCreateObject)
370     in{
371         if(id is null)
372             assert(!doCreateObject);
373     }
374     body{
375         _id = id;
376         _hasObj = doCreateObject;
377     }
378 
379 
380     final
381     @property
382     inout(HTMLPage) page() inout pure nothrow @safe @nogc { return _page; }
383 
384 
385     final
386     @property
387     inout(Activity) activity() inout pure nothrow @safe @nogc
388     {
389         if(_page is null)
390             return null;
391         else
392             return _page.activity;
393     }
394 
395 
396     final
397     @property
398     inout(Application) application() inout pure nothrow @safe @nogc
399     {
400         if(this.activity is null)
401             return null;
402         else
403             return this.activity.application;
404     }
405 
406 
407     final
408     @property
409     bool hasObject() const pure nothrow @safe @nogc
410     {
411         return _hasObj;
412     }
413 
414 
415     @property
416     WeakRef!JSObject jsobject()
417     in {
418         assert(_v.isObject);
419     }
420     body {
421         return _v.get!(WeakRef!JSObject);
422     }
423 
424 
425     @property
426     string jsExpr()
427     in {
428         assert(hasObject || hasId);
429     }
430     body {
431         if(this.hasObject)
432             return mixin(Lstr!q{_tmp_%[_id%].domObject});
433         else
434             return `document.getElementById("` ~ id ~ `")`;
435     }
436 
437 
438     final
439     @property
440     auto domObject()
441     {
442         return jsExpression(this.activity, this.jsExpr);
443     }
444 
445 
446     final @property bool hasId() const pure nothrow @safe @nogc { return _id !is null; }
447 
448 
449     final @property string id() const pure nothrow @safe @nogc { return _id; }
450 
451 
452     @property string html() { return ""; }
453     @property string mime() { return "text/html"; }
454     @property const(void)[] rawResource() { return this.html; }
455 
456 
457     void onStart(HTMLPage page)
458     {
459         _page = page;
460 
461         if(this.hasObject && !_v.isObject){
462             _v = activity.createObject(_id);
463         }
464     }
465 
466 
467     void onDestroy()
468     {
469         _page = null;
470         _v = JSValue.null_;
471     }
472 
473 
474     void onAttach(bool isInit)
475     {
476         if(isInit && this.hasObject && !_v.isObject){
477             _v = activity.createObject(_id);
478         }
479     }
480 
481 
482     void onUpdate() {}
483 
484 
485     void onDetach() {}
486 
487 
488     void onLoad(bool isInit)
489     {
490         if(this.hasObject){
491             activity.runJS(mixin(Lstr!q{
492                 _tmp_%[_id%] = {};
493                 _tmp_%[_id%].domObject = document.getElementById("%[_id%]");
494             }));
495         }
496 
497         if(this.hasId || this.hasObject){
498             foreach(key, ref v; _staticProperties)
499                 this.opIndexAssign(v, key);
500         }
501     }
502 
503 
504     void onResize(size_t w, size_t h) {}
505 
506 
507     final
508     @property
509     auto staticProps()
510     {
511         static struct Result
512         {
513             void opIndexAssign(T)(T value, string name)
514             if(is(typeof(JSValue(value)) : JSValue))
515             {
516                 _elem.staticPropsSet(name, value);
517             }
518 
519 
520             void remove(string name)
521             {
522                 _elem.staticPropsRemove(name);
523             }
524 
525 
526             bool opBinaryRight(string op : "in")(string name) inout
527             {
528                 return _elem.inStaticProps(name);
529             }
530 
531           private:
532             HTMLElement _elem;
533         }
534 
535         return Result(this);
536     }
537 
538 
539     void staticPropsSet(T)(string name, T value)
540     if(is(typeof(JSValue(value)) : JSValue))
541     in{
542         assert(this.hasId);
543     }
544     body{
545         JSValue jv = JSValue(value);
546         _staticProperties[name] = jv;
547         if(this.activity){
548             this.opIndexAssign(jv, name);
549         }
550     }
551 
552 
553     final
554     void staticPropsRemove(string name)
555     {
556         _staticProperties.remove(name);
557     }
558 
559 
560     final
561     bool inStaticProps(string name) inout
562     {
563         return !(name !in _staticProperties);
564     }
565 
566 
567     final
568     {
569         mixin JSExprDOMEagerOperators!();
570     }
571 
572 
573     final
574     Tuple!(uint, "x", uint, "y", uint, "width", uint, "height")
575      boundingClientRect() @property
576     {
577         this.activity.runJS(mixin(Lstr!q{
578             var e = %[domObject.jsExpr%].getBoundingClientRect();
579             _carrierObject_.x = e.left;
580             _carrierObject_.y = e.top;
581             _carrierObject_.w = e.width;
582             _carrierObject_.h = e.height;
583         }));
584 
585         auto co = activity.carrierObject;
586         typeof(return) res;
587         res.x = co["x"].get!uint;
588         res.y = co["y"].get!uint;
589         res.width = co["w"].get!uint;
590         res.height = co["h"].get!uint;
591 
592         return res;
593     }
594 
595 
596     final
597     uint posY() @property
598     {
599         return domObject.invoke("getBoundingClientRect")["top"].eval().get!uint;
600     }
601 
602 
603     final
604     uint posX() @property
605     {
606         return domObject.invoke("getBoundingClientRect")["left"].eval().get!uint;
607     }
608 
609 
610     final
611     uint[2] pos() @property
612     {
613         auto rec = boundingClientRect();
614         return [rec.x, rec.y];
615     }
616 
617 
618     final
619     uint width() @property
620     {
621         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
622     }
623 
624 
625     final
626     uint height() @property
627     {
628         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
629     }
630 
631 
632 
633     void onReceiveImmediateMessage(ImmediateMessage msg){}
634 
635 
636   private:
637     string _id;
638     bool _hasObj;
639     JSValue _v;
640     HTMLPage _page;
641     JSValue[string] _staticProperties;
642 }
643 
644 
645 class IDOnlyElement : HTMLElement
646 {
647     this(string id)
648     in{
649         assert(id !is null);
650     }
651     body{
652         super(id, false);
653     }
654 }
655 
656 
657 class TagOnlyElement : HTMLElement
658 {
659     this(string id)
660     in {
661         assert(id !is null);
662     }
663     body {
664         super(id, true);
665     }
666 }
667 
668 
669 /**
670 Example:
671 ----------------------
672 class MyButton : TemplateHTMLElement!(HTMLElement,
673     q{<input type="button" id="%[id%]" value="Click me!">})
674 {
675     this(string id)
676     {
677         super(id, null, false);
678     }
679 }
680 
681 auto btn1 = new MyButton("btn1");
682 assert(btn1.html == q{<input type="button" id="btn1" value="Click me!">});
683 ----------------------
684 */
685 abstract class TemplateHTMLElement(Element, string form) : Element
686 if(is(Element : HTMLElement))
687 {
688     this(T...)(auto ref T args)
689     {
690       static if(is(typeof(super(forward!args[0 .. $-1]))) &&
691                 is(typeof(args[$-1]) : Variant[string]))
692       {
693         super(forward!args[0 .. $-1]);
694         _exts = args[$-1];
695       }
696       else
697         super(forward!args);
698     }
699 
700 
701     @property
702     inout(Variant[string]) exts() inout { return _exts; }
703 
704 
705     override
706     @property
707     string html()
708     {
709         import carbon.templates : Lstr;
710         return mixin(Lstr!(form));
711     }
712 
713 
714   private:
715     Variant[string] _exts;
716 }
717 
718 
719 /// ditto
720 alias TemplateHTMLElement(string form) = TemplateHTMLElement!(HTMLElement, form);
721 
722 
723 /**
724 Example:
725 ----------------
726 class MyButton : DefineSignals!(DeclareSignals!(HTMLElement, "onClick"), "onClick")
727 {
728     this(string id)
729     {
730         super(id, true);
731     }
732 
733     string html() const { ... }
734 }
735 
736 
737 MyButton btn1 = new MyButton("btn1");
738 btn1.onClick.strongConnect(delegate(FiredContext ctx, WeakRef!(const(JSArrayCpp)) arr){
739     assert(ctx.sender == btn1);
740 
741     writeln("fired a signal by ", ctx);
742 });
743 ----------------
744 */
745 abstract class DefineSignals(Element, names...) : Element
746 if(is(Element : HTMLElement) && names.length >= 1)
747 {
748     import carbon.event;
749 
750     this(T...)(auto ref T args)
751     {
752         super(forward!args);
753     }
754 
755 
756     mixin(genMethod());
757 
758 
759   private:
760     mixin(genField());
761 
762     static
763     {
764         string genField()
765         {
766             auto app = appender!string;
767             foreach(s; names)
768                 app.formattedWrite("EventManager!(WeakRef!(const(JSArrayCpp))) _%sEvent;\n", s);
769 
770             return app.data;
771         }
772 
773 
774         string genMethod()
775         {
776             auto app = appender!string;
777             foreach(s; names){
778                 app.formattedWrite("ref RestrictedSignal!(FiredContext, WeakRef!(const(JSArrayCpp))) %1$s() { return _%1$sEvent; }\n", s);
779                 app.formattedWrite("override void %1$s(WeakRef!(const(JSArrayCpp)) arr) { _%1$sEvent.emit(this, arr); }\n", s);
780             }
781 
782             return app.data;
783         }
784     }
785 }
786 
787 
788 /**
789 Example:
790 -------------------
791 class MyButton : DeclareSignals!(HTMLElement, "onClick")
792 {
793     this(string id)
794     {
795         super(id, true);
796     }
797 
798 
799     override
800     void onClick(WeakRef!(JSArrayCpp) args)
801     {
802         writeln("OK");
803     }
804 }
805 -------------------
806 */
807 abstract class DeclareSignals(Element, names...) : Element
808 if(is(Element : HTMLElement) && names.length >= 1)
809 {
810     this(T...)(auto ref T args)
811     {
812         super(forward!args);
813     }
814 
815 
816     final
817     void doJSInitialize(bool b) @property
818     {
819         _doInit = b;
820     }
821 
822 
823     final
824     void stopPropergation(bool b) @property
825     {
826         _stopProp = b;
827     }
828 
829 
830     mixin(genDeclMethods);
831 
832     override
833     void onStart(HTMLPage page)
834     {
835         super.onStart(page);
836         this.activity.methodHandler.set(this);
837     }
838 
839 
840     override
841     void onLoad(bool init)
842     {
843         super.onLoad(init);
844 
845         if(_doInit){
846             this.activity.runJS(genSettingEventHandlers(this.id, this.domObject.jsExpr, _stopProp));
847         }
848     }
849 
850 
851   private:
852     bool _doInit = true;
853     bool _stopProp = false;
854 
855     static
856     {
857         string genDeclMethods()
858         {
859             auto app = appender!string();
860 
861             foreach(s; names)
862                 app.formattedWrite(`@JSMethodTag("%1$s"w) `"void %1$s(WeakRef!(const(JSArrayCpp)));\n", s);
863 
864             return app.data;
865         }
866 
867 
868         string genSettingEventHandlers(string id, string domExpr, bool stopProp)
869         {
870             import std.string : toLower;
871 
872             auto app = appender!string();
873             app.formattedWrite("var e = %s;", domExpr);
874 
875             foreach(s; names){
876                 if(!stopProp)
877                     app.formattedWrite(q{e.%3$s = function() { %1$s.%2$s(); };}, id, s, toLower(s));
878                 else
879                     app.formattedWrite(q{e.%3$s = function(ev) { ev.stopPropergation(); %1$s.%2$s(); };}, id, s, toLower(s));
880             }
881 
882             return app.data;
883         }
884     }
885 }
886 
887 
888 alias DeclDefSignals(Element, names...) = DefineSignals!(DeclareSignals!(Element, names), names);
889 
890 
891 /**
892 Open context menu when user click right button.
893 */
894 abstract class DeclareContextMenu(Element, setting...) : DeclareSignals!(Element, "onContextMenu", setting)
895 {
896     this(T...)(auto ref T args)
897     {
898         super(forward!args);
899     }
900 
901 
902     HTMLPage menuPage() @property;
903 
904 
905     override
906     void onContextMenu(WeakRef!(const(JSArrayCpp)))
907     {
908         this.activity.popup(this.menuPage);
909     }
910 }
911 
912 
913 /**
914 Mouse hover event
915 */
916 abstract class DeclareHoverSignal(Element) : DeclareSignals!(Element, "onMouseOver", "onMouseOut")
917 {
918     this(T...)(auto ref T args)
919     {
920         super(forward!args);
921     }
922 
923 
924     /**
925     */
926     final
927     bool hover() @property { return _hovered; }
928 
929 
930     final
931     void onHoverImpl()
932     {
933         if(_isStarted)
934             onHover(_hovered, Clock.currTime - _startTime);
935     }
936 
937 
938     /**
939     */
940     void onHover(bool bOver, Duration dur);
941 
942 
943     override
944     void onUpdate()
945     {
946         super.onUpdate();
947 
948         onHoverImpl();
949     }
950 
951 
952     override
953     void onMouseOver(WeakRef!(const(JSArrayCpp)))
954     {
955         _isStarted = true;
956         _hovered = true;
957         _startTime = Clock.currTime;
958         onHoverImpl();
959     }
960 
961 
962     override
963     void onMouseOut(WeakRef!(const(JSArrayCpp)))
964     {
965         _hovered = false;
966         _startTime = Clock.currTime;
967         onHoverImpl();
968     }
969 
970 
971   private:
972     bool _isStarted;
973     bool _hovered;
974     SysTime _startTime;
975 }
976 
977 
978 /**
979 EventPipe
980 */
981 final class PageSignalHandler : HTMLElement
982 {
983     import carbon.event;
984 
985     this(string id = "_event_signal_pipe") { super(id, false); }
986 
987   @property
988   {
989     ref RestrictedSignal!(FiredContext, Activity)       onStartHandler  () { return _onStartEvent; }
990     ref RestrictedSignal!(FiredContext, bool)           onAttachHandler () { return _onAttachEvent; }
991     ref RestrictedSignal!(FiredContext, bool)           onLoadHandler   () { return _onLoadEvent; }
992     ref RestrictedSignal!(FiredContext)                 onUpdateHandler () { return _onUpdateEvent; }
993     ref RestrictedSignal!(FiredContext)                 onDetachHandler () { return _onDetachEvent; }
994     ref RestrictedSignal!(FiredContext)                 onDestroyHandler() { return _onDestroyEvent; }
995     ref RestrictedSignal!(FiredContext, size_t, size_t) onResizeHandler () { return _onResizeEvent; }
996   }
997 
998     override
999     void onStart(HTMLPage page)
1000     {
1001         super.onStart(page);
1002         _onStartEvent.emit(this, page.activity);
1003     }
1004 
1005 
1006     override
1007     void onAttach(bool bInit)
1008     {
1009         super.onAttach(bInit);
1010         _onAttachEvent.emit(this, bInit);
1011     }
1012 
1013 
1014     override
1015     void onLoad(bool bInit)
1016     {
1017         super.onLoad(bInit);
1018         _onLoadEvent.emit(this, bInit);
1019     }
1020 
1021 
1022     override
1023     void onUpdate()
1024     {
1025         super.onUpdate();
1026         _onUpdateEvent.emit(this, );
1027     }
1028 
1029 
1030     override
1031     void onDetach()
1032     {
1033         super.onDetach();
1034         _onDetachEvent.emit(this, );
1035     }
1036 
1037 
1038     override
1039     void onDestroy()
1040     {
1041         super.onDestroy();
1042         _onDestroyEvent.emit(this, );
1043     }
1044 
1045 
1046     override
1047     void onResize(size_t w, size_t h)
1048     {
1049         super.onResize(w, h);
1050         _onResizeEvent.emit(this, w, h);
1051     }
1052 
1053 
1054   private:
1055     EventManager!(Activity) _onStartEvent;
1056     EventManager!(bool) _onAttachEvent;
1057     EventManager!(bool) _onLoadEvent;
1058     EventManager!() _onUpdateEvent;
1059     EventManager!() _onDetachEvent;
1060     EventManager!() _onDestroyEvent;
1061     EventManager!(size_t, size_t) _onResizeEvent;
1062 }
1063 
1064 
1065 /**
1066 Selectors API
1067 */
1068 alias querySelector = querySelectorImpl!false;
1069 
1070 
1071 /// ditto
1072 alias querySelectorAll = querySelectorImpl!true;
1073 
1074 
1075 auto querySelectorImpl(bool isAll)(Activity activity, string cssSelector)
1076 {
1077     import awebview.jsvariable;
1078 
1079     static struct QuerySelectorResult
1080     {
1081         string jsExpr() const @property { return _jsExpr; }
1082         Activity activity() @property { return _activity; }
1083 
1084 
1085         mixin JSExprDOMEagerOperators!();
1086 
1087       private:
1088         string _jsExpr;
1089         Activity _activity;
1090     }
1091 
1092     QuerySelectorResult res;
1093     res._jsExpr = mixin(Lstr!q{document.%[isAll ? "querySelectorAll" : "querySelector"%](%[toLiteral(cssSelector)%])});
1094     res._activity = activity;
1095 
1096     return res;
1097 }