utils.js 10 KB

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