Flickr - Widescreen

Adjusts the appearance of photostream pages depending on the browser's window size.

  1. // Flickr - Widescreen
  2. // Copyright (c) 2010, Patrick Joseph.
  3. // Released under the GPL license
  4. // http://www.gnu.org/copyleft/gpl.html
  5. //
  6. // ==UserScript==
  7. // @name Flickr - Widescreen
  8. // @namespace http://userscripts.org/users/isamux
  9. // @description Adjusts the appearance of photostream pages depending on the browser's window size.
  10. // @version 1.2.2.1
  11. // @date 2011-04-24
  12. // @creator Patrick Joseph
  13. // @include http://*.flickr.com/photos/*
  14. // @include http://*.flickr.com/groups/*/pool*/
  15. // @exclude http://*.flickr.com/photos/friends*
  16. // ==/UserScript==
  17.  
  18. // ==Changelog==
  19. //2011-04-24 v1.2.2:
  20. // * Flickr changed id of photostream's content div from "Main" to "main". Changed it in the script to make it work again.
  21. // * Updated integrated Flickr - TopPager to version 1.0.5.
  22. // 2010-11-21 v1.2.1:
  23. // * experimatal adjustment on photo page: comments are displayed in a column left to the photo.
  24. // Can be disabled by setting CONF_DISPLAY_COMMENTS_AS_LEFT_COLUMN to false.
  25. // 2010-10-11 v1.2:
  26. // * fixed medium + sets view
  27. // * added logging functionality
  28. // * added widescreen display for sets overview page (Example: http://www.flickr.com/photos/barackobamadotcom/sets/)
  29. // * added @include for group pool
  30. // 2010-04-23 v1.1:
  31. // * added top pager functionality (http://userscripts.org/scripts/show/73290).
  32. // 2010-04-22 v1.0.2:
  33. // * added handling for photostream layout "Medium + sets" (big5).
  34. // * removed @require dependency from userscript metablock.
  35. // 2010-04-15 v1.0.1:
  36. // * some minor adjustments.
  37. // ==/Changelog==
  38.  
  39. // ---------------
  40. // CONFIGURATION
  41. // ---------------
  42. const CONST_ENABLE_FIREBUG_LOGGING = false;
  43.  
  44. // some "constants" for the script
  45. const CONST_MINIMUM_COLUMN_COUNT = 3; // minimum count of columns in photostream table. Should be at least 1.
  46. const CONST_MAIN_CONTAINER = "Main"; // id of main photostream div
  47. const CONST_CELL_WIDTH = 340; // width of a single photostream table cell (Used for AUTO ADJUSTMENT)
  48. const CONST_SETS_COLUMNS_WIDTH = 300; // width of the photostream's sets column
  49. const CONST_PHOTOSTREAM_WRAPPER = '//div[@class="PhotosColumn"]'
  50.  
  51. // if CONF_AUTO_ADJUST is set to false, the values configured below,
  52. // are used to rearrange the photostream table.
  53. var CONF_AUTO_ADJUST = true;
  54. var CONF_COLUMN_COUNT = 3; // [AUTO ADJUSTED] count of columns. Cannot be set below CONST_MINIMUM_COLUMN_COUNT;
  55. var CONF_MAIN_CONTAINER_WIDTH = 1800; // [AUTO ADJUSTED] width of main container. Original width 800.
  56.  
  57. // config for photostream layout "Medium + sets"
  58. var CONF_MEDIUM_SIZE_STREAM_RESIZE_ENABLED = true; // allows to disable resize of images in "Medium + sets" layout.
  59. var CONF_MEDIUM_SIZE_STREAM_MINIMUM_IMAGE_WIDTH = 340; // minimum size for resized images in "Medium + sets" layout.
  60.  
  61. var CONF_ADD_TOP_PAGER = true; // allows to disable top pager.
  62. var CONF_DISPLAY_COMMENTS_AS_LEFT_COLUMN = true; // experimental: displays comments as left column on photo page
  63.  
  64. // Flickr - TopPager CONFIGURATION (should not be changed)
  65. const CONST_PAGINATED_CONTENT_XPATH_PHOTOSTREAM = '//div[starts-with(@class,"PhotoStream")]';
  66. const CONST_PAGINATED_CONTENT_XPATH_GROUP = '//div[@class="HoldPhotos"]';
  67. const CONST_PAGINATED_CONTENT_ID_FAVORITES = 'favoriteThumbs';
  68. // ---------------
  69.  
  70. // ---------------
  71. // SCRIPT-MAIN
  72. // ---------------
  73.  
  74. // 0. Prepare everything
  75. // init logging
  76. if(typeof console === "undefined" || !CONST_ENABLE_FIREBUG_LOGGING) {
  77. console = { log: function() { },
  78. info: function() {},
  79. debug: function() { } ,
  80. warn: function() {},
  81. error: function() {} };
  82. }
  83.  
  84. // add top pager
  85. if(CONF_ADD_TOP_PAGER)
  86. tryAddTopPager();
  87.  
  88. computeAdjustments(); // compute column resize parameters
  89.  
  90. // I. Adjust main div width
  91. var mainEl = document.getElementById(CONST_MAIN_CONTAINER);
  92. if(mainEl == null)
  93. {
  94. // on some pages the main-container is named "Main" and on newer ones "main".
  95. mainEl = document.getElementById(CONST_MAIN_CONTAINER.toLowerCase());
  96. }
  97. if(mainEl !== null)
  98. {
  99. mainEl.style.width = CONF_MAIN_CONTAINER_WIDTH + "px";
  100. }
  101.  
  102. // II.a Rearrange photostream ("Small + sets" layout)
  103. var elPhotoColumn = getElement(CONST_PHOTOSTREAM_WRAPPER);
  104. if(elPhotoColumn !== null)
  105. {
  106. elPhotoColumn.style.setProperty("width", (CONF_COLUMN_COUNT * CONST_CELL_WIDTH) + "px","important");
  107. }
  108.  
  109. // II.b Rearrange photostream ("Big wo sets" layout)
  110. elPhotoColumn = getElement('//div[@class="PhotosColumn Big5Column Big5NoSets"]');
  111. if(elPhotoColumn !== null)
  112. {
  113. rearrangeLargeSizeLayoutPhotoStream(elPhotoColumn);
  114. }
  115.  
  116. // // II.c Rearrange photostream in "Medium + sets" layout
  117. // oMediumSizeImageDivs = getElementSnapshots('//div[starts-with(@class,"StreamView Big5Photo")]');
  118. // if(null != oMediumSizeImageDivs)
  119. // {
  120. // console.info("PageInfo: Photostream Medium + sets column");
  121. // rearrangeMediumSizeLayoutPhotoStream(oMediumSizeImageDivs);
  122. // }
  123.  
  124. // III. Remove width from favorites container, to adjust to new width of CONST_MAIN_CONTAINER.
  125. var oFavoritesContainer = document.getElementById('favoriteThumbs');
  126. if(null != oFavoritesContainer)
  127. {
  128. console.info("PageInfo: Favorites");
  129. oFavoritesContainer.style.width = "auto";
  130. }
  131.  
  132. // IV. Adjust width on set page
  133. var oSetContainer = document.getElementById('ViewSet');
  134. if(null != oSetContainer)
  135. {
  136. console.info("PageInfo: Single set overview");
  137. oSetContainer.style.width = "auto";
  138. var el = getElement('//div[@class="vsThumbs"]');
  139. el.style.width = (CONF_MAIN_CONTAINER_WIDTH-CONST_SETS_COLUMNS_WIDTH) + "px";
  140. }
  141.  
  142. // V. Handle Sets-Overview Page
  143. if(isSetsPage(document.URL))
  144. {
  145. console.info("PageInfo: Set overview");
  146. oBreaks = getElementSnapshots('//div[@class="Sets"]/following-sibling::br[@clear="all"]');
  147. if(oBreaks != null)
  148. {
  149. for(i = 0; i < oBreaks.snapshotLength-3; i++)
  150. {
  151. obreakElement = oBreaks.snapshotItem(i);
  152. obreakElement.parentNode.removeChild(obreakElement);
  153. }
  154. }
  155. }
  156.  
  157. // VI. Insert Widescreen next to Flickr logo
  158. insertLogo();
  159.  
  160. // VII. Experimental new photopage adjustment
  161. mainElNewPhotPage = document.getElementById("main");
  162. if(null !== mainElNewPhotPage)
  163. {
  164. //console.info("PageInfo: New Photopage");
  165. experimentalNewPhotoPageAdjust(mainElNewPhotPage);
  166. }
  167.  
  168. // ===============
  169. // FUNCTION LIB
  170. // ===============
  171.  
  172. function computeAdjustments()
  173. {
  174. // 0. auto config
  175. if(CONF_AUTO_ADJUST)
  176. {
  177. // compute count of fitting columns
  178. CONF_COLUMN_COUNT = Math.floor(window.innerWidth/CONST_CELL_WIDTH);
  179. console.debug("Computed count of columns fitting the window: " + CONF_COLUMN_COUNT);
  180. // by adjusting the width, the main content container on the photostream is moved to the left.
  181. // Otherwise the table with more columns, would be growing out of the main window to the right.
  182. CONF_MAIN_CONTAINER_WIDTH = (CONF_COLUMN_COUNT * CONST_CELL_WIDTH);
  183. // consider sets column. If sets column exists, the CONF_COLUMN_COUNT needs to be reduced by one.
  184. if(additionalRightColumnExists())
  185. {
  186. CONF_COLUMN_COUNT = CONF_COLUMN_COUNT -1;
  187. console.debug("Additional right column exists. Count of fitting columns was reduced to " + CONF_COLUMN_COUNT);
  188. }
  189. }
  190.  
  191. // apply minimum column count
  192. if(CONF_COLUMN_COUNT < CONST_MINIMUM_COLUMN_COUNT)
  193. CONF_COLUMN_COUNT = CONST_MINIMUM_COLUMN_COUNT
  194. }
  195.  
  196. function additionalRightColumnExists()
  197. {
  198. return (collectionsColumnExists() || setsColumnExists());
  199. }
  200.  
  201. function setsColumnExists()
  202. {
  203. return elementExists('//*[@class="SetsColumn"]');
  204. }
  205.  
  206. function collectionsColumnExists()
  207. {
  208. return elementExists('//*[@class="CollectionsColumn"]');
  209. }
  210.  
  211. function isSetsPage(psUrl)
  212. {
  213. bResult = false;
  214. iUrlLength = psUrl.length;
  215. // main sets page wo pagination
  216. bNoPagination = psUrl.indexOf("/sets/") == iUrlLength-6;
  217. bResult = bNoPagination;
  218. console.debug("Is main set overview page: " + bNoPagination);
  219. if(!bNoPagination)
  220. {
  221. // is main sets page on a certain page
  222. iLastEqual = psUrl.lastIndexOf("=");
  223. sTestForSetsPaginated = "/sets/?&page";
  224. sToTest = psUrl.substr(iLastEqual - sTestForSetsPaginated.length, sTestForSetsPaginated.length);
  225. bWithPagination = (sToTest == sTestForSetsPaginated);
  226. console.debug("Is main set overview page on a specific page: " + bWithPagination);
  227. bResult = bWithPagination;
  228. }
  229.  
  230. return bResult;
  231. }
  232.  
  233. function getOrignialPhotostreamTableElement()
  234. {
  235. // get orignal photostream table position
  236. return getElement('//td[@class="PhotosColumn"]//table');
  237. }
  238.  
  239. function rearrangeTable(poTableElement, piNewColumnCount)
  240. {
  241. var allHeaderCells = getAllPhotostreamHeaderCells(poTableElement);
  242. var allImgCells = getAllPhotostreamImageCells(poTableElement);
  243. // create new table element and replace orginal
  244. elTable = document.createElement("table");
  245. elTable.innerHTML = buildTable(allHeaderCells, allImgCells, piNewColumnCount);
  246. return elTable;
  247. }
  248.  
  249. function getAllPhotostreamHeaderCells(poTable)
  250. {
  251. return getElementSnapshotsFromContext(poTable, './/tr[@valign="bottom"]//td');
  252. }
  253.  
  254. function getAllPhotostreamImageCells(poTable)
  255. {
  256. return getElementSnapshotsFromContext(poTable,'.//tr[@valign="top"]//td');
  257. }
  258. function buildTable(paAllHeaderCells, paAllImgCells, piColumnCount)
  259. {
  260. var tmpTableString = '<table width="100%" cellspacing="0"><tbody>';
  261.  
  262. for (var i = 0; i < paAllHeaderCells.snapshotLength; i++)
  263. {
  264. tmpTableString += buildTableRow(paAllHeaderCells, i, piColumnCount);
  265. tmpTableString += buildTableRow(paAllImgCells, i, piColumnCount);
  266. i+=piColumnCount;
  267. }
  268.  
  269. tmpTableString += "</tbody></table>";
  270.  
  271. return tmpTableString;
  272. }
  273.  
  274. function buildTableRow(paCellContent,piOffset, piColumnCount)
  275. {
  276. var tmp = "<tr>";
  277. while(piColumnCount--)
  278. {
  279. oTd = paCellContent.snapshotItem(piOffset++);
  280. tmp += buildTableCell(oTd);
  281. }
  282. return tmp + "</tr>";
  283. }
  284.  
  285. function buildTableCell(poTd)
  286. {
  287. if(poTd == null)
  288. return "<td>-</td>";
  289. else
  290. return "<td>" + poTd.innerHTML + "</td>";
  291. }
  292.  
  293.  
  294. function rearrangeLargeSizeLayoutPhotoStream(poPhotoColumn)
  295. {
  296. poPhotoColumn.style.setProperty("width", (CONF_COLUMN_COUNT * CONST_CELL_WIDTH) + "px","important");
  297. poPhotoColumn.style.setProperty("padding-left", "10px","important");
  298. poPhotoColumn.style.setProperty("padding-right", "10px","important");
  299.  
  300. var oPhotoItemSnapsthots = getElementSnapshots('//div[@class="photo-display-item"]');
  301. for(i = 0; i < oPhotoItemSnapsthots.snapshotLength; i++)
  302. {
  303. oPhotoItem = oPhotoItemSnapsthots.snapshotItem(i);
  304. oPhotoItem.style.setProperty("float", "left","important");
  305. oPhotoItem.style.setProperty("margin-left", "15px","important");
  306. }
  307.  
  308. }
  309.  
  310. function rearrangeMediumSizeLayoutPhotoStream(paImageDivs)
  311. {
  312. iMargin = 15;
  313. bRightColumnExists = additionalRightColumnExists();
  314. if(!bRightColumnExists)
  315. {
  316. oContainerElement = getElement('//td[contains(@class,"Big5NoSets")]');
  317. oContainerElement.style.setProperty("padding-left", 0, "important");
  318. }
  319. iWidth = computeAvailableColumnsWidthForMediumSizeLayout(iMargin, bRightColumnExists);
  320. sPhotItemWidth = iWidth + iMargin + "px";
  321. sSubItemWidth = iWidth+ "px";
  322. for (var i = 0; i < paImageDivs.snapshotLength; i++)
  323. {
  324. paImageDivs.snapshotItem(i).style.cssFloat="left";
  325. // resize images
  326. if(CONF_MEDIUM_SIZE_STREAM_RESIZE_ENABLED)
  327. {
  328. oImage = getElementFromContext(paImageDivs.snapshotItem(i),'.//img[@class="pc_img"]');
  329. // resize image container
  330. paImageDivs.snapshotItem(i).style.setProperty("width", sPhotItemWidth , "important");
  331. // resize image
  332. oImage.style.height="auto";
  333. oImage.style.width=sSubItemWidth;
  334. // resize description
  335. oDescription = getElementFromContext(paImageDivs.snapshotItem(i),'.//p[@class="Desc"]');
  336. if(oDescription != null)
  337. oDescription.style.setProperty("width", sSubItemWidth, "important");
  338.  
  339. // resize caption
  340. oCaption = getElementFromContext(paImageDivs.snapshotItem(i),'.//h4')
  341. if(oCaption != null)
  342. oCaption.style.setProperty("width", sSubItemWidth, "important");
  343. }
  344. }
  345. }
  346.  
  347. function computeAvailableColumnsWidthForMediumSizeLayout(piMargin, pbSetsColumnExists)
  348. {
  349. iColumns = 5;
  350. iTotalMargin = piMargin*iColumns;
  351. iAvailableColumnWidth = CONF_MAIN_CONTAINER_WIDTH-iTotalMargin;
  352. if(pbSetsColumnExists)
  353. {
  354. iAvailableColumnWidth = iAvailableColumnWidth - CONST_SETS_COLUMNS_WIDTH-iTotalMargin;
  355. }
  356.  
  357. iWidth = iAvailableColumnWidth/iColumns;
  358. if(CONF_MEDIUM_SIZE_STREAM_MINIMUM_IMAGE_WIDTH > iWidth)
  359. {
  360. iWidth = CONF_MEDIUM_SIZE_STREAM_MINIMUM_IMAGE_WIDTH;
  361. }
  362.  
  363. return iWidth;
  364. }
  365.  
  366. function insertLogo()
  367. {
  368. widescreenLogo = createLogo();
  369. // get flickr logo
  370. logo = document.getElementById('FlickrLogo');
  371. if(logo == null)
  372. {
  373. logo = document.getElementById('head-logo');
  374. }
  375. // insert widescreen logo
  376. logo.parentNode.insertBefore(widescreenLogo,logo);
  377. }
  378.  
  379. function createLogo()
  380. {
  381. el = document.createElement("div");
  382. el.innerHTML ='<a style="font-size:10px;" href="http://userscripts.org/scripts/show/73305" target="_blank" title="Widescreen-ed by Flickr-Widescreen">Widescreen</a>';
  383. el.style.setProperty("font-size","10px","");
  384. el.style.setProperty("float","left","");
  385. el.style.setProperty("position","relative","");
  386. el.style.setProperty("top","0px","");
  387. el.style.setProperty("left","188px","");
  388. el.style.setProperty("padding","1px","");
  389. el.style.setProperty("border","1px solid #7B0099","");
  390. el.style.setProperty("background-color","#FFBFE1","");
  391. el.style.setProperty("-moz-border-radius","3px","");
  392. el.style.setProperty("text-align","center","");
  393. el.style.setProperty("width","65px","");
  394. return el;
  395. }
  396.  
  397.  
  398. function experimentalNewPhotoPageAdjust(pMainElNewPhotPage)
  399. {
  400. if(pMainElNewPhotPage == null)
  401. return;
  402. primCol = document.getElementById("primary-column");
  403. if(primCol === null)
  404. return;
  405. if(CONF_DISPLAY_COMMENTS_AS_LEFT_COLUMN && window.innerWidth > 1800)
  406. {
  407. // display comments in left column
  408. console.info("Widescreening: Inserting comments as left column");
  409.  
  410. primCol.style.setProperty("clear","right","");
  411.  
  412. comments = document.getElementById("comments");
  413. comments.style.setProperty("float","left","");
  414. comments.style.setProperty("margin-left","25px","");
  415. comments.style.setProperty("width","370px","");
  416. document.body.insertBefore(comments,pMainElNewPhotPage)
  417. document.getElementById("message").style.width="300px";
  418. }
  419. }
  420.  
  421. // --------------------
  422. // TOP PAGER (v1.0.5)
  423. // --------------------
  424. function tryAddTopPager()
  425. {
  426. // get paginator
  427. oPaginator = getPaginator();
  428. if(oPaginator !== null)
  429. {
  430. // get element before which the top pager will be inserted.
  431. oPaginatedContent = getPaginatedContent();
  432. // insert top page paginator
  433. if(oPaginatedContent != null)
  434. {
  435. oPaginatorClone = oPaginator.cloneNode(true);
  436. oPaginatedContent.parentNode.insertBefore(oPaginatorClone, oPaginatedContent);
  437. }
  438. }
  439. }
  440.  
  441. function getPaginatedContent()
  442. {
  443. // try get that element on photostream pages, ...
  444. oTmpFound = getElement(CONST_PAGINATED_CONTENT_XPATH_PHOTOSTREAM);
  445.  
  446. if(oTmpFound == null)
  447. {
  448. //...then on groups pages
  449. oTmpFound = getElement(CONST_PAGINATED_CONTENT_XPATH_GROUP);
  450. }
  451.  
  452. if(oTmpFound == null)
  453. {
  454. //...and finally on favorites pages.
  455. oTmpFound = document.getElementById(CONST_PAGINATED_CONTENT_ID_FAVORITES);
  456. }
  457.  
  458. return oTmpFound;
  459. }
  460.  
  461. function getPaginator()
  462. {
  463. return getElement('//div[@class="Pages"]');
  464. }
  465.  
  466.  
  467.  
  468. // ===============
  469. // BASE LIB
  470. // ===============
  471.  
  472. // returns result of xpath query
  473. function getElementSnapshots(xpathExp)
  474. {
  475. oResult = document.evaluate(
  476. xpathExp,
  477. document,
  478. null,
  479. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
  480. null);
  481. return (oResult.snapshotLength > 0)? oResult : null;
  482. }
  483.  
  484. // returns result of xpath query below a given node
  485. function getElementSnapshotsFromContext(contextNode, xpathExp)
  486. {
  487. return document.evaluate(
  488. xpathExp,
  489. contextNode,
  490. null,
  491. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
  492. null);
  493. }
  494.  
  495. // returns a single dom element by xpath expression
  496. function getElement(xpathExp)
  497. {
  498. oResult = getElementSnapshots(xpathExp);
  499. return (oResult == null)?null:oResult.snapshotItem(0);
  500. }
  501.  
  502. function getElementFromContext(contextNode, xpathExp)
  503. {
  504. oResult = getElementSnapshotsFromContext(contextNode, xpathExp);
  505. return (oResult == null)?null:oResult.snapshotItem(0);
  506. }
  507.  
  508. function elementExists(xpathExp)
  509. {
  510. return (getElement(xpathExp) != null);
  511. }
  512.  
  513. function replaceElement(oldElement, newElement)
  514. {
  515. oldElement.parentNode.insertBefore(newElement, oldElement);
  516. // remove original table
  517. oldElement.parentNode.removeChild(oldElement);
  518. }