utils.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import _ from 'lodash';
  2. import baseX from 'base-x';
  3. import PAKO from 'pako';
  4. import {Buffer} from 'safe-buffer';
  5. import sjclWrapper from './sjclWrapper';
  6. export const pako = PAKO;
  7. const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
  8. const bs58 = baseX(BASE58);
  9. export function sleep(ms) {
  10. return new Promise(resolve => setTimeout(resolve, ms));
  11. }
  12. export function toHex(buf) {
  13. return Buffer.from(buf).toString('hex');
  14. }
  15. export function stringToHex(str) {
  16. return Buffer.from(str).toString('hex');
  17. }
  18. export function hexToString(str) {
  19. return Buffer.from(str, 'hex').toString();
  20. }
  21. export function randomArray(len) {
  22. const a = new Uint8Array(len);
  23. window.crypto.getRandomValues(a);
  24. return a;
  25. }
  26. export function randomHexString(len) {
  27. return Buffer.from(randomArray(len)).toString('hex');
  28. }
  29. export function formatDate(d, format) {
  30. if (!format)
  31. format = 'normal';
  32. switch (format) {
  33. case 'normal':
  34. return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` +
  35. `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
  36. case 'coDate':
  37. return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
  38. case 'noDate':
  39. return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()}`;
  40. }
  41. }
  42. export function fallbackCopyTextToClipboard(text) {
  43. let textArea = document.createElement('textarea');
  44. textArea.value = text;
  45. document.body.appendChild(textArea);
  46. textArea.focus();
  47. textArea.select();
  48. let result = false;
  49. try {
  50. result = document.execCommand('copy');
  51. } catch (e) {
  52. //
  53. }
  54. document.body.removeChild(textArea);
  55. return result;
  56. }
  57. export async function copyTextToClipboard(text) {
  58. if (!navigator.clipboard) {
  59. return fallbackCopyTextToClipboard(text);
  60. }
  61. let result = false;
  62. try {
  63. await navigator.clipboard.writeText(text);
  64. result = true;
  65. } catch (e) {
  66. //
  67. }
  68. return result;
  69. }
  70. export function toBase58(data) {
  71. return bs58.encode(Buffer.from(data));
  72. }
  73. export function fromBase58(data) {
  74. return Buffer.from(bs58.decode(data));
  75. }
  76. //base-x слишком тормозит, используем sjcl
  77. export function toBase64(data) {
  78. return sjclWrapper.codec.base64.fromBits(
  79. sjclWrapper.codec.bytes.toBits(Buffer.from(data))
  80. );
  81. }
  82. //base-x слишком тормозит, используем sjcl
  83. export function fromBase64(data) {
  84. return Buffer.from(sjclWrapper.codec.bytes.fromBits(
  85. sjclWrapper.codec.base64.toBits(data)
  86. ));
  87. }
  88. export function getObjDiff(oldObj, newObj, opts = {}) {
  89. const {
  90. exclude = [],
  91. excludeAdd = [],
  92. excludeDel = [],
  93. } = opts;
  94. const ex = new Set(exclude);
  95. const exAdd = new Set(excludeAdd);
  96. const exDel = new Set(excludeDel);
  97. const makeObjDiff = (oldObj, newObj, keyPath) => {
  98. const result = {__isDiff: true, change: {}, add: {}, del: []};
  99. keyPath = `${keyPath}${keyPath ? '/' : ''}`;
  100. for (const key of Object.keys(oldObj)) {
  101. const kp = `${keyPath}${key}`;
  102. if (newObj.hasOwnProperty(key)) {
  103. if (ex.has(kp))
  104. continue;
  105. if (!_.isEqual(oldObj[key], newObj[key])) {
  106. if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) {
  107. result.change[key] = makeObjDiff(oldObj[key], newObj[key], kp);
  108. } else {
  109. result.change[key] = _.cloneDeep(newObj[key]);
  110. }
  111. }
  112. } else {
  113. if (exDel.has(kp))
  114. continue;
  115. result.del.push(key);
  116. }
  117. }
  118. for (const key of Object.keys(newObj)) {
  119. const kp = `${keyPath}${key}`;
  120. if (exAdd.has(kp))
  121. continue;
  122. if (!oldObj.hasOwnProperty(key)) {
  123. result.add[key] = _.cloneDeep(newObj[key]);
  124. }
  125. }
  126. return result;
  127. }
  128. return makeObjDiff(oldObj, newObj, '');
  129. }
  130. export function isObjDiff(diff) {
  131. return (_.isObject(diff) && diff.__isDiff && diff.change && diff.add && diff.del);
  132. }
  133. export function isEmptyObjDiff(diff) {
  134. return (!isObjDiff(diff) ||
  135. !(Object.keys(diff.change).length ||
  136. Object.keys(diff.add).length ||
  137. diff.del.length
  138. )
  139. );
  140. }
  141. export function isEmptyObjDiffDeep(diff, opts = {}) {
  142. if (!isObjDiff(diff))
  143. return true;
  144. const {
  145. isApplyChange = true,
  146. isApplyAdd = true,
  147. isApplyDel = true,
  148. } = opts;
  149. let notEmptyDeep = false;
  150. const change = diff.change;
  151. for (const key of Object.keys(change)) {
  152. if (_.isObject(change[key]))
  153. notEmptyDeep |= !isEmptyObjDiffDeep(change[key], opts);
  154. else if (isApplyChange)
  155. notEmptyDeep = true;
  156. }
  157. return !(
  158. notEmptyDeep ||
  159. (isApplyAdd && Object.keys(diff.add).length) ||
  160. (isApplyDel && diff.del.length)
  161. );
  162. }
  163. export function applyObjDiff(obj, diff, opts = {}) {
  164. const {
  165. isAddChanged = false,
  166. isApplyChange = true,
  167. isApplyAdd = true,
  168. isApplyDel = true,
  169. } = opts;
  170. let result = _.cloneDeep(obj);
  171. if (!diff.__isDiff)
  172. return result;
  173. const change = diff.change;
  174. for (const key of Object.keys(change)) {
  175. if (result.hasOwnProperty(key)) {
  176. if (_.isObject(change[key])) {
  177. result[key] = applyObjDiff(result[key], change[key], opts);
  178. } else {
  179. if (isApplyChange)
  180. result[key] = _.cloneDeep(change[key]);
  181. }
  182. } else if (isAddChanged) {
  183. result[key] = _.cloneDeep(change[key]);
  184. }
  185. }
  186. if (isApplyAdd) {
  187. for (const key of Object.keys(diff.add)) {
  188. result[key] = _.cloneDeep(diff.add[key]);
  189. }
  190. }
  191. if (isApplyDel && diff.del.length) {
  192. for (const key of diff.del) {
  193. delete result[key];
  194. }
  195. if (_.isArray(result))
  196. result = result.filter(v => v);
  197. }
  198. return result;
  199. }
  200. export function parseQuery(str) {
  201. if (typeof str != 'string' || str.length == 0)
  202. return {};
  203. let s = str.split('&');
  204. let s_length = s.length;
  205. let bit, query = {}, first, second;
  206. for (let i = 0; i < s_length; i++) {
  207. bit = s[i].split('=');
  208. first = decodeURIComponent(bit[0]);
  209. if (first.length == 0)
  210. continue;
  211. second = decodeURIComponent(bit[1]);
  212. if (typeof query[first] == 'undefined')
  213. query[first] = second;
  214. else
  215. if (query[first] instanceof Array)
  216. query[first].push(second);
  217. else
  218. query[first] = [query[first], second];
  219. }
  220. return query;
  221. }
  222. export function escapeXml(str) {
  223. return str.replace(/&/g, '&amp;')
  224. .replace(/</g, '&lt;')
  225. .replace(/>/g, '&gt;')
  226. .replace(/"/g, '&quot;')
  227. .replace(/'/g, '&apos;')
  228. ;
  229. }
  230. export function keyEventToCode(event) {
  231. let result = [];
  232. let code = event.code;
  233. const modCode = code.substring(0, 3);
  234. if (event.metaKey && modCode != 'Met')
  235. result.push('Meta');
  236. if (event.ctrlKey && modCode != 'Con')
  237. result.push('Ctrl');
  238. if (event.shiftKey && modCode != 'Shi')
  239. result.push('Shift');
  240. if (event.altKey && modCode != 'Alt')
  241. result.push('Alt');
  242. if (modCode == 'Dig') {
  243. code = code.substring(5, 6);
  244. } else if (modCode == 'Key') {
  245. code = code.substring(3, 4);
  246. }
  247. result.push(code);
  248. return result.join('+');
  249. }
  250. export function userHotKeysObjectSwap(userHotKeys) {
  251. let result = {};
  252. for (const [name, codes] of Object.entries(userHotKeys)) {
  253. for (const code of codes) {
  254. result[code] = name;
  255. }
  256. }
  257. return result;
  258. }
  259. export function removeHtmlTags(s) {
  260. return s.replace(/(<([^>]+)>)/ig, '');
  261. }
  262. export function makeValidFilename(filename, repl = '_') {
  263. let f = filename.replace(/[\x00\\/:*"<>|]/g, repl); // eslint-disable-line no-control-regex
  264. f = f.trim();
  265. while (f.length && (f[f.length - 1] == '.' || f[f.length - 1] == '_')) {
  266. f = f.substring(0, f.length - 1);
  267. }
  268. if (f)
  269. return f;
  270. else
  271. throw new Error('Invalid filename');
  272. }
  273. export function getBookTitle(fb2) {
  274. fb2 = (fb2 ? fb2 : {});
  275. const result = {};
  276. if (fb2.author) {
  277. const authorNames = fb2.author.map(a => _.compact([
  278. a.lastName,
  279. a.firstName,
  280. a.middleName
  281. ]).join(' '));
  282. result.author = authorNames.join(', ');
  283. }
  284. if (fb2.sequence) {
  285. const seqs = fb2.sequence.map(s => _.compact([
  286. s.name,
  287. (s.number ? `#${s.number}` : null),
  288. ]).join(' '));
  289. result.sequence = seqs.join(', ');
  290. if (result.sequence)
  291. result.sequenceTitle = `(${result.sequence})`;
  292. }
  293. result.bookTitle = _.compact([result.sequenceTitle, fb2.bookTitle]).join(' ');
  294. result.fullTitle = _.compact([
  295. result.author,
  296. result.bookTitle
  297. ]).join(' - ');
  298. return result;
  299. }