a zip code crypto-currency system good for red ONLY

url-serializer.js 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import { InjectionToken } from '@angular/core';
  2. import { isArray, isBlank, isPresent } from '../util/util';
  3. /**
  4. * @hidden
  5. */
  6. export class UrlSerializer {
  7. constructor(_app, config) {
  8. this._app = _app;
  9. if (config && isArray(config.links)) {
  10. this.links = normalizeLinks(config.links);
  11. }
  12. else {
  13. this.links = [];
  14. }
  15. }
  16. /**
  17. * Parse the URL into a Path, which is made up of multiple NavSegments.
  18. * Match which components belong to each segment.
  19. */
  20. parse(browserUrl) {
  21. if (browserUrl.charAt(0) === '/') {
  22. browserUrl = browserUrl.substr(1);
  23. }
  24. // trim off data after ? and #
  25. browserUrl = browserUrl.split('?')[0].split('#')[0];
  26. return convertUrlToSegments(this._app, browserUrl, this.links);
  27. }
  28. createSegmentFromName(navContainer, nameOrComponent) {
  29. const configLink = this.getLinkFromName(nameOrComponent);
  30. if (configLink) {
  31. return this._createSegment(this._app, navContainer, configLink, null);
  32. }
  33. return null;
  34. }
  35. getLinkFromName(nameOrComponent) {
  36. return this.links.find(link => {
  37. return (link.component === nameOrComponent) ||
  38. (link.name === nameOrComponent);
  39. });
  40. }
  41. /**
  42. * Serialize a path, which is made up of multiple NavSegments,
  43. * into a URL string. Turn each segment into a string and concat them to a URL.
  44. */
  45. serialize(segments) {
  46. if (!segments || !segments.length) {
  47. return '/';
  48. }
  49. const sections = segments.map(segment => {
  50. if (segment.type === 'tabs') {
  51. if (segment.requiresExplicitNavPrefix) {
  52. return `/${segment.type}/${segment.navId}/${segment.secondaryId}/${segment.id}`;
  53. }
  54. return `/${segment.secondaryId}/${segment.id}`;
  55. }
  56. // it's a nav
  57. if (segment.requiresExplicitNavPrefix) {
  58. return `/${segment.type}/${segment.navId}/${segment.id}`;
  59. }
  60. return `/${segment.id}`;
  61. });
  62. return sections.join('');
  63. }
  64. /**
  65. * Serializes a component and its data into a NavSegment.
  66. */
  67. serializeComponent(navContainer, component, data) {
  68. if (component) {
  69. const link = findLinkByComponentData(this.links, component, data);
  70. if (link) {
  71. return this._createSegment(this._app, navContainer, link, data);
  72. }
  73. }
  74. return null;
  75. }
  76. /**
  77. * @internal
  78. */
  79. _createSegment(app, navContainer, configLink, data) {
  80. let urlParts = configLink.segmentParts;
  81. if (isPresent(data)) {
  82. // create a copy of the original parts in the link config
  83. urlParts = urlParts.slice();
  84. // loop through all the data and convert it to a string
  85. const keys = Object.keys(data);
  86. const keysLength = keys.length;
  87. if (keysLength) {
  88. for (var i = 0; i < urlParts.length; i++) {
  89. if (urlParts[i].charAt(0) === ':') {
  90. for (var j = 0; j < keysLength; j++) {
  91. if (urlParts[i] === `:${keys[j]}`) {
  92. // this data goes into the URL part (between slashes)
  93. urlParts[i] = encodeURIComponent(data[keys[j]]);
  94. break;
  95. }
  96. }
  97. }
  98. }
  99. }
  100. }
  101. let requiresExplicitPrefix = true;
  102. if (navContainer.parent) {
  103. requiresExplicitPrefix = navContainer.parent && navContainer.parent.getAllChildNavs().length > 1;
  104. }
  105. else {
  106. // if it's a root nav, and there are multiple root navs, we need an explicit prefix
  107. requiresExplicitPrefix = app.getRootNavById(navContainer.id) && app.getRootNavs().length > 1;
  108. }
  109. return {
  110. id: urlParts.join('/'),
  111. name: configLink.name,
  112. component: configLink.component,
  113. loadChildren: configLink.loadChildren,
  114. data: data,
  115. defaultHistory: configLink.defaultHistory,
  116. navId: navContainer.name || navContainer.id,
  117. type: navContainer.getType(),
  118. secondaryId: navContainer.getSecondaryIdentifier(),
  119. requiresExplicitNavPrefix: requiresExplicitPrefix
  120. };
  121. }
  122. }
  123. export function formatUrlPart(name) {
  124. name = name.replace(URL_REPLACE_REG, '-');
  125. name = name.charAt(0).toLowerCase() + name.substring(1).replace(/[A-Z]/g, match => {
  126. return '-' + match.toLowerCase();
  127. });
  128. while (name.indexOf('--') > -1) {
  129. name = name.replace('--', '-');
  130. }
  131. if (name.charAt(0) === '-') {
  132. name = name.substring(1);
  133. }
  134. if (name.substring(name.length - 1) === '-') {
  135. name = name.substring(0, name.length - 1);
  136. }
  137. return encodeURIComponent(name);
  138. }
  139. export const isPartMatch = (urlPart, configLinkPart) => {
  140. if (isPresent(urlPart) && isPresent(configLinkPart)) {
  141. if (configLinkPart.charAt(0) === ':') {
  142. return true;
  143. }
  144. return (urlPart === configLinkPart);
  145. }
  146. return false;
  147. };
  148. export const createMatchedData = (matchedUrlParts, link) => {
  149. let data = null;
  150. for (var i = 0; i < link.segmentPartsLen; i++) {
  151. if (link.segmentParts[i].charAt(0) === ':') {
  152. data = data || {};
  153. data[link.segmentParts[i].substring(1)] = decodeURIComponent(matchedUrlParts[i]);
  154. }
  155. }
  156. return data;
  157. };
  158. export const findLinkByComponentData = (links, component, instanceData) => {
  159. let foundLink = null;
  160. let foundLinkDataMatches = -1;
  161. for (var i = 0; i < links.length; i++) {
  162. var link = links[i];
  163. if (link.component === component) {
  164. // ok, so the component matched, but multiple links can point
  165. // to the same component, so let's make sure this is the right link
  166. var dataMatches = 0;
  167. if (instanceData) {
  168. var instanceDataKeys = Object.keys(instanceData);
  169. // this link has data
  170. for (var j = 0; j < instanceDataKeys.length; j++) {
  171. if (isPresent(link.dataKeys[instanceDataKeys[j]])) {
  172. dataMatches++;
  173. }
  174. }
  175. }
  176. else if (link.dataLen) {
  177. // this component does not have data but the link does
  178. continue;
  179. }
  180. if (dataMatches >= foundLinkDataMatches) {
  181. foundLink = link;
  182. foundLinkDataMatches = dataMatches;
  183. }
  184. }
  185. }
  186. return foundLink;
  187. };
  188. export const normalizeLinks = (links) => {
  189. for (var i = 0, ilen = links.length; i < ilen; i++) {
  190. var link = links[i];
  191. if (isBlank(link.segment)) {
  192. link.segment = link.name;
  193. }
  194. link.dataKeys = {};
  195. link.segmentParts = link.segment.split('/');
  196. link.segmentPartsLen = link.segmentParts.length;
  197. // used for sorting
  198. link.staticLen = link.dataLen = 0;
  199. var stillCountingStatic = true;
  200. for (var j = 0; j < link.segmentPartsLen; j++) {
  201. if (link.segmentParts[j].charAt(0) === ':') {
  202. link.dataLen++;
  203. stillCountingStatic = false;
  204. link.dataKeys[link.segmentParts[j].substring(1)] = true;
  205. }
  206. else if (stillCountingStatic) {
  207. link.staticLen++;
  208. }
  209. }
  210. }
  211. // sort by the number of parts, with the links
  212. // with the most parts first
  213. return links.sort(sortConfigLinks);
  214. };
  215. function sortConfigLinks(a, b) {
  216. // sort by the number of parts
  217. if (a.segmentPartsLen > b.segmentPartsLen) {
  218. return -1;
  219. }
  220. if (a.segmentPartsLen < b.segmentPartsLen) {
  221. return 1;
  222. }
  223. // sort by the number of static parts in a row
  224. if (a.staticLen > b.staticLen) {
  225. return -1;
  226. }
  227. if (a.staticLen < b.staticLen) {
  228. return 1;
  229. }
  230. // sort by the number of total data parts
  231. if (a.dataLen < b.dataLen) {
  232. return -1;
  233. }
  234. if (a.dataLen > b.dataLen) {
  235. return 1;
  236. }
  237. return 0;
  238. }
  239. const URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`|>|<|;|:|@|&|=/g;
  240. /**
  241. * @hidden
  242. */
  243. export const DeepLinkConfigToken = new InjectionToken('USERLINKS');
  244. export function setupUrlSerializer(app, userDeepLinkConfig) {
  245. return new UrlSerializer(app, userDeepLinkConfig);
  246. }
  247. export function navGroupStringtoObjects(navGroupStrings) {
  248. // each string has a known format-ish, convert it to it
  249. return navGroupStrings.map(navGroupString => {
  250. const sections = navGroupString.split('/');
  251. if (sections[0] === 'nav') {
  252. return {
  253. type: 'nav',
  254. navId: sections[1],
  255. niceId: sections[1],
  256. secondaryId: null,
  257. segmentPieces: sections.splice(2)
  258. };
  259. }
  260. else if (sections[0] === 'tabs') {
  261. return {
  262. type: 'tabs',
  263. navId: sections[1],
  264. niceId: sections[1],
  265. secondaryId: sections[2],
  266. segmentPieces: sections.splice(3)
  267. };
  268. }
  269. return {
  270. type: null,
  271. navId: null,
  272. niceId: null,
  273. secondaryId: null,
  274. segmentPieces: sections
  275. };
  276. });
  277. }
  278. export function urlToNavGroupStrings(url) {
  279. const tokens = url.split('/');
  280. const keywordIndexes = [];
  281. for (let i = 0; i < tokens.length; i++) {
  282. if (i !== 0 && (tokens[i] === 'nav' || tokens[i] === 'tabs')) {
  283. keywordIndexes.push(i);
  284. }
  285. }
  286. // append the last index + 1 to the list no matter what
  287. keywordIndexes.push(tokens.length);
  288. const groupings = [];
  289. let activeKeywordIndex = 0;
  290. let tmpArray = [];
  291. for (let i = 0; i < tokens.length; i++) {
  292. if (i >= keywordIndexes[activeKeywordIndex]) {
  293. groupings.push(tmpArray.join('/'));
  294. tmpArray = [];
  295. activeKeywordIndex++;
  296. }
  297. tmpArray.push(tokens[i]);
  298. }
  299. // okay, after the loop we've gotta push one more time just to be safe
  300. groupings.push(tmpArray.join('/'));
  301. return groupings;
  302. }
  303. export function convertUrlToSegments(app, url, navLinks) {
  304. const pairs = convertUrlToDehydratedSegments(url, navLinks);
  305. return hydrateSegmentsWithNav(app, pairs);
  306. }
  307. export function convertUrlToDehydratedSegments(url, navLinks) {
  308. const navGroupStrings = urlToNavGroupStrings(url);
  309. const navGroups = navGroupStringtoObjects(navGroupStrings);
  310. return getSegmentsFromNavGroups(navGroups, navLinks);
  311. }
  312. export function hydrateSegmentsWithNav(app, dehydratedSegmentPairs) {
  313. const segments = [];
  314. for (let i = 0; i < dehydratedSegmentPairs.length; i++) {
  315. let navs = getNavFromNavGroup(dehydratedSegmentPairs[i].navGroup, app);
  316. // okay, cool, let's walk through the segments and hydrate them
  317. for (const dehydratedSegment of dehydratedSegmentPairs[i].segments) {
  318. if (navs.length === 1) {
  319. segments.push(hydrateSegment(dehydratedSegment, navs[0]));
  320. navs = navs[0].getActiveChildNavs();
  321. }
  322. else if (navs.length > 1) {
  323. // this is almost certainly an async race condition bug in userland
  324. // if you're in this state, it would be nice to just bail here
  325. // but alas we must perservere and handle the issue
  326. // the simple solution is to just use the last child
  327. // because that is probably what the user wants anyway
  328. // remember, do not harm, even if it makes our shizzle ugly
  329. segments.push(hydrateSegment(dehydratedSegment, navs[navs.length - 1]));
  330. navs = navs[navs.length - 1].getActiveChildNavs();
  331. }
  332. else {
  333. break;
  334. }
  335. }
  336. }
  337. return segments;
  338. }
  339. export function getNavFromNavGroup(navGroup, app) {
  340. if (navGroup.navId) {
  341. const rootNav = app.getNavByIdOrName(navGroup.navId);
  342. if (rootNav) {
  343. return [rootNav];
  344. }
  345. return [];
  346. }
  347. // we don't know what nav to use, so just use the root nav.
  348. // if there is more than one root nav, throw an error
  349. return app.getRootNavs();
  350. }
  351. /*
  352. * Let's face the facts: Getting a dehydrated segment from the url is really hard
  353. * because we need to do a ton of crazy looping
  354. * the are chunks of a url that are totally irrelevant at this stage, such as the secondary identifier
  355. * stating which tab is selected, etc.
  356. * but is necessary.
  357. * We look at segment pieces in reverse order to try to build segments
  358. * as in, if you had an array like this
  359. * ['my', 'super', 'cool', 'url']
  360. * we want to look at the pieces in reverse order:
  361. * url
  362. * cool url
  363. * super cool url
  364. * my super cool url
  365. * cool
  366. * super cool
  367. * my super cool
  368. * super
  369. * my super
  370. * my
  371. **/
  372. export function getSegmentsFromNavGroups(navGroups, navLinks) {
  373. const pairs = [];
  374. const usedNavLinks = new Set();
  375. for (const navGroup of navGroups) {
  376. const segments = [];
  377. const segmentPieces = navGroup.segmentPieces.concat([]);
  378. for (let i = segmentPieces.length; i >= 0; i--) {
  379. let created = false;
  380. for (let j = 0; j < i; j++) {
  381. const startIndex = i - j - 1;
  382. const endIndex = i;
  383. const subsetOfUrl = segmentPieces.slice(startIndex, endIndex);
  384. for (const navLink of navLinks) {
  385. if (!usedNavLinks.has(navLink.name)) {
  386. const segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink);
  387. if (segment) {
  388. i = startIndex + 1;
  389. usedNavLinks.add(navLink.name);
  390. created = true;
  391. // sweet, we found a segment
  392. segments.push(segment);
  393. // now we want to null out the url subsection in the segmentPieces
  394. for (let k = startIndex; k < endIndex; k++) {
  395. segmentPieces[k] = null;
  396. }
  397. break;
  398. }
  399. }
  400. }
  401. if (created) {
  402. break;
  403. }
  404. }
  405. if (!created && segmentPieces[i - 1]) {
  406. // this is very likely a tab's secondary identifier
  407. segments.push({
  408. id: null,
  409. name: null,
  410. secondaryId: segmentPieces[i - 1],
  411. component: null,
  412. loadChildren: null,
  413. data: null,
  414. defaultHistory: null
  415. });
  416. }
  417. }
  418. // since we're getting segments in from right-to-left in the url, reverse them
  419. // so they're in the correct order. Also filter out and bogus segments
  420. const orderedSegments = segments.reverse();
  421. // okay, this is the lazy persons approach here.
  422. // so here's the deal! Right now if section of the url is not a part of a segment
  423. // it is almost certainly the secondaryId for a tabs component
  424. // basically, knowing the segment for the `tab` itself is good, but we also need to know
  425. // which tab is selected, so we have an identifer in the url that is associated with the tabs component
  426. // telling us which tab is selected. With that in mind, we are going to go through and find the segments with only secondary identifiers,
  427. // and simply add the secondaryId to the next segment, and then remove the empty segment from the list
  428. for (let i = 0; i < orderedSegments.length; i++) {
  429. if (orderedSegments[i].secondaryId && !orderedSegments[i].id && ((i + 1) <= orderedSegments.length - 1)) {
  430. orderedSegments[i + 1].secondaryId = orderedSegments[i].secondaryId;
  431. orderedSegments[i] = null;
  432. }
  433. }
  434. const cleanedSegments = segments.filter(segment => !!segment);
  435. // if the nav group has a secondary id, make sure the first segment also has it set
  436. if (navGroup.secondaryId && segments.length) {
  437. cleanedSegments[0].secondaryId = navGroup.secondaryId;
  438. }
  439. pairs.push({
  440. navGroup: navGroup,
  441. segments: cleanedSegments
  442. });
  443. }
  444. return pairs;
  445. }
  446. export function getSegmentsFromUrlPieces(urlSections, navLink) {
  447. if (navLink.segmentPartsLen !== urlSections.length) {
  448. return null;
  449. }
  450. for (let i = 0; i < urlSections.length; i++) {
  451. if (!isPartMatch(urlSections[i], navLink.segmentParts[i])) {
  452. // just return an empty array if the part doesn't match
  453. return null;
  454. }
  455. }
  456. return {
  457. id: urlSections.join('/'),
  458. name: navLink.name,
  459. component: navLink.component,
  460. loadChildren: navLink.loadChildren,
  461. data: createMatchedData(urlSections, navLink),
  462. defaultHistory: navLink.defaultHistory
  463. };
  464. }
  465. export function hydrateSegment(segment, nav) {
  466. const hydratedSegment = Object.assign({}, segment);
  467. hydratedSegment.type = nav.getType();
  468. hydratedSegment.navId = nav.name || nav.id;
  469. // secondaryId is set on an empty dehydrated segment in the case of tabs to identify which tab is selected
  470. hydratedSegment.secondaryId = segment.secondaryId;
  471. return hydratedSegment;
  472. }
  473. export function getNonHydratedSegmentIfLinkAndUrlMatch(urlChunks, navLink) {
  474. let allSegmentsMatch = true;
  475. for (let i = 0; i < urlChunks.length; i++) {
  476. if (!isPartMatch(urlChunks[i], navLink.segmentParts[i])) {
  477. allSegmentsMatch = false;
  478. break;
  479. }
  480. }
  481. if (allSegmentsMatch) {
  482. return {
  483. id: navLink.segmentParts.join('/'),
  484. name: navLink.name,
  485. component: navLink.component,
  486. loadChildren: navLink.loadChildren,
  487. data: createMatchedData(urlChunks, navLink),
  488. defaultHistory: navLink.defaultHistory
  489. };
  490. }
  491. return null;
  492. }
  493. //# sourceMappingURL=url-serializer.js.map