hotreloadclient.pas 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. unit hotreloadclient;
  2. {$mode objfpc}
  3. interface
  4. uses sysutils, types, js, web;
  5. Type
  6. { THotReloadOptions }
  7. THotReloadOptions = Class
  8. private
  9. FLog: Boolean;
  10. FName: string;
  11. FOverlayElementID: String;
  12. FPath: String;
  13. FPollInterval: Cardinal;
  14. FReload: Boolean;
  15. FTimeOut: Cardinal;
  16. FWarn: Boolean;
  17. Public
  18. Constructor Create; reintroduce;
  19. Property Path : String Read FPath Write FPath;
  20. Property Timeout : Cardinal Read FTimeOut Write FTimeOut;
  21. Property PollInterval : Cardinal Read FPollInterval Write FPollInterval;
  22. Property Reload : Boolean Read FReload Write FReload;
  23. Property Log : Boolean Read FLog Write FLog;
  24. Property Warn : Boolean Read FWarn Write FWarn;
  25. property Name : string Read FName write FName;
  26. Property OverlayElementID : String Read FOverlayElementID Write FOverlayElementID;
  27. end;
  28. THotReload = Class
  29. private
  30. FOptions : THotReloadOptions;
  31. FLastReq : TJSXMLHttpRequest;
  32. function doAbort(Event: TEventListenerEvent): boolean;
  33. function doStatus(Event: TEventListenerEvent): boolean;
  34. function GetLineColor(aLine: String): String;
  35. function GetLineStyle(aLine: String): String;
  36. procedure HandleMessage(aData: TJSObject);
  37. procedure Reload;
  38. procedure ShowLogOverlay(O: TStringDynArray);
  39. Protected
  40. class var Global : THotReload;
  41. procedure OnTick; virtual;
  42. public
  43. Constructor Create; reintroduce;
  44. Constructor Create(AOptions : THotReloadOptions);
  45. Class Procedure StartHotReload;
  46. Class Procedure StopHotReload;
  47. class function getGlobal : THotReload;
  48. procedure Initialize;
  49. Property Options : THotReloadOptions Read FOptions;
  50. end;
  51. implementation
  52. { THotReload }
  53. constructor THotReload.Create;
  54. begin
  55. Create(THotReloadOptions.Create);
  56. end;
  57. constructor THotReload.Create(AOptions: THotReloadOptions);
  58. begin
  59. FOptions:=AOptions;
  60. Initialize;
  61. end;
  62. class procedure THotReload.StartHotReload;
  63. begin
  64. Global:=THotReload.Create;
  65. end;
  66. class procedure THotReload.StopHotReload;
  67. begin
  68. FreeAndNil(Global);
  69. end;
  70. class function THotReload.getGlobal: THotReload;
  71. begin
  72. result:=Global;
  73. end;
  74. function THotReload.doAbort(Event: TEventListenerEvent): boolean;
  75. begin
  76. if Event=nil then ;
  77. if Options.log then
  78. console.warn('Status request aborted');
  79. FLastReq:=Nil;
  80. Result:=false;
  81. end;
  82. Procedure THotReload.Reload;
  83. begin
  84. window.location.reload(true);
  85. end;
  86. function THotReload.GetLineColor(aLine : String) : String;
  87. var
  88. P : Integer;
  89. begin
  90. P:=Pos(':',ALine);
  91. if (P>0) then
  92. begin
  93. Aline:=Copy(ALine,1,P-1);
  94. P:=Pos(' ',ALine);
  95. if P>0 then
  96. ALine:=Copy(ALine,P+1,Length(Aline)-P);
  97. end;
  98. case lowercase(aline) of
  99. 'error': Result:='E36049';
  100. 'note': Result:='B3CB74';
  101. 'warning': Result:='FFD080';
  102. 'hint': Result:='7CAFC2';
  103. 'info': Result:='7FACCA';
  104. 'fatal': Result:='E36049';
  105. else
  106. Result:='EBE7E3';
  107. end;
  108. end;
  109. function THotReload.GetLineStyle(aLine : String) : String;
  110. Var
  111. Color : String;
  112. begin
  113. color:=getLineColor(aLine);
  114. Result:='background-color:#' + color + '; color:#fff; padding:2px 4px; border-radius: 2px';
  115. end;
  116. Procedure THotReload.ShowLogOverlay(O : TStringDynArray);
  117. Const
  118. DefaultStyle =
  119. 'background: rgba(0,0,0,0.85);'+
  120. 'color: #E8E8E8;'+
  121. 'lineHeight: 1.2;'+
  122. 'fontFamily: Menlo, Consolas, monospace;'+
  123. 'fontSize: 13px;'+
  124. 'left: 0;'+
  125. 'right: 0;'+
  126. 'top: 0;'+
  127. 'bottom: 0;'+
  128. 'dir: ltr;'+
  129. 'textAlign: left';
  130. Var
  131. D,SP,MN : TJSElement;
  132. N : TJSNode;
  133. I : Integer;
  134. begin
  135. Writeln('Searching for '+Options.OverlayElementID);
  136. D:=document.getElementById(Options.OverlayElementID);
  137. if D=Nil then
  138. exit;
  139. // Clear
  140. N:=D.firstElementChild;
  141. While (N<>Nil) do
  142. begin
  143. D.removeChild(N);
  144. N:=D.firstElementChild;
  145. end;
  146. D['style']:=DefaultStyle;
  147. For I:=0 to Length(O)-1 do
  148. begin
  149. MN:=document.createElement('div');
  150. SP:=Document.createElement('span');
  151. SP['style']:=GetLineStyle(O[i]);
  152. SP.appendChild(document.createTextNode(O[i]));
  153. MN.appendChild(SP);
  154. D.appendChild(MN);
  155. end;
  156. end;
  157. Procedure THotReload.HandleMessage(aData : TJSObject);
  158. Var
  159. a : JSValue;
  160. O : TStringDynArray;
  161. I : integer;
  162. begin
  163. if Options.Log then
  164. console.log('Status ',aData);
  165. if isDefined(aData['ping']) then
  166. exit;
  167. a:=aData['action'];
  168. if isUnDefined(a) or not isString(a) then
  169. exit;
  170. case String(a) of
  171. 'building' :
  172. if Options.Log then
  173. Console.log(Options.Name+': Server is building job ID '+String(aData['compileID']));
  174. 'built' :
  175. begin
  176. if Options.Log then
  177. Console.log(Options.Name+': Server has built job ID '+String(aData['compileID']));
  178. if isArray(aData['output']) then
  179. begin
  180. O:=TStringDynArray(aData['output']);
  181. For I:=0 to Length(o)-1 do
  182. if Pos('Error: ',O[i])>0 then
  183. Console.Error(O[i])
  184. else
  185. Console.Log(O[i]);
  186. if (Options.OverlayElementID<>'') then
  187. ShowLogOverlay(O);
  188. end;
  189. if isBoolean(aData['success']) and (Boolean(aData['success'])) and Options.reload then
  190. begin
  191. if Options.Log then
  192. Console.log(Options.Name+': Reloading page');
  193. Reload;
  194. end;
  195. end;
  196. 'sync' :
  197. begin
  198. if Options.Reload then
  199. begin
  200. if Options.Log then
  201. Console.log(Options.Name+': Resync event. Reloading page');
  202. Reload;
  203. end
  204. else if Options.Log then
  205. Console.log(Options.Name+': Resync event. Ignoring');
  206. end;
  207. else
  208. if Options.Log then
  209. console.warn('Unknown status data', TJSJSON.stringify(aData));
  210. end;
  211. end;
  212. function THotReload.doStatus(Event: TEventListenerEvent): boolean;
  213. Var
  214. Data : TJSObject;
  215. begin
  216. if Event=nil then ;
  217. if Options.log then
  218. console.warn('Status received');
  219. try
  220. Data:=TJSJSON.parseObject(FLastReq.responseText);
  221. HandleMessage(Data);
  222. except
  223. console.error('Error parsing JSON status text: '+FLastReq.responseText);
  224. end;
  225. FLastReq:=Nil;
  226. Result:=True;
  227. end;
  228. procedure THotReload.OnTick;
  229. Var
  230. Req : TJSXMLHttpRequest;
  231. begin
  232. if Options.log then
  233. console.log('tick');
  234. if (FLastReq<>Nil) then
  235. Exit;
  236. Req:=TJSXMLHttpRequest.new;
  237. Req.addEventListener('load',@DoStatus);
  238. Req.addEventListener('abort',@DoAbort);
  239. Req.open('GET',Options.Path);
  240. Req.send;
  241. FLastReq:=Req;
  242. end;
  243. procedure THotReload.Initialize;
  244. begin
  245. console.log('init');
  246. if isunDefined(window) then
  247. exit; // Cannot do anything
  248. console.log('init 2');
  249. Window.setInterval(@OnTick,Options.PollInterval);
  250. end;
  251. { THotReloadOptions }
  252. constructor THotReloadOptions.create;
  253. begin
  254. FPath:='/$sys/status';
  255. FTimeOut:=20*1000;
  256. FPollInterval:=1000;
  257. FLog:=True;
  258. FWarn:=True;
  259. FName:='hotreload';
  260. end;
  261. end.