CreditDisplay.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import AssociativeArray from "../Core/AssociativeArray.js";
  2. import buildModuleUrl from "../Core/buildModuleUrl.js";
  3. import Check from "../Core/Check.js";
  4. import Credit from "../Core/Credit.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import Uri from "../ThirdParty/Uri.js";
  9. var mobileWidth = 576;
  10. var lightboxHeight = 100;
  11. var textColor = "#ffffff";
  12. var highlightColor = "#48b";
  13. function contains(credits, credit) {
  14. var len = credits.length;
  15. for (var i = 0; i < len; i++) {
  16. var existingCredit = credits[i];
  17. if (Credit.equals(existingCredit, credit)) {
  18. return true;
  19. }
  20. }
  21. return false;
  22. }
  23. function swapCesiumCredit(creditDisplay) {
  24. // We don't want to clutter the screen with the Cesium logo and the Cesium ion
  25. // logo at the same time. Since the ion logo is required, we just replace the
  26. // Cesium logo or add the logo if the Cesium one was removed.
  27. var previousCredit = creditDisplay._previousCesiumCredit;
  28. var currentCredit = creditDisplay._currentCesiumCredit;
  29. if (Credit.equals(currentCredit, previousCredit)) {
  30. return;
  31. }
  32. if (defined(previousCredit)) {
  33. creditDisplay._cesiumCreditContainer.removeChild(previousCredit.element);
  34. }
  35. if (defined(currentCredit)) {
  36. creditDisplay._cesiumCreditContainer.appendChild(currentCredit.element);
  37. }
  38. creditDisplay._previousCesiumCredit = currentCredit;
  39. }
  40. var delimiterClassName = "cesium-credit-delimiter";
  41. function createDelimiterElement(delimiter) {
  42. var delimiterElement = document.createElement("span");
  43. delimiterElement.textContent = delimiter;
  44. delimiterElement.className = delimiterClassName;
  45. return delimiterElement;
  46. }
  47. function createCreditElement(element, elementWrapperTagName) {
  48. // may need to wrap the credit in another element
  49. if (defined(elementWrapperTagName)) {
  50. var wrapper = document.createElement(elementWrapperTagName);
  51. wrapper._creditId = element._creditId;
  52. wrapper.appendChild(element);
  53. element = wrapper;
  54. }
  55. return element;
  56. }
  57. function displayCredits(container, credits, delimiter, elementWrapperTagName) {
  58. var childNodes = container.childNodes;
  59. var domIndex = -1;
  60. for (var creditIndex = 0; creditIndex < credits.length; ++creditIndex) {
  61. var credit = credits[creditIndex];
  62. if (defined(credit)) {
  63. domIndex = creditIndex;
  64. if (defined(delimiter)) {
  65. // credits may be separated by delimiters
  66. domIndex *= 2;
  67. if (creditIndex > 0) {
  68. var delimiterDomIndex = domIndex - 1;
  69. if (childNodes.length <= delimiterDomIndex) {
  70. container.appendChild(createDelimiterElement(delimiter));
  71. } else {
  72. var existingDelimiter = childNodes[delimiterDomIndex];
  73. if (existingDelimiter.className !== delimiterClassName) {
  74. container.replaceChild(
  75. createDelimiterElement(delimiter),
  76. existingDelimiter
  77. );
  78. }
  79. }
  80. }
  81. }
  82. var element = credit.element;
  83. // check to see if the correct credit is in the right place
  84. if (childNodes.length <= domIndex) {
  85. container.appendChild(
  86. createCreditElement(element, elementWrapperTagName)
  87. );
  88. } else {
  89. var existingElement = childNodes[domIndex];
  90. if (existingElement._creditId !== credit._id) {
  91. // not the right credit, swap it in
  92. container.replaceChild(
  93. createCreditElement(element, elementWrapperTagName),
  94. existingElement
  95. );
  96. }
  97. }
  98. }
  99. }
  100. // any remaining nodes in the container are unnecessary
  101. ++domIndex;
  102. while (domIndex < childNodes.length) {
  103. container.removeChild(childNodes[domIndex]);
  104. }
  105. }
  106. function styleLightboxContainer(that) {
  107. var lightboxCredits = that._lightboxCredits;
  108. var width = that.viewport.clientWidth;
  109. var height = that.viewport.clientHeight;
  110. if (width !== that._lastViewportWidth) {
  111. if (width < mobileWidth) {
  112. lightboxCredits.className =
  113. "cesium-credit-lightbox cesium-credit-lightbox-mobile";
  114. lightboxCredits.style.marginTop = "0";
  115. } else {
  116. lightboxCredits.className =
  117. "cesium-credit-lightbox cesium-credit-lightbox-expanded";
  118. lightboxCredits.style.marginTop =
  119. Math.floor((height - lightboxCredits.clientHeight) * 0.5) + "px";
  120. }
  121. that._lastViewportWidth = width;
  122. }
  123. if (width >= mobileWidth && height !== that._lastViewportHeight) {
  124. lightboxCredits.style.marginTop =
  125. Math.floor((height - lightboxCredits.clientHeight) * 0.5) + "px";
  126. that._lastViewportHeight = height;
  127. }
  128. }
  129. function addStyle(selector, styles) {
  130. var style = selector + " {";
  131. for (var attribute in styles) {
  132. if (styles.hasOwnProperty(attribute)) {
  133. style += attribute + ": " + styles[attribute] + "; ";
  134. }
  135. }
  136. style += " }\n";
  137. return style;
  138. }
  139. function appendCss() {
  140. var style = "";
  141. style += addStyle(".cesium-credit-lightbox-overlay", {
  142. display: "none",
  143. "z-index": "1", //must be at least 1 to draw over top other Cesium widgets
  144. position: "absolute",
  145. top: "0",
  146. left: "0",
  147. width: "100%",
  148. height: "100%",
  149. "background-color": "rgba(80, 80, 80, 0.8)",
  150. });
  151. style += addStyle(".cesium-credit-lightbox", {
  152. "background-color": "#303336",
  153. color: textColor,
  154. position: "relative",
  155. "min-height": lightboxHeight + "px",
  156. margin: "auto",
  157. });
  158. style += addStyle(
  159. ".cesium-credit-lightbox > ul > li a, .cesium-credit-lightbox > ul > li a:visited",
  160. {
  161. color: textColor,
  162. }
  163. );
  164. style += addStyle(".cesium-credit-lightbox > ul > li a:hover", {
  165. color: highlightColor,
  166. });
  167. style += addStyle(".cesium-credit-lightbox.cesium-credit-lightbox-expanded", {
  168. border: "1px solid #444",
  169. "border-radius": "5px",
  170. "max-width": "370px",
  171. });
  172. style += addStyle(".cesium-credit-lightbox.cesium-credit-lightbox-mobile", {
  173. height: "100%",
  174. width: "100%",
  175. });
  176. style += addStyle(".cesium-credit-lightbox-title", {
  177. padding: "20px 20px 0 20px",
  178. });
  179. style += addStyle(".cesium-credit-lightbox-close", {
  180. "font-size": "18pt",
  181. cursor: "pointer",
  182. position: "absolute",
  183. top: "0",
  184. right: "6px",
  185. color: textColor,
  186. });
  187. style += addStyle(".cesium-credit-lightbox-close:hover", {
  188. color: highlightColor,
  189. });
  190. style += addStyle(".cesium-credit-lightbox > ul", {
  191. margin: "0",
  192. padding: "12px 20px 12px 40px",
  193. "font-size": "13px",
  194. });
  195. style += addStyle(".cesium-credit-lightbox > ul > li", {
  196. "padding-bottom": "6px",
  197. });
  198. style += addStyle(".cesium-credit-lightbox > ul > li *", {
  199. padding: "0",
  200. margin: "0",
  201. });
  202. style += addStyle(".cesium-credit-expand-link", {
  203. "padding-left": "5px",
  204. cursor: "pointer",
  205. "text-decoration": "underline",
  206. color: textColor,
  207. });
  208. style += addStyle(".cesium-credit-expand-link:hover", {
  209. color: highlightColor,
  210. });
  211. style += addStyle(".cesium-credit-text", {
  212. color: textColor,
  213. });
  214. style += addStyle(
  215. ".cesium-credit-textContainer *, .cesium-credit-logoContainer *",
  216. {
  217. display: "inline",
  218. }
  219. );
  220. var head = document.head;
  221. var css = document.createElement("style");
  222. css.innerHTML = style;
  223. head.insertBefore(css, head.firstChild);
  224. }
  225. /**
  226. * The credit display is responsible for displaying credits on screen.
  227. *
  228. * @param {HTMLElement} container The HTML element where credits will be displayed
  229. * @param {String} [delimiter= ' • '] The string to separate text credits
  230. * @param {HTMLElement} [viewport=document.body] The HTML element that will contain the credits popup
  231. *
  232. * @alias CreditDisplay
  233. * @constructor
  234. *
  235. * @example
  236. * var creditDisplay = new Cesium.CreditDisplay(creditContainer);
  237. */
  238. function CreditDisplay(container, delimiter, viewport) {
  239. //>>includeStart('debug', pragmas.debug);
  240. Check.defined("container", container);
  241. //>>includeEnd('debug');
  242. var that = this;
  243. viewport = defaultValue(viewport, document.body);
  244. var lightbox = document.createElement("div");
  245. lightbox.className = "cesium-credit-lightbox-overlay";
  246. viewport.appendChild(lightbox);
  247. var lightboxCredits = document.createElement("div");
  248. lightboxCredits.className = "cesium-credit-lightbox";
  249. lightbox.appendChild(lightboxCredits);
  250. function hideLightbox(event) {
  251. if (lightboxCredits.contains(event.target)) {
  252. return;
  253. }
  254. that.hideLightbox();
  255. }
  256. lightbox.addEventListener("click", hideLightbox, false);
  257. var title = document.createElement("div");
  258. title.className = "cesium-credit-lightbox-title";
  259. title.textContent = "Data provided by:";
  260. lightboxCredits.appendChild(title);
  261. var closeButton = document.createElement("a");
  262. closeButton.onclick = this.hideLightbox.bind(this);
  263. closeButton.innerHTML = "&times;";
  264. closeButton.className = "cesium-credit-lightbox-close";
  265. lightboxCredits.appendChild(closeButton);
  266. var creditList = document.createElement("ul");
  267. lightboxCredits.appendChild(creditList);
  268. var cesiumCreditContainer = document.createElement("div");
  269. cesiumCreditContainer.className = "cesium-credit-logoContainer";
  270. cesiumCreditContainer.style.display = "inline";
  271. container.appendChild(cesiumCreditContainer);
  272. var screenContainer = document.createElement("div");
  273. screenContainer.className = "cesium-credit-textContainer";
  274. screenContainer.style.display = "inline";
  275. container.appendChild(screenContainer);
  276. var expandLink = document.createElement("a");
  277. expandLink.className = "cesium-credit-expand-link";
  278. expandLink.onclick = this.showLightbox.bind(this);
  279. expandLink.textContent = "Data attribution";
  280. container.appendChild(expandLink);
  281. appendCss();
  282. var cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit);
  283. this._delimiter = defaultValue(delimiter, " • ");
  284. this._screenContainer = screenContainer;
  285. this._cesiumCreditContainer = cesiumCreditContainer;
  286. this._lastViewportHeight = undefined;
  287. this._lastViewportWidth = undefined;
  288. this._lightboxCredits = lightboxCredits;
  289. this._creditList = creditList;
  290. this._lightbox = lightbox;
  291. this._hideLightbox = hideLightbox;
  292. this._expandLink = expandLink;
  293. this._expanded = false;
  294. this._defaultCredits = [];
  295. this._cesiumCredit = cesiumCredit;
  296. this._previousCesiumCredit = undefined;
  297. this._currentCesiumCredit = cesiumCredit;
  298. this._currentFrameCredits = {
  299. screenCredits: new AssociativeArray(),
  300. lightboxCredits: new AssociativeArray(),
  301. };
  302. this._defaultCredit = undefined;
  303. this.viewport = viewport;
  304. /**
  305. * The HTML element where credits will be displayed.
  306. * @type {HTMLElement}
  307. */
  308. this.container = container;
  309. }
  310. /**
  311. * Adds a credit to the list of current credits to be displayed in the credit container
  312. *
  313. * @param {Credit} credit The credit to display
  314. */
  315. CreditDisplay.prototype.addCredit = function (credit) {
  316. //>>includeStart('debug', pragmas.debug);
  317. Check.defined("credit", credit);
  318. //>>includeEnd('debug');
  319. if (credit._isIon) {
  320. // If this is the an ion logo credit from the ion server
  321. // Juse use the default credit (which is identical) to avoid blinking
  322. if (!defined(this._defaultCredit)) {
  323. this._defaultCredit = Credit.clone(getDefaultCredit());
  324. }
  325. this._currentCesiumCredit = this._defaultCredit;
  326. return;
  327. }
  328. if (!credit.showOnScreen) {
  329. this._currentFrameCredits.lightboxCredits.set(credit.id, credit);
  330. } else {
  331. this._currentFrameCredits.screenCredits.set(credit.id, credit);
  332. }
  333. };
  334. /**
  335. * Adds credits that will persist until they are removed
  336. *
  337. * @param {Credit} credit The credit to added to defaults
  338. */
  339. CreditDisplay.prototype.addDefaultCredit = function (credit) {
  340. //>>includeStart('debug', pragmas.debug);
  341. Check.defined("credit", credit);
  342. //>>includeEnd('debug');
  343. var defaultCredits = this._defaultCredits;
  344. if (!contains(defaultCredits, credit)) {
  345. defaultCredits.push(credit);
  346. }
  347. };
  348. /**
  349. * Removes a default credit
  350. *
  351. * @param {Credit} credit The credit to be removed from defaults
  352. */
  353. CreditDisplay.prototype.removeDefaultCredit = function (credit) {
  354. //>>includeStart('debug', pragmas.debug);
  355. Check.defined("credit", credit);
  356. //>>includeEnd('debug');
  357. var defaultCredits = this._defaultCredits;
  358. var index = defaultCredits.indexOf(credit);
  359. if (index !== -1) {
  360. defaultCredits.splice(index, 1);
  361. }
  362. };
  363. CreditDisplay.prototype.showLightbox = function () {
  364. this._lightbox.style.display = "block";
  365. this._expanded = true;
  366. };
  367. CreditDisplay.prototype.hideLightbox = function () {
  368. this._lightbox.style.display = "none";
  369. this._expanded = false;
  370. };
  371. /**
  372. * Updates the credit display before a new frame is rendered.
  373. */
  374. CreditDisplay.prototype.update = function () {
  375. if (this._expanded) {
  376. styleLightboxContainer(this);
  377. }
  378. };
  379. /**
  380. * Resets the credit display to a beginning of frame state, clearing out current credits.
  381. */
  382. CreditDisplay.prototype.beginFrame = function () {
  383. var currentFrameCredits = this._currentFrameCredits;
  384. var screenCredits = currentFrameCredits.screenCredits;
  385. screenCredits.removeAll();
  386. var defaultCredits = this._defaultCredits;
  387. for (var i = 0; i < defaultCredits.length; ++i) {
  388. var defaultCredit = defaultCredits[i];
  389. screenCredits.set(defaultCredit.id, defaultCredit);
  390. }
  391. currentFrameCredits.lightboxCredits.removeAll();
  392. if (!Credit.equals(CreditDisplay.cesiumCredit, this._cesiumCredit)) {
  393. this._cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit);
  394. }
  395. this._currentCesiumCredit = this._cesiumCredit;
  396. };
  397. /**
  398. * Sets the credit display to the end of frame state, displaying credits from the last frame in the credit container.
  399. */
  400. CreditDisplay.prototype.endFrame = function () {
  401. var screenCredits = this._currentFrameCredits.screenCredits.values;
  402. displayCredits(
  403. this._screenContainer,
  404. screenCredits,
  405. this._delimiter,
  406. undefined
  407. );
  408. var lightboxCredits = this._currentFrameCredits.lightboxCredits.values;
  409. this._expandLink.style.display =
  410. lightboxCredits.length > 0 ? "inline" : "none";
  411. displayCredits(this._creditList, lightboxCredits, undefined, "li");
  412. swapCesiumCredit(this);
  413. };
  414. /**
  415. * Destroys the resources held by this object. Destroying an object allows for deterministic
  416. * release of resources, instead of relying on the garbage collector to destroy this object.
  417. * <br /><br />
  418. * Once an object is destroyed, it should not be used; calling any function other than
  419. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  420. * assign the return value (<code>undefined</code>) to the object as done in the example.
  421. *
  422. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  423. */
  424. CreditDisplay.prototype.destroy = function () {
  425. this._lightbox.removeEventListener("click", this._hideLightbox, false);
  426. this.container.removeChild(this._cesiumCreditContainer);
  427. this.container.removeChild(this._screenContainer);
  428. this.container.removeChild(this._expandLink);
  429. this.viewport.removeChild(this._lightbox);
  430. return destroyObject(this);
  431. };
  432. /**
  433. * Returns true if this object was destroyed; otherwise, false.
  434. * <br /><br />
  435. *
  436. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  437. */
  438. CreditDisplay.prototype.isDestroyed = function () {
  439. return false;
  440. };
  441. CreditDisplay._cesiumCredit = undefined;
  442. CreditDisplay._cesiumCreditInitialized = false;
  443. var defaultCredit;
  444. function getDefaultCredit() {
  445. if (!defined(defaultCredit)) {
  446. var logo = buildModuleUrl("Assets/Images/ion-credit.png");
  447. // When hosting in a WebView, the base URL scheme is file:// or ms-appx-web://
  448. // which is stripped out from the Credit's <img> tag; use the full path instead
  449. if (logo.indexOf("http://") !== 0 && logo.indexOf("https://") !== 0) {
  450. var logoUrl = new Uri(logo);
  451. logo = logoUrl.getPath();
  452. }
  453. defaultCredit = new Credit(
  454. '<a href="https://cesium.com/" target="_blank"><img src="' +
  455. logo +
  456. '" title="Cesium ion"/></a>',
  457. true
  458. );
  459. }
  460. if (!CreditDisplay._cesiumCreditInitialized) {
  461. CreditDisplay._cesiumCredit = defaultCredit;
  462. CreditDisplay._cesiumCreditInitialized = true;
  463. }
  464. return defaultCredit;
  465. }
  466. Object.defineProperties(CreditDisplay, {
  467. /**
  468. * Gets or sets the Cesium logo credit.
  469. * @memberof CreditDisplay
  470. * @type {Credit}
  471. */
  472. cesiumCredit: {
  473. get: function () {
  474. getDefaultCredit();
  475. return CreditDisplay._cesiumCredit;
  476. },
  477. set: function (value) {
  478. CreditDisplay._cesiumCredit = value;
  479. CreditDisplay._cesiumCreditInitialized = true;
  480. },
  481. },
  482. });
  483. export default CreditDisplay;