ProgressUI-Module

Reusable progress UI module

Verze ze dne 22. 03. 2025. Zobrazit nejnovější verzi.

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/530526/1558017/ProgressUI-Module.js

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