ui-button.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. * Copyright 2018 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * SingleFile is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * SingleFile is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /* global browser, singlefile */
  21. singlefile.ui.button = (() => {
  22. const DEFAULT_ICON_PATH = "/extension/ui/resources/icon_16.png";
  23. const WAIT_ICON_PATH_PREFIX = "/extension/ui/resources/icon_16_wait";
  24. const DEFAULT_TITLE = "Save page with SingleFile";
  25. const DEFAULT_COLOR = [2, 147, 20, 255];
  26. const BUTTON_PROPERTIES = [{ name: "color", browserActionMethod: "setBadgeBackgroundColor" }, { name: "path", browserActionMethod: "setIcon" }, { name: "text", browserActionMethod: "setBadgeText" }, { name: "title", browserActionMethod: "setTitle" }];
  27. browser.browserAction.onClicked.addListener(async tab => {
  28. if (singlefile.core.isAllowedURL(tab.url)) {
  29. const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
  30. if (!tabs.length) {
  31. singlefile.ui.saveTab(tab);
  32. } else {
  33. tabs.forEach(tab => singlefile.core.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
  34. }
  35. }
  36. });
  37. browser.tabs.onActivated.addListener(async activeInfo => {
  38. const tab = await browser.tabs.get(activeInfo.tabId);
  39. await onTabActivated(tab);
  40. });
  41. browser.tabs.onCreated.addListener(onTabActivated);
  42. browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => onTabActivated(tab));
  43. browser.runtime.onMessage.addListener((request, sender) => {
  44. if (request.processProgress) {
  45. if (request.maxIndex) {
  46. onProgress(sender.tab.id, request.index, request.maxIndex, request.options);
  47. }
  48. }
  49. if (request.processEnd) {
  50. onEnd(sender.tab.id, request.options);
  51. }
  52. if (request.processError) {
  53. if (request.error) {
  54. console.error("Initialization error", request.error); // eslint-disable-line no-console
  55. }
  56. onError(sender.tab.id, request.options);
  57. }
  58. });
  59. if (browser.runtime.onMessageExternal) {
  60. browser.runtime.onMessageExternal.addListener(async message => {
  61. if (message.method == "enableAutoSave") {
  62. setAutoSaveActiveTabEnabled(message.enabled);
  63. }
  64. if (message.method == "isAutoSaveEnabled") {
  65. return isAutoSaveEnabled();
  66. }
  67. });
  68. }
  69. return {
  70. onInitialize,
  71. onProgress,
  72. onEnd,
  73. onError,
  74. refresh: (tabId, options) => refresh(tabId, getProperties(tabId, options))
  75. };
  76. async function setAutoSaveActiveTabEnabled(enabled) {
  77. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  78. const tab = tabs[0];
  79. if (tab) {
  80. const tabId = tab.id;
  81. const tabsData = await singlefile.storage.get();
  82. if (!tabsData[tabId]) {
  83. tabsData[tabId] = {};
  84. }
  85. tabsData[tabId].autoSave = enabled;
  86. await singlefile.storage.set(tabsData);
  87. refresh(tabId, getProperties(tabId, { autoSave: enabled }));
  88. }
  89. }
  90. async function isAutoSaveEnabled() {
  91. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  92. const tab = tabs[0];
  93. if (tab && singlefile.core.isAllowedURL(tab.url)) {
  94. const tabId = tab.id;
  95. const tabsData = await singlefile.storage.get();
  96. return tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tabId] && tabsData[tabId].autoSave);
  97. }
  98. return false;
  99. }
  100. function onInitialize(tabId, options, step) {
  101. refresh(tabId, getProperties(tabId, options, "•••", step == 1 ? DEFAULT_COLOR : [4, 229, 36, 255], "Initializing SingleFile (" + step + "/2)"));
  102. }
  103. function onError(tabId, options) {
  104. refresh(tabId, getProperties(tabId, options, "ERR", [229, 4, 12, 255]));
  105. }
  106. async function onEnd(tabId, options) {
  107. refresh(tabId, getProperties(tabId, options, "OK", [4, 229, 36, 255]));
  108. }
  109. function onProgress(tabId, index, maxIndex, options) {
  110. const progress = Math.max(Math.min(20, Math.floor((index / maxIndex) * 20)), 0);
  111. const barProgress = Math.floor((index / maxIndex) * 8);
  112. refresh(tabId, getProperties(tabId, options, "", [4, 229, 36, 255], "Save progress: " + (progress * 5) + "%", WAIT_ICON_PATH_PREFIX + barProgress + ".png", progress, barProgress, [128, 128, 128, 255]));
  113. }
  114. async function onTabActivated(tab) {
  115. const autoSave = await singlefile.ui.autosave.isEnabled(tab.id);
  116. await refresh(tab.id, getProperties(tab.id, { autoSave }));
  117. if (singlefile.core.isAllowedURL(tab.url) && browser.browserAction && browser.browserAction.enable && browser.browserAction.disable) {
  118. if (singlefile.core.isAllowedURL(tab.url)) {
  119. try {
  120. await browser.browserAction.enable(tab.id);
  121. } catch (error) {
  122. /* ignored */
  123. }
  124. } else {
  125. try {
  126. await browser.browserAction.disable(tab.id);
  127. } catch (error) {
  128. /* ignored */
  129. }
  130. }
  131. }
  132. }
  133. function getProperties(tabId, options, text, color, title = DEFAULT_TITLE, path = DEFAULT_ICON_PATH, progress = -1, barProgress = -1, autoColor = [208, 208, 208, 255]) {
  134. return {
  135. text: options.autoSave ? "[A]" : (text || ""),
  136. color: options.autoSave ? autoColor : color || DEFAULT_COLOR,
  137. title: options.autoSave ? "Autosave active" : title,
  138. path: options.autoSave ? DEFAULT_ICON_PATH : path,
  139. progress: options.autoSave ? - 1 : progress,
  140. barProgress: options.autoSave ? - 1 : barProgress
  141. };
  142. }
  143. async function refresh(tabId, tabData) {
  144. const tabsData = await singlefile.storage.getTemporary();
  145. if (!tabsData[tabId]) {
  146. tabsData[tabId] = {};
  147. }
  148. if (!tabsData[tabId].pendingRefresh) {
  149. tabsData[tabId].pendingRefresh = Promise.resolve();
  150. }
  151. try {
  152. tabsData[tabId].pendingRefresh = tabsData[tabId].pendingRefresh.then(() => refreshAsync(tabId, tabsData, tabData));
  153. await tabsData[tabId].pendingRefresh;
  154. } catch (error) {
  155. /* ignored */
  156. }
  157. }
  158. async function refreshAsync(tabId, tabsData, tabData) {
  159. for (let property of BUTTON_PROPERTIES) {
  160. await refreshProperty(tabId, tabsData, property.name, property.browserActionMethod, tabData);
  161. }
  162. }
  163. async function refreshProperty(tabId, tabsData, property, browserActionMethod, tabData) {
  164. const value = tabData[property];
  165. const browserActionParameter = { tabId };
  166. if (browser.browserAction[browserActionMethod]) {
  167. browserActionParameter[property] = value;
  168. if (!tabsData[tabId]) {
  169. tabsData[tabId] = {};
  170. }
  171. if (!tabsData[tabId].button) {
  172. tabsData[tabId].button = {};
  173. }
  174. if (JSON.stringify(tabsData[tabId].button[browserActionMethod]) != JSON.stringify(value)) {
  175. tabsData[tabId].button[browserActionMethod] = value;
  176. await browser.browserAction[browserActionMethod](browserActionParameter);
  177. }
  178. }
  179. }
  180. })();