a zip code crypto-currency system good for red ONLY

deep-linker.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. import { DIRECTION_BACK, convertToViews, isNav, isTab, isTabs } from './nav-util';
  2. import { isArray, isPresent } from '../util/util';
  3. import { formatUrlPart } from './url-serializer';
  4. import { ViewController } from './view-controller';
  5. /**
  6. * @hidden
  7. */
  8. export class DeepLinker {
  9. constructor(_app, _serializer, _location, _moduleLoader, _baseCfr) {
  10. this._app = _app;
  11. this._serializer = _serializer;
  12. this._location = _location;
  13. this._moduleLoader = _moduleLoader;
  14. this._baseCfr = _baseCfr;
  15. /** @internal */
  16. this._history = [];
  17. }
  18. /**
  19. * @internal
  20. */
  21. init() {
  22. // scenario 1: Initial load of all navs from the initial browser URL
  23. const browserUrl = normalizeUrl(this._location.path());
  24. (void 0) /* console.debug */;
  25. // remember this URL in our internal history stack
  26. this._historyPush(browserUrl);
  27. // listen for browser URL changes
  28. this._location.subscribe((locationChg) => {
  29. this._urlChange(normalizeUrl(locationChg.url));
  30. });
  31. }
  32. /**
  33. * The browser's location has been updated somehow.
  34. * @internal
  35. */
  36. _urlChange(browserUrl) {
  37. // do nothing if this url is the same as the current one
  38. if (!this._isCurrentUrl(browserUrl)) {
  39. let isGoingBack = true;
  40. if (this._isBackUrl(browserUrl)) {
  41. // scenario 2: user clicked the browser back button
  42. // scenario 4: user changed the browser URL to what was the back url was
  43. // scenario 5: user clicked a link href that was the back url
  44. (void 0) /* console.debug */;
  45. this._historyPop();
  46. }
  47. else {
  48. // scenario 3: user click forward button
  49. // scenario 4: user changed browser URL that wasn't the back url
  50. // scenario 5: user clicked a link href that wasn't the back url
  51. isGoingBack = false;
  52. (void 0) /* console.debug */;
  53. this._historyPush(browserUrl);
  54. }
  55. // get the app's root nav container
  56. const activeNavContainers = this._app.getActiveNavContainers();
  57. if (activeNavContainers && activeNavContainers.length) {
  58. if (browserUrl === '/') {
  59. // a url change to the index url
  60. if (isPresent(this._indexAliasUrl)) {
  61. // we already know the indexAliasUrl
  62. // update the url to use the know alias
  63. browserUrl = this._indexAliasUrl;
  64. }
  65. else {
  66. // the url change is to the root but we don't
  67. // already know the url used. So let's just
  68. // reset the root nav to its root page
  69. activeNavContainers.forEach((navContainer) => {
  70. navContainer.goToRoot({
  71. updateUrl: false,
  72. isNavRoot: true
  73. });
  74. });
  75. return;
  76. }
  77. }
  78. // normal url
  79. const segments = this.getCurrentSegments(browserUrl);
  80. segments
  81. .map(segment => {
  82. // find the matching nav container
  83. for (const navContainer of activeNavContainers) {
  84. const nav = getNavFromTree(navContainer, segment.navId);
  85. if (nav) {
  86. return {
  87. segment: segment,
  88. navContainer: nav
  89. };
  90. }
  91. }
  92. })
  93. .filter(pair => !!pair)
  94. .forEach(pair => {
  95. this._loadViewForSegment(pair.navContainer, pair.segment, () => { });
  96. });
  97. }
  98. }
  99. }
  100. getCurrentSegments(browserUrl) {
  101. if (!browserUrl) {
  102. browserUrl = normalizeUrl(this._location.path());
  103. }
  104. return this._serializer.parse(browserUrl);
  105. }
  106. /**
  107. * Update the deep linker using the NavController's current active view.
  108. * @internal
  109. */
  110. navChange(direction) {
  111. if (direction) {
  112. const activeNavContainers = this._app.getActiveNavContainers();
  113. // the only time you'll ever get a TABS here is when loading directly from a URL
  114. // this method will be called again when the TAB is loaded
  115. // so just don't worry about the TABS for now
  116. // if you encounter a TABS, just return
  117. for (const activeNavContainer of activeNavContainers) {
  118. if (isTabs(activeNavContainer) || activeNavContainer.isTransitioning()) {
  119. return;
  120. }
  121. }
  122. // okay, get the root navs and build the segments up
  123. let segments = [];
  124. const navContainers = this._app.getRootNavs();
  125. for (const navContainer of navContainers) {
  126. const segmentsForNav = this.getSegmentsFromNav(navContainer);
  127. segments = segments.concat(segmentsForNav);
  128. }
  129. segments = segments.filter(segment => !!segment);
  130. if (segments.length) {
  131. const browserUrl = this._serializer.serialize(segments);
  132. this._updateLocation(browserUrl, direction);
  133. }
  134. }
  135. }
  136. getSegmentsFromNav(nav) {
  137. let segments = [];
  138. if (isNav(nav)) {
  139. segments.push(this.getSegmentFromNav(nav));
  140. }
  141. else if (isTab(nav)) {
  142. segments.push(this.getSegmentFromTab(nav));
  143. }
  144. nav.getActiveChildNavs().forEach(child => {
  145. segments = segments.concat(this.getSegmentsFromNav(child));
  146. });
  147. return segments;
  148. }
  149. getSegmentFromNav(nav, component, data) {
  150. if (!component) {
  151. const viewController = nav.getActive(true);
  152. if (viewController) {
  153. component = viewController.component;
  154. data = viewController.data;
  155. }
  156. }
  157. return this._serializer.serializeComponent(nav, component, data);
  158. }
  159. getSegmentFromTab(navContainer, component, data) {
  160. if (navContainer && navContainer.parent) {
  161. const tabsNavContainer = navContainer.parent;
  162. const activeChildNavs = tabsNavContainer.getActiveChildNavs();
  163. if (activeChildNavs && activeChildNavs.length) {
  164. const activeChildNav = activeChildNavs[0];
  165. const viewController = activeChildNav.getActive(true);
  166. if (viewController) {
  167. component = viewController.component;
  168. data = viewController.data;
  169. }
  170. return this._serializer.serializeComponent(tabsNavContainer, component, data);
  171. }
  172. }
  173. }
  174. /**
  175. * @internal
  176. */
  177. _updateLocation(browserUrl, direction) {
  178. if (this._indexAliasUrl === browserUrl) {
  179. browserUrl = '/';
  180. }
  181. if (direction === DIRECTION_BACK && this._isBackUrl(browserUrl)) {
  182. // this URL is exactly the same as the back URL
  183. // it's safe to use the browser's location.back()
  184. (void 0) /* console.debug */;
  185. this._historyPop();
  186. this._location.back();
  187. }
  188. else if (!this._isCurrentUrl(browserUrl)) {
  189. // probably navigating forward
  190. (void 0) /* console.debug */;
  191. this._historyPush(browserUrl);
  192. this._location.go(browserUrl);
  193. }
  194. }
  195. getComponentFromName(componentName) {
  196. const link = this._serializer.getLinkFromName(componentName);
  197. if (link) {
  198. // cool, we found the right link for this component name
  199. return this.getNavLinkComponent(link);
  200. }
  201. // umm, idk
  202. return Promise.reject(`invalid link: ${componentName}`);
  203. }
  204. getNavLinkComponent(link) {
  205. if (link.component) {
  206. // sweet, we're already got a component loaded for this link
  207. return Promise.resolve(link.component);
  208. }
  209. if (link.loadChildren) {
  210. // awesome, looks like we'll lazy load this component
  211. // using loadChildren as the URL to request
  212. return this._moduleLoader.load(link.loadChildren).then((response) => {
  213. link.component = response.component;
  214. return response.component;
  215. });
  216. }
  217. return Promise.reject(`invalid link component: ${link.name}`);
  218. }
  219. /**
  220. * @internal
  221. */
  222. resolveComponent(component) {
  223. let cfr = this._moduleLoader.getComponentFactoryResolver(component);
  224. if (!cfr) {
  225. cfr = this._baseCfr;
  226. }
  227. return cfr.resolveComponentFactory(component);
  228. }
  229. /**
  230. * @internal
  231. */
  232. createUrl(navContainer, nameOrComponent, _data, prepareExternalUrl = true) {
  233. // create a segment out of just the passed in name
  234. const segment = this._serializer.createSegmentFromName(navContainer, nameOrComponent);
  235. const allSegments = this.getCurrentSegments();
  236. if (segment) {
  237. for (let i = 0; i < allSegments.length; i++) {
  238. if (allSegments[i].navId === navContainer.name || allSegments[i].navId === navContainer.id) {
  239. allSegments[i] = segment;
  240. const url = this._serializer.serialize(allSegments);
  241. return prepareExternalUrl ? this._location.prepareExternalUrl(url) : url;
  242. }
  243. }
  244. }
  245. return '';
  246. }
  247. /**
  248. * Each NavController will call this method when it initializes for
  249. * the first time. This allows each NavController to figure out
  250. * where it lives in the path and load up the correct component.
  251. * @internal
  252. */
  253. getSegmentByNavIdOrName(navId, name) {
  254. const browserUrl = normalizeUrl(this._location.path());
  255. const segments = this._serializer.parse(browserUrl);
  256. for (const segment of segments) {
  257. if (segment.navId === navId || segment.navId === name) {
  258. return segment;
  259. }
  260. }
  261. return null;
  262. }
  263. /**
  264. * @internal
  265. */
  266. initViews(segment) {
  267. const link = this._serializer.getLinkFromName(segment.name);
  268. return this.getNavLinkComponent(link).then((component) => {
  269. segment.component = component;
  270. const view = new ViewController(component, segment.data);
  271. view.id = segment.id;
  272. if (isArray(segment.defaultHistory)) {
  273. return convertToViews(this, segment.defaultHistory).then(views => {
  274. views.push(view);
  275. return views;
  276. });
  277. }
  278. return [view];
  279. });
  280. }
  281. /**
  282. * @internal
  283. */
  284. _isBackUrl(browserUrl) {
  285. return (browserUrl === this._history[this._history.length - 2]);
  286. }
  287. /**
  288. * @internal
  289. */
  290. _isCurrentUrl(browserUrl) {
  291. return (browserUrl === this._history[this._history.length - 1]);
  292. }
  293. /**
  294. * @internal
  295. */
  296. _historyPush(browserUrl) {
  297. if (!this._isCurrentUrl(browserUrl)) {
  298. this._history.push(browserUrl);
  299. if (this._history.length > 30) {
  300. this._history.shift();
  301. }
  302. }
  303. }
  304. /**
  305. * @internal
  306. */
  307. _historyPop() {
  308. this._history.pop();
  309. if (!this._history.length) {
  310. this._historyPush(this._location.path());
  311. }
  312. }
  313. /**
  314. * @internal
  315. */
  316. _getTabSelector(tab) {
  317. if (isPresent(tab.tabUrlPath)) {
  318. return tab.tabUrlPath;
  319. }
  320. if (isPresent(tab.tabTitle)) {
  321. return formatUrlPart(tab.tabTitle);
  322. }
  323. return `tab-${tab.index}`;
  324. }
  325. /**
  326. * Using the known Path of Segments, walk down all descendents
  327. * from the root NavController and load each NavController according
  328. * to each Segment. This is usually called after a browser URL and
  329. * Path changes and needs to update all NavControllers to match
  330. * the new browser URL. Because the URL is already known, it will
  331. * not update the browser's URL when transitions have completed.
  332. *
  333. * @internal
  334. */
  335. _loadViewForSegment(navContainer, segment, done) {
  336. if (!segment) {
  337. return done(false, false);
  338. }
  339. if (isTabs(navContainer) || (isTab(navContainer) && navContainer.parent)) {
  340. const tabs = (isTabs(navContainer) ? navContainer : navContainer.parent);
  341. const selectedIndex = tabs._getSelectedTabIndex(segment.secondaryId);
  342. const tab = tabs.getByIndex(selectedIndex);
  343. tab._segment = segment;
  344. tabs.select(tab, {
  345. updateUrl: false,
  346. animate: false
  347. }, true);
  348. return done(false, false);
  349. }
  350. const navController = navContainer;
  351. const numViews = navController.length() - 1;
  352. // walk backwards to see if the exact view we want to show here
  353. // is already in the stack that we can just pop back to
  354. for (let i = numViews; i >= 0; i--) {
  355. const viewController = navController.getByIndex(i);
  356. if (viewController && (viewController.id === segment.id || viewController.id === segment.name)) {
  357. // hooray! we've already got a view loaded in the stack
  358. // matching the view they wanted to show
  359. if (i === numViews) {
  360. // this is the last view in the stack and it's the same
  361. // as the segment so there's no change needed
  362. return done(false, false);
  363. }
  364. else {
  365. // it's not the exact view as the end
  366. // let's have this nav go back to this exact view
  367. return navController.popTo(viewController, {
  368. animate: false,
  369. updateUrl: false,
  370. }, done);
  371. }
  372. }
  373. }
  374. // ok, so we don't know about a view that they're navigating to
  375. // so we might as well just call setRoot and make tthe view the first view
  376. // this seems like the least bad option
  377. return navController.setRoot(segment.component || segment.name, segment.data, {
  378. id: segment.id, animate: false, updateUrl: false
  379. }, done);
  380. }
  381. }
  382. export function setupDeepLinker(app, serializer, location, moduleLoader, cfr) {
  383. const deepLinker = new DeepLinker(app, serializer, location, moduleLoader, cfr);
  384. deepLinker.init();
  385. return deepLinker;
  386. }
  387. export function normalizeUrl(browserUrl) {
  388. browserUrl = browserUrl.trim();
  389. if (browserUrl.charAt(0) !== '/') {
  390. // ensure first char is a /
  391. browserUrl = '/' + browserUrl;
  392. }
  393. if (browserUrl.length > 1 && browserUrl.charAt(browserUrl.length - 1) === '/') {
  394. // ensure last char is not a /
  395. browserUrl = browserUrl.substr(0, browserUrl.length - 1);
  396. }
  397. return browserUrl;
  398. }
  399. export function getNavFromTree(nav, id) {
  400. while (nav) {
  401. if (nav.id === id || nav.name === id) {
  402. return nav;
  403. }
  404. nav = nav.parent;
  405. }
  406. return null;
  407. }
  408. //# sourceMappingURL=deep-linker.js.map