p2jsres.pp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. {
  2. This file is part of the Pas2JS run time library.
  3. Copyright (c) 2023 by Michael Van Canneyt
  4. Low-level Pas2JS resource handling.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit p2jsres;
  13. {$ENDIF}
  14. {$mode objfpc}
  15. {$h+}
  16. {$modeswitch externalclass}
  17. interface
  18. {$IFDEF FPC_DOTTEDUNITS}
  19. uses System.Types;
  20. {$ELSE}
  21. uses types;
  22. {$ENDIF}
  23. Type
  24. TResourceSource = (rsJS,rsHTML);
  25. TResourceInfo = record
  26. name : string;
  27. encoding : string;
  28. resourceunit : string;
  29. format : string;
  30. data : string;
  31. end;
  32. TResourceEnumCallBack = Reference to function(const resName : string) : boolean;
  33. TResourcesLoadedEnumCallBack = Reference to Procedure(const LoadedResources : Array of String);
  34. TResourcesLoadErrorCallBack = Reference to Procedure(const aError : string);
  35. Function SetResourceSource(aSource : TResourceSource) : TResourceSource;
  36. Function GetResourceNames : TStringDynArray;
  37. Function GetResourceNames(aSource : TResourceSource) : TStringDynArray;
  38. Function EnumResources(aCallback : TResourceEnumCallBack) : Integer;
  39. Function EnumResources(aSource : TResourceSource; aCallback : TResourceEnumCallBack) : Integer;
  40. Function GetResourceInfo(Const aName : String; var aInfo : TResourceInfo) : Boolean;
  41. Function GetResourceInfo(aSource : TResourceSource; Const aName : String; var aInfo : TResourceInfo) : Boolean;
  42. Procedure LoadHTMLLinkResources(const aURL : String; OnLoad : TResourcesLoadedEnumCallBack = Nil; OnError : TResourcesLoadErrorCallBack = Nil);
  43. implementation
  44. Uses
  45. {$IFDEF FPC_DOTTEDUNITS}
  46. System.SysUtils, JSApi.JS, BrowserApi.Web;
  47. {$ELSE}
  48. sysutils, js, web;
  49. {$ENDIF}
  50. var
  51. gMode: TResourceSource;
  52. { ---------------------------------------------------------------------
  53. Global entry points
  54. ---------------------------------------------------------------------}
  55. Function SeTResourceSource(aSource : TResourceSource) : TResourceSource;
  56. begin
  57. Result:=gMode;
  58. gMode:=aSource;
  59. end;
  60. Function GetResourceNames : TStringDynArray;
  61. begin
  62. Result:=GetResourceNames(gMode);
  63. end;
  64. Function EnumResources(aCallback : TResourceEnumCallBack) : Integer;
  65. begin
  66. Result:=EnumResources(gMode,aCallback);
  67. end;
  68. Function GetResourceInfo(Const aName : String; var aInfo : TResourceInfo) : Boolean;
  69. begin
  70. Result:=GetResourceInfo(gMode,aName,aInfo);
  71. end;
  72. { ---------------------------------------------------------------------
  73. JS resources
  74. ---------------------------------------------------------------------}
  75. Type
  76. TRTLResourceInfo = class external name 'Object' (TJSObject)
  77. name : string;
  78. encoding : string;
  79. resourceunit : string; external name 'unit';
  80. format : string;
  81. data : string;
  82. end;
  83. function rtlGetResourceList : TStringDynArray; external name 'rtl.getResourceList';
  84. function rtlGetResource(const aName : string) : TRTLResourceInfo; external name 'rtl.getResource';
  85. Function GetRTLResourceInfo(Const aName : String; var aInfo : TResourceInfo) : Boolean;
  86. Var
  87. RTLInfo : TRTLResourceInfo;
  88. begin
  89. RTLInfo:=rtlGetResource(lowercase(aName));
  90. Result:=Assigned(RTLInfo);
  91. if Result then
  92. begin
  93. aInfo.name:=RTLinfo.name;
  94. aInfo.encoding:=RTLinfo.encoding;
  95. aInfo.format:=RTLinfo.format;
  96. aInfo.resourceunit:=RTLinfo.resourceunit;
  97. aInfo.data:=RTLinfo.data;
  98. end;
  99. end;
  100. { ---------------------------------------------------------------------
  101. HTML resources
  102. ---------------------------------------------------------------------}
  103. Const
  104. IDPrefix = 'resource-';
  105. Function IsResourceLink(L : TJSHTMLLinkElement) : Boolean;
  106. begin
  107. Result:=(Copy(L.id,1,Length(IDPrefix))=IDPrefix) and (isDefined(L.Dataset['unit'])) and (Copy(L.href,1,4)='data')
  108. end;
  109. Function GetHTMLResources : TStringDynArray;
  110. Var
  111. LC : TJSHTMLCollection;
  112. L : TJSHTMLLinkElement;
  113. I : Integer;
  114. ID : String;
  115. begin
  116. SetLength(Result,0);
  117. if not isDefined(document) then // If called in Node...
  118. exit;
  119. // No cache, we do this dynamically: it's possible to add link nodes at runtime.
  120. LC:=document.getElementsByTagName('link');
  121. For I:=0 to LC.length-1 do
  122. begin
  123. L:=TJSHTMLLinkElement(LC[i]);
  124. ID:=L.ID;
  125. if IsResourceLink(L) then
  126. begin
  127. Delete(ID,1,Length(IDPrefix));
  128. if (ID<>'') then
  129. TJSArray(Result).Push(ID);
  130. end;
  131. end;
  132. end;
  133. Function GetHTMLResourceInfo(Const aName : String; var aInfo : TResourceInfo) : Boolean;
  134. Var
  135. el : TJSElement;
  136. L : TJSHTMLLinkElement absolute el;
  137. S : String;
  138. I : Integer;
  139. begin
  140. Result:=False;
  141. if not isDefined(document) then // If called in Node...
  142. exit;
  143. El:=document.getElementByID(IDPrefix+lowercase(aName));
  144. Result:=assigned(el) and SameText(el.tagName,'link');
  145. if not Result then
  146. exit;
  147. ainfo.name:=lowercase(aName);
  148. ainfo.Resourceunit:=String(L.Dataset['unit']);
  149. S:=L.href;
  150. S:=Copy(S,6,Length(S)-5); // strip data;
  151. I:=Pos(',',S);
  152. aInfo.data:=Copy(S,I+1,Length(S)-1);
  153. S:=copy(S,1,I-1);
  154. I:=Pos(';',S);
  155. if I=0 then
  156. aInfo.encoding:=''
  157. else
  158. begin
  159. aInfo.encoding:=Copy(S,I+1,Length(S)-1);
  160. S:=Copy(S,1,I-1);
  161. end;
  162. aInfo.Format:=S;
  163. end;
  164. Function HasTemplate : Boolean;
  165. begin
  166. asm
  167. return ('content' in document.createElement('template'))
  168. end;
  169. Result:=false;
  170. end;
  171. Procedure LoadHTMLLinkResources(const aURL : String; OnLoad : TResourcesLoadedEnumCallBack = Nil; OnError : TResourcesLoadErrorCallBack = Nil);
  172. function FetchOK(Res : JSValue) : JSValue;
  173. var
  174. Response : TJSResponse absolute res;
  175. begin
  176. Result:=Nil;
  177. if not Response.ok then
  178. begin
  179. if Assigned(OnError) then
  180. Raise TJSError.New('HTTP Error for URL aURL, status = '+IntToStr(Response.status)+' : '+Response.statusText)
  181. end
  182. else
  183. Result:=Response.Text();
  184. end;
  185. function BlobOK(Res : JSValue) : JSValue;
  186. Var
  187. aText : String absolute res;
  188. ID : String;
  189. Tmpl : TJSHTMLTemplateElement;
  190. El : TJSHTMLElement;
  191. L : TJSHTMLLinkElement absolute El;
  192. Arr : TStringDynArray;
  193. aParent : TJSHTMLElement;
  194. begin
  195. Result:=Nil;
  196. aParent:=TJSHTMLElement(document.head);
  197. if aParent=Nil then
  198. aParent:=TJSHTMLElement(document.body);
  199. SetLength(Arr,0);
  200. Tmpl:=TJSHTMLTemplateElement(Document.createElement('template'));
  201. Tmpl.innerhtml:=TJSString(aText).trim;
  202. el:=TJSHTMLElement(Tmpl.Content.firstElementChild);
  203. While El<>Nil do
  204. begin
  205. if SameText(El.tagName,'link') and IsResourceLink(L) then
  206. begin
  207. aParent.Append(TJSHTMLElement(document.importNode(l,true)));
  208. ID:=L.ID;
  209. Delete(ID,1,Length(IDPrefix));
  210. if (ID<>'') then
  211. TJSArray(Arr).Push(ID);
  212. end;
  213. el:=TJSHTMLElement(el.nextElementSibling);
  214. end;
  215. if Assigned(OnLoad) then
  216. OnLoad(Arr);
  217. end;
  218. function DoError (aValue : JSValue) : JSValue;
  219. Var
  220. aErr : TJSError absolute aValue;
  221. begin
  222. Result:=Nil;
  223. if Assigned(OnError) then
  224. if aErr=Nil then
  225. OnError('Error: ' + aErr.message)
  226. end;
  227. begin
  228. if not HasTemplate then
  229. begin
  230. if Assigned(OnError) then
  231. OnError('No template support in this browser')
  232. end
  233. else
  234. window.fetch(aURL)._then(@FetchOK)._then(@BlobOK).catch(@doError);
  235. end;
  236. { ---------------------------------------------------------------------
  237. Global entries, specifying resource mode
  238. ---------------------------------------------------------------------}
  239. Function GetResourceNames(aSource : TResourceSource) : TStringDynArray;
  240. begin
  241. case aSource of
  242. rsJS : Result:=rtlGetResourceList;
  243. rsHTML : Result:=GetHTMLResources;
  244. end;
  245. end;
  246. Function EnumResources(aSource : TResourceSource; aCallback : TResourceEnumCallBack) : Integer;
  247. Var
  248. RL : TStringDynArray;
  249. I : Integer;
  250. ContinueEnum : Boolean;
  251. begin
  252. Result:=0;
  253. RL:=GetResourceNames(aSource);
  254. I:=0;
  255. Result:=Length(RL);
  256. ContinueEnum:=True;
  257. While (I<Result) and ContinueEnum do
  258. begin
  259. ContinueEnum:=aCallBack(RL[i]);
  260. Inc(I);
  261. end;
  262. end;
  263. Function GetResourceInfo(aSource : TResourceSource; Const aName : String; var aInfo : TResourceInfo) : Boolean;
  264. begin
  265. case aSource of
  266. rsJS : Result:=GetRTLResourceInfo(aName,aInfo);
  267. rsHTML : Result:=GetHTMLResourceInfo(aName,aInfo);
  268. end;
  269. end;
  270. end.