utils.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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 'coMonth':
  39. return `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
  40. case 'noDate':
  41. return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()}`;
  42. }
  43. }
  44. export function fallbackCopyTextToClipboard(text) {
  45. let textArea = document.createElement('textarea');
  46. textArea.value = text;
  47. document.body.appendChild(textArea);
  48. textArea.focus();
  49. textArea.select();
  50. let result = false;
  51. try {
  52. result = document.execCommand('copy');
  53. } catch (e) {
  54. //
  55. }
  56. document.body.removeChild(textArea);
  57. return result;
  58. }
  59. export async function copyTextToClipboard(text) {
  60. if (!navigator.clipboard) {
  61. return fallbackCopyTextToClipboard(text);
  62. }
  63. let result = false;
  64. try {
  65. await navigator.clipboard.writeText(text);
  66. result = true;
  67. } catch (e) {
  68. //
  69. }
  70. return result;
  71. }
  72. export function toBase58(data) {
  73. return bs58.encode(Buffer.from(data));
  74. }
  75. export function fromBase58(data) {
  76. return Buffer.from(bs58.decode(data));
  77. }
  78. //base-x слишком тормозит, используем sjcl
  79. export function toBase64(data) {
  80. return sjclWrapper.codec.base64.fromBits(
  81. sjclWrapper.codec.bytes.toBits(Buffer.from(data))
  82. );
  83. }
  84. //base-x слишком тормозит, используем sjcl
  85. export function fromBase64(data) {
  86. return Buffer.from(sjclWrapper.codec.bytes.fromBits(
  87. sjclWrapper.codec.base64.toBits(data)
  88. ));
  89. }
  90. export function hasProp(obj, prop) {
  91. return Object.prototype.hasOwnProperty.call(obj, prop);
  92. }
  93. export function getObjDiff(oldObj, newObj, opts = {}) {
  94. const {
  95. exclude = [],
  96. excludeAdd = [],
  97. excludeDel = [],
  98. } = opts;
  99. const ex = new Set(exclude);
  100. const exAdd = new Set(excludeAdd);
  101. const exDel = new Set(excludeDel);
  102. const makeObjDiff = (oldObj, newObj, keyPath) => {
  103. const result = {__isDiff: true, change: {}, add: {}, del: []};
  104. keyPath = `${keyPath}${keyPath ? '/' : ''}`;
  105. for (const key of Object.keys(oldObj)) {
  106. const kp = `${keyPath}${key}`;
  107. if (Object.prototype.hasOwnProperty.call(newObj, key)) {
  108. if (ex.has(kp))
  109. continue;
  110. if (!_.isEqual(oldObj[key], newObj[key])) {
  111. if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) {
  112. result.change[key] = makeObjDiff(oldObj[key], newObj[key], kp);
  113. } else {
  114. result.change[key] = _.cloneDeep(newObj[key]);
  115. }
  116. }
  117. } else {
  118. if (exDel.has(kp))
  119. continue;
  120. result.del.push(key);
  121. }
  122. }
  123. for (const key of Object.keys(newObj)) {
  124. const kp = `${keyPath}${key}`;
  125. if (exAdd.has(kp))
  126. continue;
  127. if (!Object.prototype.hasOwnProperty.call(oldObj, key)) {
  128. result.add[key] = _.cloneDeep(newObj[key]);
  129. }
  130. }
  131. return result;
  132. }
  133. return makeObjDiff(oldObj, newObj, '');
  134. }
  135. export function isObjDiff(diff) {
  136. return (_.isObject(diff) && diff.__isDiff && diff.change && diff.add && diff.del);
  137. }
  138. export function isEmptyObjDiff(diff) {
  139. return (!isObjDiff(diff) ||
  140. !(Object.keys(diff.change).length ||
  141. Object.keys(diff.add).length ||
  142. diff.del.length
  143. )
  144. );
  145. }
  146. export function isEmptyObjDiffDeep(diff, opts = {}) {
  147. if (!isObjDiff(diff))
  148. return true;
  149. const {
  150. isApplyChange = true,
  151. isApplyAdd = true,
  152. isApplyDel = true,
  153. } = opts;
  154. let notEmptyDeep = false;
  155. const change = diff.change;
  156. for (const key of Object.keys(change)) {
  157. if (_.isObject(change[key]))
  158. notEmptyDeep |= !isEmptyObjDiffDeep(change[key], opts);
  159. else if (isApplyChange)
  160. notEmptyDeep = true;
  161. }
  162. return !(
  163. notEmptyDeep ||
  164. (isApplyAdd && Object.keys(diff.add).length) ||
  165. (isApplyDel && diff.del.length)
  166. );
  167. }
  168. export function applyObjDiff(obj, diff, opts = {}) {
  169. const {
  170. isAddChanged = false,
  171. isApplyChange = true,
  172. isApplyAdd = true,
  173. isApplyDel = true,
  174. } = opts;
  175. let result = _.cloneDeep(obj);
  176. if (!diff.__isDiff)
  177. return result;
  178. const change = diff.change;
  179. for (const key of Object.keys(change)) {
  180. if (Object.prototype.hasOwnProperty.call(result, key)) {
  181. if (_.isObject(change[key])) {
  182. result[key] = applyObjDiff(result[key], change[key], opts);
  183. } else {
  184. if (isApplyChange)
  185. result[key] = _.cloneDeep(change[key]);
  186. }
  187. } else if (isAddChanged) {
  188. result[key] = _.cloneDeep(change[key]);
  189. }
  190. }
  191. if (isApplyAdd) {
  192. for (const key of Object.keys(diff.add)) {
  193. result[key] = _.cloneDeep(diff.add[key]);
  194. }
  195. }
  196. if (isApplyDel && diff.del.length) {
  197. for (const key of diff.del) {
  198. delete result[key];
  199. }
  200. if (_.isArray(result))
  201. result = result.filter(v => v);
  202. }
  203. return result;
  204. }
  205. export function parseQuery(str) {
  206. if (typeof str != 'string' || str.length == 0)
  207. return {};
  208. let s = str.split('&');
  209. let s_length = s.length;
  210. let bit, query = {}, first, second;
  211. for (let i = 0; i < s_length; i++) {
  212. bit = s[i].split('=');
  213. first = decodeURIComponent(bit[0]);
  214. if (first.length == 0)
  215. continue;
  216. second = decodeURIComponent(bit[1]);
  217. if (typeof query[first] == 'undefined')
  218. query[first] = second;
  219. else
  220. if (query[first] instanceof Array)
  221. query[first].push(second);
  222. else
  223. query[first] = [query[first], second];
  224. }
  225. return query;
  226. }
  227. export function escapeXml(str) {
  228. return str.replace(/&/g, '&amp;')
  229. .replace(/</g, '&lt;')
  230. .replace(/>/g, '&gt;')
  231. .replace(/"/g, '&quot;')
  232. .replace(/'/g, '&apos;')
  233. ;
  234. }
  235. export function keyEventToCode(event) {
  236. let result = [];
  237. let code = event.code;
  238. const modCode = code.substring(0, 3);
  239. if (event.metaKey && modCode != 'Met')
  240. result.push('Meta');
  241. if (event.ctrlKey && modCode != 'Con')
  242. result.push('Ctrl');
  243. if (event.shiftKey && modCode != 'Shi')
  244. result.push('Shift');
  245. if (event.altKey && modCode != 'Alt')
  246. result.push('Alt');
  247. if (modCode == 'Dig') {
  248. code = code.substring(5, 6);
  249. } else if (modCode == 'Key') {
  250. code = code.substring(3, 4);
  251. }
  252. result.push(code);
  253. return result.join('+');
  254. }
  255. export function userHotKeysObjectSwap(userHotKeys) {
  256. let result = {};
  257. for (const [name, codes] of Object.entries(userHotKeys)) {
  258. for (const code of codes) {
  259. result[code] = name;
  260. }
  261. }
  262. return result;
  263. }
  264. export function removeHtmlTags(s) {
  265. return s.replace(/(<([^>]+)>)/ig, '');
  266. }
  267. export function makeValidFilename(filename, repl = '_') {
  268. let f = filename.replace(/[\x00\\/:*"<>|]/g, repl); // eslint-disable-line no-control-regex
  269. f = f.trim();
  270. while (f.length && (f[f.length - 1] == '.' || f[f.length - 1] == '_')) {
  271. f = f.substring(0, f.length - 1);
  272. }
  273. if (f)
  274. return f;
  275. else
  276. throw new Error('Invalid filename');
  277. }
  278. export function getBookTitle(fb2) {
  279. fb2 = (fb2 ? fb2 : {});
  280. const result = {};
  281. if (fb2.author) {
  282. const authorNames = fb2.author.map(a => _.compact([
  283. a.lastName,
  284. a.firstName,
  285. a.middleName
  286. ]).join(' '));
  287. result.author = authorNames.join(', ');
  288. }
  289. if (fb2.sequence) {
  290. const seqs = fb2.sequence.map(s => _.compact([
  291. s.name,
  292. (s.number ? `#${s.number}` : null),
  293. ]).join(' '));
  294. result.sequence = seqs.join(', ');
  295. if (result.sequence)
  296. result.sequenceTitle = `(${result.sequence})`;
  297. }
  298. result.bookTitle = _.compact([result.sequenceTitle, fb2.bookTitle]).join(' ');
  299. result.fullTitle = _.compact([
  300. result.author,
  301. result.bookTitle
  302. ]).join(' - ');
  303. return result;
  304. }
  305. export function resizeImage(dataUrl, toWidth, toHeight, quality = 0.9) {
  306. return new Promise ((resolve, reject) => { (async() => {
  307. const img = new Image();
  308. let resolved = false;
  309. img.onload = () => {
  310. try {
  311. let width = img.width;
  312. let height = img.height;
  313. if (width > height) {
  314. if (width > toWidth) {
  315. height = height * (toWidth / width);
  316. width = toWidth;
  317. }
  318. } else {
  319. if (height > toHeight) {
  320. width = width * (toHeight / height);
  321. height = toHeight;
  322. }
  323. }
  324. const canvas = document.createElement('canvas');
  325. canvas.width = width;
  326. canvas.height = height;
  327. const ctx = canvas.getContext('2d');
  328. ctx.drawImage(img, 0, 0, width, height);
  329. const result = canvas.toDataURL('image/jpeg', quality);
  330. resolved = true;
  331. resolve(result);
  332. } catch (e) {
  333. reject(e);
  334. return;
  335. }
  336. };
  337. img.onerror = reject;
  338. img.src = dataUrl;
  339. await sleep(1000);
  340. if (!resolved)
  341. reject('Не удалось изменить размер');
  342. })().catch(reject); });
  343. }
  344. export function makeDonation() {
  345. window.open('https://donatty.com/liberama', '_blank');
  346. }