ProgressUI Module

Reusable progress UI module

Від 22.03.2025. Дивіться остання версія.

Цей скрипт не слід встановлювати безпосередньо. Це - бібліотека для інших скриптів для включення в мета директиву // @require https://update.greatest.deepsurf.us/scripts/530526/1558020/ProgressUI%20Module.js

  1. // ==UserScript==
  2. // @name ProgressUI Module
  3. // @namespace Violentmonkey Scripts
  4. // @description Reusable progress UI module
  5. // @version 0.2
  6. // @author maanimis
  7. // @run-at document-idle
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. function _extends() {
  15. return _extends = Object.assign ? Object.assign.bind() : function (n) {
  16. for (var e = 1; e < arguments.length; e++) {
  17. var t = arguments[e];
  18. for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
  19. }
  20. return n;
  21. }, _extends.apply(null, arguments);
  22. }
  23.  
  24. const DEFAULT_THEMES = {
  25. light: {
  26. background: '#f8f8f8',
  27. text: '#333333',
  28. border: '#e0e0e0',
  29. progressBg: '#e0e0e0',
  30. progressFill: '#4CAF50',
  31. shadow: 'rgba(0,0,0,0.2)'
  32. },
  33. dark: {
  34. background: '#2a2a2a',
  35. text: '#ffffff',
  36. border: '#444444',
  37. progressBg: '#444444',
  38. progressFill: '#4CAF50',
  39. shadow: 'rgba(0,0,0,0.5)'
  40. }
  41. };
  42. class ProgressUI {
  43. constructor(config = {}) {
  44. this.config = this.validateConfig(config);
  45. this.elements = this.createUIElements();
  46. this.applyStyles();
  47. this.attachToDOM();
  48. }
  49. update(message, percent) {
  50. if (!this.isMounted) return false;
  51. if (message) {
  52. this.elements.status.textContent = message;
  53. }
  54. if (typeof percent === 'number') {
  55. const clamped = Math.max(0, Math.min(100, percent));
  56. this.elements.progressBar.style.width = `${clamped}%`;
  57. this.elements.percentText.textContent = `${Math.round(clamped)}%`;
  58. }
  59. return true;
  60. }
  61. scheduleCleanup(delay = 3000, fade = true) {
  62. window.clearTimeout(this.removalTimeout);
  63. this.removalTimeout = window.setTimeout(() => this.remove(fade), delay);
  64. }
  65. remove(fade = true) {
  66. if (!this.isMounted) return;
  67. if (fade) {
  68. this.elements.container.style.opacity = '0';
  69. window.setTimeout(() => this.detachFromDOM(), 300);
  70. } else {
  71. this.detachFromDOM();
  72. }
  73. }
  74. get isMounted() {
  75. return document.body.contains(this.elements.container);
  76. }
  77. static showQuick(message, config = {}) {
  78. var _config$closable, _config$duration, _config$percent;
  79. const instance = new ProgressUI(_extends({}, config, {
  80. closable: (_config$closable = config.closable) != null ? _config$closable : false,
  81. duration: (_config$duration = config.duration) != null ? _config$duration : 3000
  82. }));
  83. instance.update(message, (_config$percent = config.percent) != null ? _config$percent : 100);
  84. instance.scheduleCleanup(config.duration, true);
  85. return instance;
  86. }
  87. validateConfig(config) {
  88. var _config$position, _config$width, _config$theme, _config$title, _config$closable2, _config$colors, _config$duration2;
  89. return {
  90. position: (_config$position = config.position) != null ? _config$position : 'top-right',
  91. width: (_config$width = config.width) != null ? _config$width : '300px',
  92. theme: (_config$theme = config.theme) != null ? _config$theme : 'light',
  93. title: (_config$title = config.title) != null ? _config$title : '',
  94. closable: (_config$closable2 = config.closable) != null ? _config$closable2 : true,
  95. colors: (_config$colors = config.colors) != null ? _config$colors : {},
  96. duration: (_config$duration2 = config.duration) != null ? _config$duration2 : 3000
  97. };
  98. }
  99. createUIElements() {
  100. const container = document.createElement('div');
  101. const status = document.createElement('div');
  102. const progressBar = document.createElement('div');
  103. const percentText = document.createElement('div');
  104. return {
  105. container,
  106. status,
  107. progressBar,
  108. percentText
  109. };
  110. }
  111. applyStyles() {
  112. const colors = this.getThemeColors();
  113.  
  114. // Container styles
  115. Object.assign(this.elements.container.style, _extends({
  116. position: 'fixed',
  117. zIndex: '9999'
  118. }, this.getPositionStyles(), {
  119. backgroundColor: colors.background,
  120. color: colors.text,
  121. padding: '15px',
  122. borderRadius: '5px',
  123. boxShadow: `0 0 10px ${colors.shadow}`,
  124. width: this.config.width,
  125. fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
  126. transition: 'opacity 0.3s ease'
  127. }));
  128.  
  129. // Status element
  130. Object.assign(this.elements.status.style, {
  131. marginBottom: '10px',
  132. fontSize: '14px',
  133. fontWeight: '500',
  134. whiteSpace: 'nowrap',
  135. overflow: 'hidden',
  136. textOverflow: 'ellipsis'
  137. });
  138.  
  139. // Progress bar container
  140. Object.assign(this.elements.progressBar.parentElement.style, {
  141. width: '100%',
  142. backgroundColor: colors.progressBg,
  143. borderRadius: '4px',
  144. height: '10px',
  145. overflow: 'hidden'
  146. });
  147.  
  148. // Progress bar
  149. Object.assign(this.elements.progressBar.style, {
  150. height: '100%',
  151. width: '0%',
  152. backgroundColor: colors.progressFill,
  153. transition: 'width 0.3s'
  154. });
  155.  
  156. // Percentage text
  157. Object.assign(this.elements.percentText.style, {
  158. textAlign: 'right',
  159. marginTop: '5px',
  160. fontSize: '12px',
  161. fontWeight: '600'
  162. });
  163. this.addOptionalElements();
  164. }
  165. getPositionStyles() {
  166. const positionStyles = new CSSStyleDeclaration();
  167. switch (this.config.position) {
  168. case 'top-left':
  169. positionStyles.top = '20px';
  170. positionStyles.left = '20px';
  171. break;
  172. case 'top-center':
  173. positionStyles.top = '20px';
  174. positionStyles.left = '50%';
  175. positionStyles.transform = 'translateX(-50%)';
  176. break;
  177. case 'bottom-left':
  178. positionStyles.bottom = '20px';
  179. positionStyles.left = '20px';
  180. break;
  181. case 'bottom-right':
  182. positionStyles.bottom = '20px';
  183. positionStyles.right = '20px';
  184. break;
  185. case 'bottom-center':
  186. positionStyles.bottom = '20px';
  187. positionStyles.left = '50%';
  188. positionStyles.transform = 'translateX(-50%)';
  189. break;
  190. case 'center':
  191. positionStyles.top = '50%';
  192. positionStyles.left = '50%';
  193. positionStyles.transform = 'translate(-50%, -50%)';
  194. break;
  195. default:
  196. // top-right
  197. positionStyles.top = '20px';
  198. positionStyles.right = '20px';
  199. }
  200. return positionStyles;
  201. }
  202. getThemeColors() {
  203. const baseTheme = this.config.theme === 'custom' ? DEFAULT_THEMES.light : DEFAULT_THEMES[this.config.theme];
  204. return _extends({}, baseTheme, this.config.colors);
  205. }
  206. addOptionalElements() {
  207. if (this.config.title) {
  208. const title = document.createElement('div');
  209. title.textContent = this.config.title;
  210. Object.assign(title.style, {
  211. marginBottom: '10px',
  212. fontSize: '16px',
  213. fontWeight: 'bold',
  214. paddingRight: '15px'
  215. });
  216. this.elements.container.prepend(title);
  217. }
  218. if (this.config.closable) {
  219. const closeButton = document.createElement('div');
  220. closeButton.innerHTML = '×';
  221. Object.assign(closeButton.style, {
  222. position: 'absolute',
  223. top: '5px',
  224. right: '8px',
  225. fontSize: '18px',
  226. fontWeight: 'bold',
  227. cursor: 'pointer',
  228. opacity: '0.6'
  229. });
  230. closeButton.addEventListener('click', () => this.remove());
  231. this.elements.container.appendChild(closeButton);
  232. }
  233. }
  234. attachToDOM() {
  235. document.querySelectorAll('.progress-ui-container').forEach(el => el.remove());
  236. document.body.appendChild(this.elements.container);
  237. }
  238. detachFromDOM() {
  239. if (this.isMounted) {
  240. document.body.removeChild(this.elements.container);
  241. }
  242. }
  243. }
  244.  
  245. // const globalContext =
  246. // typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
  247.  
  248. const globalContext = window;
  249. globalContext.ProgressUI = ProgressUI;
  250.  
  251. })();